Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

txnbuild: SEP 10 challenge builder #1468

Merged
merged 9 commits into from
Jul 16, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
80 changes: 79 additions & 1 deletion txnbuild/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ package txnbuild

import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"time"

"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
Expand All @@ -25,7 +27,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)
}

Expand Down Expand Up @@ -177,3 +180,78 @@ 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.
// More details on SEP 10: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md
func BuildChallengeTx(serverSignerSecret, clientAccountID,
poliha marked this conversation as resolved.
Show resolved Hide resolved
anchorName, network string, fee uint32, randomNonce string, timebound int64) (string, error) {
poliha marked this conversation as resolved.
Show resolved Hide resolved
serverKP, err := keypair.Parse(serverSignerSecret)
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 {
return "", errors.New("64 byte long random nonce required")
}

// represent server signing account as SimpleAccount
sa := SimpleAccount{
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the helper method NewTimeout() here instead

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just following the spec here. The timebound should be {min: now(), max: now() + 300 }.
NewTimeout gives {min: 0, max: now() + 300 }.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, that's annoying. Ok, makes sense. Can we make just one call to Now and store in a temporary variable? That will guarantee that the timebound is always the right length.

}

// 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: &ca,
Name: anchorName + " auth",
Value: randomNonceBytes,
},
},
Timebounds: txTimebound,
Network: network,
BaseFee: fee,
}

txeB64, err := tx.BuildSignEncode(serverKP.(*keypair.Full))
if err != nil {
return "", err
}
return txeB64, nil
poliha marked this conversation as resolved.
Show resolved Hide resolved
}

// GenerateRandomString creates a base-64 encoded, cryptographically secure random string of `n` bytes.
func GenerateRandomString(n int) (string, error) {
poliha marked this conversation as resolved.
Show resolved Hide resolved
bytes := make([]byte, n)
_, err := rand.Read(bytes)

if err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(bytes), err
poliha marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions txnbuild/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,3 +708,15 @@ 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()

// 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 := "AAAAAODcbeFyXKxmUWK1L6znNbKKIkPkHRJNbLktcKPqLnLFAAAB9AAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAA4Nxt4XJcrGZRYrUvrOc1sooiQ+QdEk1suS1wo+oucsUAAAAKAAAACFNERiBhdXRoAAAAAQAAAEB9oUS/XFNTiznx/0NVReQeopvpWGPkQ9KM8ZXlvHRx4crv83p+12U2TYW2pbH5ctU6aHZDtb/pmgqZ3W5hlbbgAAAAAAAAAAHqLnLFAAAAQCzOl4lvDkW3aMs867Axz/2s09OqVZ6zjYbdyaaCutj63yHf098QhJWdFPv38ZGrUrfCitF2BsznwgUX0czATAM="
assert.Equal(t, expected, txeBase64, "Base 64 XDR should match")
}