Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions txnbuild/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,10 @@
// Muxed accounts or ID memos can be provided to identity a user of a shared Stellar account.
// More details on SEP 10: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md
func BuildChallengeTx(serverSignerSecret, clientAccountID, webAuthDomain, homeDomain, network string, timebound time.Duration, memo *MemoID) (*Transaction, error) {
return BuildChallengeTxWithClientDomain(serverSignerSecret, clientAccountID, webAuthDomain, homeDomain, network, timebound, memo, nil, nil)
}

func BuildChallengeTxWithClientDomain(serverSignerSecret, clientAccountID, webAuthDomain, homeDomain, network string, timebound time.Duration, memo *MemoID, clientDomain, clientDomainAccountID *string) (*Transaction, error) {

Check failure on line 1046 in txnbuild/transaction.go

View workflow job for this annotation

GitHub Actions / golangci

The line is 225 characters long, which exceeds the maximum of 140 characters. (lll)
if timebound < time.Second {
return nil, errors.New("provided timebound must be at least 1s (300s is recommended)")
}
Expand Down Expand Up @@ -1104,6 +1108,20 @@
if memo != nil {
txParams.Memo = memo
}

if clientDomain != nil && clientDomainAccountID != nil {
clientAccountId, addressToAccountIdErr := xdr.AddressToAccountId(*clientDomainAccountID)
if addressToAccountIdErr != nil {
return nil, errors.Wrapf(addressToAccountIdErr, "%s is not a valid account id or muxed account", *clientDomainAccountID)
}

txParams.Operations = append(txParams.Operations, &ManageData{
SourceAccount: clientAccountId.Address(),
Name: "client_domain",
Value: []byte(*clientDomain),
})
}

tx, err := NewTransaction(txParams)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1157,6 +1175,10 @@
// the address is muxed, or if the memo returned is non-nil, the challenge transaction
// is being used to authenticate a user of a shared Stellar account.
func ReadChallengeTx(challengeTx, serverAccountID, network, webAuthDomain string, homeDomains []string) (tx *Transaction, clientAccountID string, matchedHomeDomain string, memo *MemoID, err error) {
return ReadChallengeTxWithClientDomain(challengeTx, serverAccountID, network, webAuthDomain, homeDomains, nil, nil)
}

func ReadChallengeTxWithClientDomain(challengeTx, serverAccountID, network, webAuthDomain string, homeDomains []string, clientDomain, clientDomainAccountID *string) (tx *Transaction, clientAccountID string, matchedHomeDomain string, memo *MemoID, err error) {

Check failure on line 1181 in txnbuild/transaction.go

View workflow job for this annotation

GitHub Actions / golangci

cyclomatic complexity 37 of func `ReadChallengeTxWithClientDomain` is high (> 15) (gocyclo)
parsed, err := TransactionFromXDR(challengeTx)
if err != nil {
return tx, clientAccountID, matchedHomeDomain, memo, errors.Wrap(err, "could not parse challenge")
Expand Down Expand Up @@ -1267,6 +1289,20 @@
if !bytes.Equal(op.Value, []byte(webAuthDomain)) {
return tx, clientAccountID, matchedHomeDomain, memo, errors.Errorf("web auth domain operation value is %q but expect %q", string(op.Value), webAuthDomain)
}
case "client_domain":
if op.SourceAccount == serverAccountID {
return tx, clientAccountID, matchedHomeDomain, memo, errors.New("client domain operation must not be server source account")
}
if clientDomain != nil && !bytes.Equal(op.Value, []byte(*clientDomain)) {
return tx, clientAccountID, matchedHomeDomain, memo, errors.Errorf("client domain operation value is %q but expect %q", string(op.Value), *clientDomain)

Check failure on line 1297 in txnbuild/transaction.go

View workflow job for this annotation

GitHub Actions / golangci

The line is 156 characters long, which exceeds the maximum of 140 characters. (lll)
}
if clientDomainAccountID == nil {
return tx, clientAccountID, matchedHomeDomain, memo, errors.Errorf("client domain account id is required")
}
err = verifyTxSignature(tx, network, *clientDomainAccountID)
if err != nil {
return tx, clientAccountID, matchedHomeDomain, memo, errors.New("client domain operation is present but txn not signed by client")
}
default:
// verify unknown subsequent operations are manage data ops with source account set to server account
if op.SourceAccount != serverAccountID {
Expand Down
200 changes: 200 additions & 0 deletions txnbuild/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,39 @@
}
}

func TestBuildChallengeTxWithClientDomain(t *testing.T) {
kp0 := newKeypair0()
kp1 := newKeypair1().Address()
clientDomain := "clientdomain.stellar.org"

Check failure on line 1163 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

string `clientdomain.stellar.org` has 5 occurrences, make it a constant (goconst)
{
// 1 minute timebound
tx, err := BuildChallengeTxWithClientDomain(kp0.Seed(), kp0.Address(), "testwebauth.stellar.org", "testanchor.stellar.org", network.TestNetworkPassphrase, time.Minute, nil, &clientDomain, &kp1)

Check failure on line 1166 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

The line is 195 characters long, which exceeds the maximum of 140 characters. (lll)
assert.NoError(t, err)
txeBase64, err := tx.Base64()
assert.NoError(t, err)
var txXDR xdr.TransactionEnvelope
err = xdr.SafeUnmarshalBase64(txeBase64, &txXDR)
assert.NoError(t, err)
assert.Equal(t, int64(0), txXDR.SeqNum(), "sequence number should be 0")
assert.Equal(t, uint32(300), txXDR.Fee(), "Fee should be 300")
assert.Equal(t, 3, len(txXDR.Operations()), "number operations should be 3")
timeDiff := txXDR.TimeBounds().MaxTime - txXDR.TimeBounds().MinTime
assert.Equal(t, int64(60), int64(timeDiff), "time difference should be 60 seconds")

Check failure on line 1177 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

G115: integer overflow conversion uint64 -> int64 (gosec)
op := txXDR.Operations()[0]
assert.Equal(t, xdr.OperationTypeManageData, op.Body.Type, "operation type should be manage data")
assert.Equal(t, xdr.String64("testanchor.stellar.org auth"), op.Body.ManageDataOp.DataName, "DataName should be 'testanchor.stellar.org auth'")

Check failure on line 1180 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

The line is 145 characters long, which exceeds the maximum of 140 characters. (lll)
assert.Equal(t, 64, len(*op.Body.ManageDataOp.DataValue), "DataValue should be 64 bytes")
webAuthOp := txXDR.Operations()[1]
assert.Equal(t, xdr.OperationTypeManageData, webAuthOp.Body.Type, "operation type should be manage data")
assert.Equal(t, xdr.String64("web_auth_domain"), webAuthOp.Body.ManageDataOp.DataName, "DataName should be 'web_auth_domain'")
assert.Equal(t, "testwebauth.stellar.org", string(*webAuthOp.Body.ManageDataOp.DataValue), "DataValue should be 'testwebauth.stellar.org'")

Check failure on line 1185 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

The line is 141 characters long, which exceeds the maximum of 140 characters. (lll)
clientDomainOp := txXDR.Operations()[2]
assert.Equal(t, xdr.OperationTypeManageData, clientDomainOp.Body.Type, "operation type should be manage data")
assert.Equal(t, xdr.String64("client_domain"), clientDomainOp.Body.ManageDataOp.DataName, "DataName should be 'client_domain'")
assert.Equal(t, "clientdomain.stellar.org", string(*clientDomainOp.Body.ManageDataOp.DataValue), "DataValue should be 'clientdomain.stellar.org'")
}
}

func TestHashHex(t *testing.T) {
kp0 := newKeypair0()
sourceAccount := NewSimpleAccount(kp0.Address(), int64(9605939170639897))
Expand Down Expand Up @@ -3037,6 +3070,173 @@
assert.EqualError(t, err, `web auth domain operation value is "testwebauth.example.org" but expect "testwebauth.stellar.org"`)
}

func TestReadChallengeTxWithClientDomain_valid(t *testing.T) {
serverKP := newKeypair0()
clientKP := newKeypair1()
clientDomainKP := newKeypair2()
clientDomainKPAddress := clientDomainKP.Address()
clientDomain := "clientdomain.stellar.org"
txSource := NewSimpleAccount(serverKP.Address(), -1)
op := ManageData{
SourceAccount: clientKP.Address(),
Name: "testanchor.stellar.org auth",
Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))),
}
webAuthDomainOp := ManageData{
SourceAccount: serverKP.Address(),
Name: "web_auth_domain",
Value: []byte("testwebauth.stellar.org"),
}
clientDomainOp := ManageData{
SourceAccount: clientDomainKPAddress,
Name: "client_domain",
Value: []byte(clientDomain),
}

tx, err := NewTransaction(
TransactionParams{
SourceAccount: &txSource,
IncrementSequenceNum: true,
Operations: []Operation{&op, &webAuthDomainOp, &clientDomainOp},
BaseFee: MinBaseFee,
Preconditions: Preconditions{TimeBounds: NewTimeout(1000)},
},
)
assert.NoError(t, err)

tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP, clientKP, clientDomainKP)
assert.NoError(t, err)
tx64, err := tx.Base64()
require.NoError(t, err)
readTx, readClientAccountID, _, _, err := ReadChallengeTxWithClientDomain(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}, &clientDomain, &clientDomainKPAddress)
assert.Equal(t, tx, readTx)
assert.Equal(t, clientKP.Address(), readClientAccountID)
assert.NoError(t, err)
}

func TestReadChallengeTxWithClientDomain_invalidNotSignedByClientDomainID(t *testing.T) {
serverKP := newKeypair0()
clientKP := newKeypair1()
clientDomainKP := newKeypair2()
clientDomainKPAddress := clientDomainKP.Address()
clientDomain := "clientdomain.stellar.org"
txSource := NewSimpleAccount(serverKP.Address(), -1)
op := ManageData{
SourceAccount: clientKP.Address(),
Name: "testanchor.stellar.org auth",
Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))),
}
webAuthDomainOp := ManageData{
SourceAccount: serverKP.Address(),
Name: "web_auth_domain",
Value: []byte("testwebauth.stellar.org"),
}
clientDomainOp := ManageData{
SourceAccount: clientDomainKPAddress,
Name: "client_domain",
Value: []byte(clientDomain),
}
tx, err := NewTransaction(
TransactionParams{
SourceAccount: &txSource,
IncrementSequenceNum: true,
Operations: []Operation{&op, &webAuthDomainOp, &clientDomainOp},
BaseFee: MinBaseFee,
Preconditions: Preconditions{TimeBounds: NewTimeout(1000)},
},
)
assert.NoError(t, err)

tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP, clientKP)
assert.NoError(t, err)
tx64, err := tx.Base64()
require.NoError(t, err)
readTx, readClientAccountID, _, _, err := ReadChallengeTxWithClientDomain(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}, &clientDomain, &clientDomainKPAddress)
assert.Equal(t, tx, readTx)
assert.Equal(t, clientKP.Address(), readClientAccountID)
assert.EqualError(t, err, "client domain operation is present but txn not signed by client")
}

func TestReadChallengeTxWithClientDomain_invalidClientDomainSourceAccount(t *testing.T) {
serverKP := newKeypair0()
clientKP := newKeypair1()
clientDomainKP := newKeypair2()
clientDomainKPAddress := clientDomainKP.Address()
clientDomain := "clientdomain.stellar.org"
txSource := NewSimpleAccount(serverKP.Address(), -1)
op1 := ManageData{
SourceAccount: clientKP.Address(),
Name: "testanchor.stellar.org auth",
Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))),
}
webAuthDomainOp := ManageData{
SourceAccount: serverKP.Address(),
Name: "web_auth_domain",
Value: []byte("testwebauth.stellar.org"),
}
clientDomainOp := ManageData{
SourceAccount: serverKP.Address(),
Name: "client_domain",
Value: []byte(clientDomain),
}
tx, err := NewTransaction(
TransactionParams{
SourceAccount: &txSource,
IncrementSequenceNum: true,
Operations: []Operation{&op1, &webAuthDomainOp, &clientDomainOp},
BaseFee: MinBaseFee,
Preconditions: Preconditions{TimeBounds: NewTimeout(300)},
},
)
assert.NoError(t, err)
tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP)
assert.NoError(t, err)
tx64, err := tx.Base64()
require.NoError(t, err)
_, _, _, _, err = ReadChallengeTxWithClientDomain(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}, &clientDomain, &clientDomainKPAddress)

Check failure on line 3196 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

declaration has 4 blank identifiers (dogsled)
assert.EqualError(t, err, `client domain operation must not be server source account`)
}

func TestReadChallengeTxWithClientDomain_invalidClientDomain(t *testing.T) {
serverKP := newKeypair0()
clientKP := newKeypair1()
clientDomainKP := newKeypair2()
clientDomainKPAddress := clientDomainKP.Address()
clientDomain := "clientdomain.stellar.org"
txSource := NewSimpleAccount(serverKP.Address(), -1)
op1 := ManageData{
SourceAccount: clientKP.Address(),
Name: "testanchor.stellar.org auth",
Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))),
}
webAuthDomainOp := ManageData{
SourceAccount: serverKP.Address(),
Name: "web_auth_domain",
Value: []byte("testwebauth.stellar.org"),
}
clientDomainOp := ManageData{
SourceAccount: clientDomainKPAddress,
Name: "client_domain",
Value: []byte("clientdomain.example.org"),
}
tx, err := NewTransaction(
TransactionParams{
SourceAccount: &txSource,
IncrementSequenceNum: true,
Operations: []Operation{&op1, &webAuthDomainOp, &clientDomainOp},
BaseFee: MinBaseFee,
Preconditions: Preconditions{TimeBounds: NewTimeout(300)},
},
)
assert.NoError(t, err)
tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP)
assert.NoError(t, err)
tx64, err := tx.Base64()
require.NoError(t, err)
_, _, _, _, err = ReadChallengeTxWithClientDomain(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}, &clientDomain, &clientDomainKPAddress)

Check failure on line 3236 in txnbuild/transaction_test.go

View workflow job for this annotation

GitHub Actions / golangci

declaration has 4 blank identifiers (dogsled)
assert.EqualError(t, err, `client domain operation value is "clientdomain.example.org" but expect "clientdomain.stellar.org"`)
}

func TestVerifyChallengeTxThreshold_invalidServer(t *testing.T) {
serverKP := newKeypair0()
clientKP := newKeypair1()
Expand Down
Loading