From b75db478035f57953cdd7c6670b6d21bb6aac053 Mon Sep 17 00:00:00 2001 From: ire-and-curses Date: Tue, 2 Jul 2019 16:15:40 -0700 Subject: [PATCH 1/7] SEP 10 challenge builder - test failing --- txnbuild/transaction.go | 69 +++++++++++++++++++++++++++++++++++- txnbuild/transaction_test.go | 14 ++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 839edececa..07edef758a 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -11,6 +11,7 @@ package txnbuild import ( "bytes" + "crypto/rand" "encoding/base64" "fmt" @@ -25,7 +26,8 @@ import ( type Account interface { GetAccountID() string IncrementSequenceNumber() (xdr.SequenceNumber, error) - // To do: implement in v2.0.0: add GetSequenceNumber method + // Action needed in release: v2.0.0 + // TODO: add GetSequenceNumber method // GetSequenceNumber() (xdr.SequenceNumber, error) } @@ -177,3 +179,68 @@ func (tx *Transaction) BuildSignEncode(keypairs ...*keypair.Full) (string, error return txeBase64, err } + +// BuildChallengeTx is a factory method that creates a valid SEP 10 challenge, for use in web authentication. +func BuildChallengeTx(accountID, anchorName, network string, fee uint32) (Transaction, error) { + randomNonce, err := GenerateRandomString(64) + if err != nil { + return Transaction{}, err + } + + sa := SimpleAccount{ + AccountID: accountID, + } + + // Create a SEP 10 compatible response. See + // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#response + + tx := Transaction{ + SourceAccount: &sa, + Operations: []Operation{ + &ManageData{ + SourceAccount: &sa, + Name: anchorName + " auth", + Value: []byte(randomNonce), + }, + }, + Timebounds: NewTimeout(300), + Network: network, + } + + // Action needed in release: v2.0.0 + // TODO: remove this and use Build() with optional argument (https://github.com/stellar/go/issues/1259) + tx.xdrTransaction.SourceAccount.SetAddress(tx.SourceAccount.GetAccountID()) + + tx.xdrTransaction.SeqNum = 0 + for _, op := range tx.Operations { + xdrOperation, err := op.BuildXDR() + if err != nil { + return Transaction{}, errors.Wrap(err, fmt.Sprintf("failed to build operation %T", op)) + } + tx.xdrTransaction.Operations = append(tx.xdrTransaction.Operations, xdrOperation) + } + + tx.xdrTransaction.TimeBounds = &xdr.TimeBounds{MinTime: xdr.TimePoint(tx.Timebounds.MinTime), + MaxTime: xdr.TimePoint(tx.Timebounds.MaxTime)} + + tx.xdrTransaction.Fee = xdr.Uint32(fee) + + if tx.xdrEnvelope == nil { + tx.xdrEnvelope = &xdr.TransactionEnvelope{} + tx.xdrEnvelope.Tx = tx.xdrTransaction + } + + return tx, nil +} + +// GenerateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes. +func GenerateRandomString(n int) (string, error) { + bytes := make([]byte, n) + _, err := rand.Read(bytes) + + if err != nil { + return "", err + } + + return base64.URLEncoding.EncodeToString(bytes), err +} diff --git a/txnbuild/transaction_test.go b/txnbuild/transaction_test.go index 61ef5821ec..0c503cb56e 100644 --- a/txnbuild/transaction_test.go +++ b/txnbuild/transaction_test.go @@ -708,3 +708,17 @@ func TestManageBuyOfferUpdateOffer(t *testing.T) { expected := "AAAAACXK8doPx27P6IReQlRRuweSSUiUfjqgyswxiu3Sh2R+AAAAZAAAJWoAAAAKAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAMAAAAAAAAAAFBQkNEAAAAACXK8doPx27P6IReQlRRuweSSUiUfjqgyswxiu3Sh2R+AAAAAB3NZQAAAAABAAAAMgAAAAAALJSWAAAAAAAAAAHSh2R+AAAAQK/sasTxgNqvkz3dGaDOyUgfa9UAAmUBmgiyaQU1dMlNNvTVH1D7PQKXkTooWmb6qK7Ee8vaTCFU6gGmShhA9wE=" assert.Equal(t, expected, received, "Base 64 XDR should match") } + +func TestBuildChallengeTx(t *testing.T) { + kp0 := newKeypair0() + + tx, err := BuildChallengeTx(kp0.Address(), "SDF", network.TestNetworkPassphrase, 100) + assert.NoError(t, err) + err = tx.Sign(kp0) + assert.NoError(t, err) + txeBase64, err := tx.Base64() + assert.NoError(t, err) + + expected := "TODO: TX here" + assert.Equal(t, expected, txeBase64, "Base 64 XDR should match") +} From 0e9087ebd8bba47a5e1ec1787269823267468236 Mon Sep 17 00:00:00 2001 From: oliha Date: Wed, 3 Jul 2019 09:48:22 -0400 Subject: [PATCH 2/7] fix challenge txbuild --- txnbuild/transaction.go | 75 +++++++++++++++++++++--------------- txnbuild/transaction_test.go | 10 ++--- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 07edef758a..359fbcda8a 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -14,6 +14,7 @@ import ( "crypto/rand" "encoding/base64" "fmt" + "time" "github.com/stellar/go/keypair" "github.com/stellar/go/network" @@ -181,56 +182,66 @@ func (tx *Transaction) BuildSignEncode(keypairs ...*keypair.Full) (string, error } // BuildChallengeTx is a factory method that creates a valid SEP 10 challenge, for use in web authentication. -func BuildChallengeTx(accountID, anchorName, network string, fee uint32) (Transaction, error) { - randomNonce, err := GenerateRandomString(64) +// "randomNonce" is a base64 encoded 64 byte long random string. +// "timebound" is the number of seconds the transaction should be valid for, O means infinity. +// More details on SEP 10: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md +func BuildChallengeTx(serverSignerSecret, clientAccountID, + anchorName, network string, fee uint32, randomNonce string, timebound int64) (string, error) { + serverKP, err := keypair.Parse(serverSignerSecret) if err != nil { - return Transaction{}, err + return "", err + } + + randomNonceBytes, err := base64.StdEncoding.DecodeString(randomNonce) + if err != nil { + return "", errors.Wrap(err, "failed to decode random nonce") } + if len(randomNonceBytes) != 64 { + return "", errors.New("64 byte long random nonce required") + } + + // represent server signing account as SimpleAccount sa := SimpleAccount{ - AccountID: accountID, + AccountID: serverKP.Address(), + // Action needed in release: v2.0.0 + // TODO: remove this and use "Sequence: 0" and build transaction with optional argument + // (https://github.com/stellar/go/issues/1259) + Sequence: int64(-1), + } + + // represent client account as SimpleAccount + ca := SimpleAccount{ + AccountID: clientAccountID, + } + + txTimebound := NewInfiniteTimeout() + + if timebound > 0 { + txTimebound = NewTimebounds(time.Now().UTC().Unix(), time.Now().UTC().Unix()+timebound) } // Create a SEP 10 compatible response. See // https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#response - tx := Transaction{ SourceAccount: &sa, Operations: []Operation{ &ManageData{ - SourceAccount: &sa, + SourceAccount: &ca, Name: anchorName + " auth", - Value: []byte(randomNonce), + Value: randomNonceBytes, }, }, - Timebounds: NewTimeout(300), + Timebounds: txTimebound, Network: network, + BaseFee: fee, } - // Action needed in release: v2.0.0 - // TODO: remove this and use Build() with optional argument (https://github.com/stellar/go/issues/1259) - tx.xdrTransaction.SourceAccount.SetAddress(tx.SourceAccount.GetAccountID()) - - tx.xdrTransaction.SeqNum = 0 - for _, op := range tx.Operations { - xdrOperation, err := op.BuildXDR() - if err != nil { - return Transaction{}, errors.Wrap(err, fmt.Sprintf("failed to build operation %T", op)) - } - tx.xdrTransaction.Operations = append(tx.xdrTransaction.Operations, xdrOperation) - } - - tx.xdrTransaction.TimeBounds = &xdr.TimeBounds{MinTime: xdr.TimePoint(tx.Timebounds.MinTime), - MaxTime: xdr.TimePoint(tx.Timebounds.MaxTime)} - - tx.xdrTransaction.Fee = xdr.Uint32(fee) - - if tx.xdrEnvelope == nil { - tx.xdrEnvelope = &xdr.TransactionEnvelope{} - tx.xdrEnvelope.Tx = tx.xdrTransaction + txeB64, err := tx.BuildSignEncode(serverKP.(*keypair.Full)) + if err != nil { + return "", err } - - return tx, nil + return txeB64, nil } // GenerateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes. @@ -242,5 +253,5 @@ func GenerateRandomString(n int) (string, error) { return "", err } - return base64.URLEncoding.EncodeToString(bytes), err + return base64.StdEncoding.EncodeToString(bytes), err } diff --git a/txnbuild/transaction_test.go b/txnbuild/transaction_test.go index 0c503cb56e..9cdfcaa1ae 100644 --- a/txnbuild/transaction_test.go +++ b/txnbuild/transaction_test.go @@ -712,13 +712,11 @@ func TestManageBuyOfferUpdateOffer(t *testing.T) { func TestBuildChallengeTx(t *testing.T) { kp0 := newKeypair0() - tx, err := BuildChallengeTx(kp0.Address(), "SDF", network.TestNetworkPassphrase, 100) - assert.NoError(t, err) - err = tx.Sign(kp0) - assert.NoError(t, err) - txeBase64, err := tx.Base64() + // use GenerateRandomString(64) to get randomNonce like below + randomNonce := "faFEv1xTU4s58f9DVUXkHqKb6Vhj5EPSjPGV5bx0ceHK7/N6ftdlNk2FtqWx+XLVOmh2Q7W/6ZoKmd1uYZW24A==" + txeBase64, err := BuildChallengeTx(kp0.Seed(), kp0.Address(), "SDF", network.TestNetworkPassphrase, 500, randomNonce, 0) assert.NoError(t, err) - expected := "TODO: TX here" + expected := "AAAAAODcbeFyXKxmUWK1L6znNbKKIkPkHRJNbLktcKPqLnLFAAAB9AAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAA4Nxt4XJcrGZRYrUvrOc1sooiQ+QdEk1suS1wo+oucsUAAAAKAAAACFNERiBhdXRoAAAAAQAAAEB9oUS/XFNTiznx/0NVReQeopvpWGPkQ9KM8ZXlvHRx4crv83p+12U2TYW2pbH5ctU6aHZDtb/pmgqZ3W5hlbbgAAAAAAAAAAHqLnLFAAAAQCzOl4lvDkW3aMs867Axz/2s09OqVZ6zjYbdyaaCutj63yHf098QhJWdFPv38ZGrUrfCitF2BsznwgUX0czATAM=" assert.Equal(t, expected, txeBase64, "Base 64 XDR should match") } From da636508eff828d6a0d3f78452cdd58f82fc706e Mon Sep 17 00:00:00 2001 From: oliha Date: Wed, 3 Jul 2019 10:22:44 -0400 Subject: [PATCH 3/7] add example --- txnbuild/example_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/txnbuild/example_test.go b/txnbuild/example_test.go index 1842530512..778ac621bf 100644 --- a/txnbuild/example_test.go +++ b/txnbuild/example_test.go @@ -464,3 +464,18 @@ func ExampleManageBuyOffer() { // Output: AAAAAH4RyzTWNfXhqwLUoCw91aWkZtgIzY8SAVkIPc0uFVmYAAAAZAAMoj8AAAAEAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAMAAAAAAAAAAFBQkNEAAAAAODcbeFyXKxmUWK1L6znNbKKIkPkHRJNbLktcKPqLnLFAAAAADuaygAAAAABAAAAZAAAAAAAAAAAAAAAAAAAAAEuFVmYAAAAQPh8h1TrzDpcgzB/VE8V0X2pFGV8/JyuYrx0I5bRfBJuLJr0l8yL1isP1wZjvMdX7fNiktwSLuUuj749nWA6wAo= } + +func ExampleBuildChallengeTx() { + // Generate random nonce + randomNonce, err := GenerateRandomString(64) + serverSignerSeed := "SBZVMB74Z76QZ3ZOY7UTDFYKMEGKW5XFJEB6PFKBF4UYSSWHG4EDH7PY" + clientAccountID := "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" + anchorName := "SDF" + fee := uint32(400) + timebound := int64(300) + + // tx envelope returned here is ignored because it will be different for each run and cause tests to fail + _, err = BuildChallengeTx(serverSignerSeed, clientAccountID, anchorName, + network.TestNetworkPassphrase, fee, randomNonce, timebound) + check(err) +} From 8fc9939e359f818a3290068fce0a2cd1236ed93b Mon Sep 17 00:00:00 2001 From: oliha Date: Wed, 3 Jul 2019 14:10:11 -0400 Subject: [PATCH 4/7] add default values to txnbuild.BuildChallengeTx --- txnbuild/transaction.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 359fbcda8a..b19b112f14 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -192,6 +192,13 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, return "", err } + if randomNonce == "" { + randomNonce, err = GenerateRandomString(64) + if err != nil { + return "", err + } + } + randomNonceBytes, err := base64.StdEncoding.DecodeString(randomNonce) if err != nil { return "", errors.Wrap(err, "failed to decode random nonce") @@ -218,7 +225,8 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, txTimebound := NewInfiniteTimeout() if timebound > 0 { - txTimebound = NewTimebounds(time.Now().UTC().Unix(), time.Now().UTC().Unix()+timebound) + currentTime := time.Now().UTC().Unix() + txTimebound = NewTimebounds(currentTime, currentTime+timebound) } // Create a SEP 10 compatible response. See From 2dedf5198031b821f7aa6e4eb2bb24f03dbe5f7f Mon Sep 17 00:00:00 2001 From: oliha Date: Tue, 16 Jul 2019 11:19:19 -0400 Subject: [PATCH 5/7] fix timebounds and tests --- txnbuild/example_test.go | 9 ++++----- txnbuild/transaction.go | 27 +++++++++++--------------- txnbuild/transaction_test.go | 37 +++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/txnbuild/example_test.go b/txnbuild/example_test.go index 51c451d8ff..8b647ef8b1 100644 --- a/txnbuild/example_test.go +++ b/txnbuild/example_test.go @@ -2,6 +2,7 @@ package txnbuild import ( "fmt" + "time" "github.com/stellar/go/keypair" "github.com/stellar/go/network" @@ -505,15 +506,13 @@ func ExampleManageBuyOffer() { func ExampleBuildChallengeTx() { // Generate random nonce - randomNonce, err := GenerateRandomString(64) serverSignerSeed := "SBZVMB74Z76QZ3ZOY7UTDFYKMEGKW5XFJEB6PFKBF4UYSSWHG4EDH7PY" clientAccountID := "GDQNY3PBOJOKYZSRMK2S7LHHGWZIUISD4QORETLMXEWXBI7KFZZMKTL3" anchorName := "SDF" - fee := uint32(400) - timebound := int64(300) + timebound := time.Duration(5 * time.Minute) // tx envelope returned here is ignored because it will be different for each run and cause tests to fail - _, err = BuildChallengeTx(serverSignerSeed, clientAccountID, anchorName, - network.TestNetworkPassphrase, fee, randomNonce, timebound) + _, err := BuildChallengeTx(serverSignerSeed, clientAccountID, anchorName, + network.TestNetworkPassphrase, timebound) check(err) } diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index e4a5ac4d78..89bbdb87d9 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -200,23 +200,18 @@ func (tx *Transaction) BuildSignEncode(keypairs ...*keypair.Full) (string, error return txeBase64, err } - // BuildChallengeTx is a factory method that creates a valid SEP 10 challenge, for use in web authentication. -// "randomNonce" is a base64 encoded 64 byte long random string. -// "timebound" is the number of seconds the transaction should be valid for, O means infinity. +// "timebound" is the time duration the transaction should be valid for, O means infinity. // More details on SEP 10: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md -func BuildChallengeTx(serverSignerSecret, clientAccountID, - anchorName, network string, fee uint32, randomNonce string, timebound int64) (string, error) { +func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network string, timebound time.Duration) (string, error) { serverKP, err := keypair.Parse(serverSignerSecret) if err != nil { return "", err } - if randomNonce == "" { - randomNonce, err = GenerateRandomString(64) - if err != nil { - return "", err - } + randomNonce, err := generateRandomString(64) + if err != nil { + return "", err } randomNonceBytes, err := base64.StdEncoding.DecodeString(randomNonce) @@ -243,10 +238,10 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, } txTimebound := NewInfiniteTimeout() - if timebound > 0 { - currentTime := time.Now().UTC().Unix() - txTimebound = NewTimebounds(currentTime, currentTime+timebound) + currentTime := time.Now().UTC() + maxTime := currentTime.Add(timebound) + txTimebound = NewTimebounds(currentTime.Unix(), maxTime.Unix()) } // Create a SEP 10 compatible response. See @@ -262,7 +257,7 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, }, Timebounds: txTimebound, Network: network, - BaseFee: fee, + BaseFee: uint32(100), } txeB64, err := tx.BuildSignEncode(serverKP.(*keypair.Full)) @@ -272,8 +267,8 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, return txeB64, nil } -// GenerateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes. -func GenerateRandomString(n int) (string, error) { +// generateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes. +func generateRandomString(n int) (string, error) { bytes := make([]byte, n) _, err := rand.Read(bytes) diff --git a/txnbuild/transaction_test.go b/txnbuild/transaction_test.go index c249601363..5f38b882ee 100644 --- a/txnbuild/transaction_test.go +++ b/txnbuild/transaction_test.go @@ -3,6 +3,7 @@ package txnbuild import ( "crypto/sha256" "testing" + "time" "github.com/stellar/go/network" "github.com/stellar/go/strkey" @@ -739,13 +740,39 @@ func TestManageBuyOfferUpdateOffer(t *testing.T) { func TestBuildChallengeTx(t *testing.T) { kp0 := newKeypair0() - // use GenerateRandomString(64) to get randomNonce like below - randomNonce := "faFEv1xTU4s58f9DVUXkHqKb6Vhj5EPSjPGV5bx0ceHK7/N6ftdlNk2FtqWx+XLVOmh2Q7W/6ZoKmd1uYZW24A==" - txeBase64, err := BuildChallengeTx(kp0.Seed(), kp0.Address(), "SDF", network.TestNetworkPassphrase, 500, randomNonce, 0) + // infinite timebound + txeBase64, err := BuildChallengeTx(kp0.Seed(), kp0.Address(), "SDF", network.TestNetworkPassphrase, 0) + assert.NoError(t, err) + var txXDR xdr.TransactionEnvelope + err = xdr.SafeUnmarshalBase64(txeBase64, &txXDR) + assert.NoError(t, err) + assert.Equal(t, xdr.SequenceNumber(0), txXDR.Tx.SeqNum, "sequence number should be 0") + assert.Equal(t, xdr.Uint32(100), txXDR.Tx.Fee, "Fee should be 100") + assert.Equal(t, 1, len(txXDR.Tx.Operations), "number operations should be 1") + assert.Equal(t, xdr.TimePoint(0), xdr.TimePoint(txXDR.Tx.TimeBounds.MinTime), "Min time should be 0") + assert.Equal(t, xdr.TimePoint(0), xdr.TimePoint(txXDR.Tx.TimeBounds.MaxTime), "Max time should be 0") + op := txXDR.Tx.Operations[0] + assert.Equal(t, xdr.OperationTypeManageData, op.Body.Type, "operation type should be manage data") + assert.Equal(t, xdr.String64("SDF auth"), op.Body.ManageDataOp.DataName, "DataName should be 'SDF auth'") + assert.Equal(t, 64, len(*op.Body.ManageDataOp.DataValue), "DataValue should be 64 bytes") + + // 5 minutes timebound + txeBase64, err = BuildChallengeTx(kp0.Seed(), kp0.Address(), "SDF1", network.TestNetworkPassphrase, time.Duration(5*time.Minute)) assert.NoError(t, err) - expected := "AAAAAODcbeFyXKxmUWK1L6znNbKKIkPkHRJNbLktcKPqLnLFAAAB9AAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAA4Nxt4XJcrGZRYrUvrOc1sooiQ+QdEk1suS1wo+oucsUAAAAKAAAACFNERiBhdXRoAAAAAQAAAEB9oUS/XFNTiznx/0NVReQeopvpWGPkQ9KM8ZXlvHRx4crv83p+12U2TYW2pbH5ctU6aHZDtb/pmgqZ3W5hlbbgAAAAAAAAAAHqLnLFAAAAQCzOl4lvDkW3aMs867Axz/2s09OqVZ6zjYbdyaaCutj63yHf098QhJWdFPv38ZGrUrfCitF2BsznwgUX0czATAM=" - assert.Equal(t, expected, txeBase64, "Base 64 XDR should match") + var txXDR1 xdr.TransactionEnvelope + err = xdr.SafeUnmarshalBase64(txeBase64, &txXDR1) + assert.NoError(t, err) + assert.Equal(t, xdr.SequenceNumber(0), txXDR1.Tx.SeqNum, "sequence number should be 0") + assert.Equal(t, xdr.Uint32(100), txXDR1.Tx.Fee, "Fee should be 100") + assert.Equal(t, 1, len(txXDR1.Tx.Operations), "number operations should be 1") + + timeDiff := txXDR1.Tx.TimeBounds.MaxTime - txXDR1.Tx.TimeBounds.MinTime + assert.Equal(t, int64(300), int64(timeDiff), "time difference should be 300 seconds") + op1 := txXDR1.Tx.Operations[0] + assert.Equal(t, xdr.OperationTypeManageData, op1.Body.Type, "operation type should be manage data") + assert.Equal(t, xdr.String64("SDF1 auth"), op1.Body.ManageDataOp.DataName, "DataName should be 'SDF1 auth'") + assert.Equal(t, 64, len(*op1.Body.ManageDataOp.DataValue), "DataValue should be 64 bytes") } func TestHashHex(t *testing.T) { From 6dab03ae22dbe0e8c446dcdd9d5949731670bcda Mon Sep 17 00:00:00 2001 From: oliha Date: Tue, 16 Jul 2019 11:25:31 -0400 Subject: [PATCH 6/7] update txnbuild changelog --- txnbuild/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/txnbuild/CHANGELOG.md b/txnbuild/CHANGELOG.md index cb89ea9ea3..ef013c441a 100644 --- a/txnbuild/CHANGELOG.md +++ b/txnbuild/CHANGELOG.md @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased + +* Add `Transaction.BuildChallengeTx` method for building [SEP-10](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md) challenge transaction. + + ## [v1.3.0](https://github.com/stellar/go/releases/tag/horizonclient-v1.3.0) - 2019-07-08 * Add support for getting the hex-encoded transaction hash with `Transaction.HashHex` method. From d0897b023f32e4096b474b2ab037e98600b25455 Mon Sep 17 00:00:00 2001 From: oliha Date: Tue, 16 Jul 2019 13:46:10 -0400 Subject: [PATCH 7/7] review changes --- txnbuild/example_test.go | 6 +++--- txnbuild/helpers_test.go | 15 +++++++++++++++ txnbuild/transaction.go | 19 +++++++------------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/txnbuild/example_test.go b/txnbuild/example_test.go index 8b647ef8b1..308e6d5d74 100644 --- a/txnbuild/example_test.go +++ b/txnbuild/example_test.go @@ -511,8 +511,8 @@ func ExampleBuildChallengeTx() { anchorName := "SDF" timebound := time.Duration(5 * time.Minute) - // tx envelope returned here is ignored because it will be different for each run and cause tests to fail - _, err := BuildChallengeTx(serverSignerSeed, clientAccountID, anchorName, - network.TestNetworkPassphrase, timebound) + tx, err := BuildChallengeTx(serverSignerSeed, clientAccountID, anchorName, network.TestNetworkPassphrase, timebound) + _, err = checkChallengeTx(tx, anchorName) + check(err) } diff --git a/txnbuild/helpers_test.go b/txnbuild/helpers_test.go index 841ea27288..3bbfabeb5d 100644 --- a/txnbuild/helpers_test.go +++ b/txnbuild/helpers_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stellar/go/keypair" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" ) @@ -42,3 +44,16 @@ func check(err error) { panic(err) } } + +func checkChallengeTx(txeBase64, anchorName string) (bool, error) { + var txXDR xdr.TransactionEnvelope + err := xdr.SafeUnmarshalBase64(txeBase64, &txXDR) + if err != nil { + return false, err + } + op := txXDR.Tx.Operations[0] + if (xdr.OperationTypeManageData == op.Body.Type) && (op.Body.ManageDataOp.DataName == xdr.String64(anchorName+" auth")) { + return true, nil + } + return false, errors.New("invalid challenge tx") +} diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 89bbdb87d9..613eca60ce 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -209,17 +209,12 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network s return "", err } - randomNonce, err := generateRandomString(64) + randomNonce, err := generateRandomNonce(64) if err != nil { return "", err } - randomNonceBytes, err := base64.StdEncoding.DecodeString(randomNonce) - if err != nil { - return "", errors.Wrap(err, "failed to decode random nonce") - } - - if len(randomNonceBytes) != 64 { + if len(randomNonce) != 64 { return "", errors.New("64 byte long random nonce required") } @@ -252,7 +247,7 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network s &ManageData{ SourceAccount: &ca, Name: anchorName + " auth", - Value: randomNonceBytes, + Value: randomNonce, }, }, Timebounds: txTimebound, @@ -267,16 +262,16 @@ func BuildChallengeTx(serverSignerSecret, clientAccountID, anchorName, network s return txeB64, nil } -// generateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes. -func generateRandomString(n int) (string, error) { +// generateRandomNonce creates a cryptographically secure random slice of `n` bytes. +func generateRandomNonce(n int) ([]byte, error) { bytes := make([]byte, n) _, err := rand.Read(bytes) if err != nil { - return "", err + return []byte{}, err } - return base64.StdEncoding.EncodeToString(bytes), err + return bytes, err } // HashHex returns the hex-encoded hash of the transaction.