diff --git a/txnbuild/CHANGELOG.md b/txnbuild/CHANGELOG.md index a185b154f5..ee61d6a1d2 100644 --- a/txnbuild/CHANGELOG.md +++ b/txnbuild/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [v4.2.0](https://github.com/stellar/go/releases/tag/horizonclient-v4.2.0) - 2020-11-11 + +* Add `HashHex()`, `SignWithKeyString()`, `SignHashX()`, and `AddSignatureBase64()` functions back to `FeeBumpTransaction` ([#3199](https://github.com/stellar/go/pull/3199)). + ## [v4.1.0](https://github.com/stellar/go/releases/tag/horizonclient-v4.1.0) - 2020-10-16 * Add helper function `ParseAssetString()`, making it easier to build an `Asset` structure from a string in [canonical form](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0011.md#asset) and check its various properties ([#3105](https://github.com/stellar/go/pull/3105)). diff --git a/txnbuild/fee_bump_test.go b/txnbuild/fee_bump_test.go index 12b9cad8ef..05290fce84 100644 --- a/txnbuild/fee_bump_test.go +++ b/txnbuild/fee_bump_test.go @@ -1,6 +1,7 @@ package txnbuild import ( + "crypto/sha256" "encoding/base64" "github.com/stellar/go/network" "testing" @@ -191,6 +192,161 @@ func TestFeeBumpAllowsFeeAccountToEqualInnerSourceAccount(t *testing.T) { assert.NoError(t, err) } +func TestFeeBumpSignWithKeyString(t *testing.T) { + kp0, kp1 := newKeypair0(), newKeypair1() + sourceAccount := NewSimpleAccount(kp0.Address(), 1) + + tx, err := NewTransaction( + TransactionParams{ + SourceAccount: &sourceAccount, + Operations: []Operation{&Inflation{}}, + BaseFee: MinBaseFee, + Timebounds: NewInfiniteTimeout(), + }, + ) + assert.NoError(t, err) + tx, err = tx.Sign(network.TestNetworkPassphrase, kp0) + assert.NoError(t, err) + + feeBumpTx, err := NewFeeBumpTransaction( + FeeBumpTransactionParams{ + FeeAccount: kp1.Address(), + BaseFee: 2 * MinBaseFee, + Inner: tx, + }, + ) + assert.NoError(t, err) + feeBumpTx, err = feeBumpTx.Sign(network.TestNetworkPassphrase, kp1) + assert.NoError(t, err) + expectedBase64, err := feeBumpTx.Base64() + assert.NoError(t, err) + + feeBumpTx, err = NewFeeBumpTransaction( + FeeBumpTransactionParams{ + FeeAccount: kp1.Address(), + BaseFee: 2 * MinBaseFee, + Inner: tx, + }, + ) + assert.NoError(t, err) + feeBumpTx, err = feeBumpTx.SignWithKeyString(network.TestNetworkPassphrase, kp1.Seed()) + assert.NoError(t, err) + base64, err := feeBumpTx.Base64() + assert.NoError(t, err) + + assert.Equal(t, expectedBase64, base64) +} + +func TestFeeBumpSignHashX(t *testing.T) { + // 256 bit preimage + preimage := "this is a preimage for hashx transactions on the stellar network" + preimageHash := sha256.Sum256([]byte(preimage)) + + kp0, kp1 := newKeypair0(), newKeypair1() + payment := Payment{ + Destination: "GCCOBXW2XQNUSL467IEILE6MMCNRR66SSVL4YQADUNYYNUVREF3FIV2Z", + Amount: "10", + Asset: NativeAsset{}, + } + sourceAccount := NewSimpleAccount(kp0.Address(), int64(4353383146192899)) + + tx, err := NewTransaction( + TransactionParams{ + SourceAccount: &sourceAccount, + IncrementSequenceNum: true, + Operations: []Operation{&payment}, + BaseFee: MinBaseFee, + Timebounds: NewInfiniteTimeout(), + }, + ) + assert.NoError(t, err) + tx, err = tx.Sign(network.TestNetworkPassphrase, kp0) + assert.NoError(t, err) + + feeBumpTx, err := NewFeeBumpTransaction( + FeeBumpTransactionParams{ + FeeAccount: kp1.Address(), + BaseFee: 2 * MinBaseFee, + Inner: tx, + }, + ) + assert.NoError(t, err) + feeBumpTx, err = feeBumpTx.SignHashX([]byte(preimage)) + assert.NoError(t, err) + + signatures := feeBumpTx.Signatures() + assert.Len(t, signatures, 1) + assert.Equal(t, xdr.Signature(preimage), signatures[0].Signature) + var expectedHint [4]byte + copy(expectedHint[:], preimageHash[28:]) + assert.Equal(t, xdr.SignatureHint(expectedHint), signatures[0].Hint) +} + +func TestFeeBumpAddSignatureBase64(t *testing.T) { + kp0 := newKeypair0() + kp1 := newKeypair1() + kp2 := newKeypair2() + txSource := NewSimpleAccount(kp0.Address(), int64(9605939170639897)) + opSource := NewSimpleAccount(kp1.Address(), 0) + createAccount := CreateAccount{ + Destination: "GCCOBXW2XQNUSL467IEILE6MMCNRR66SSVL4YQADUNYYNUVREF3FIV2Z", + Amount: "10", + SourceAccount: &opSource, + } + + inner, err := NewTransaction( + TransactionParams{ + SourceAccount: &txSource, + IncrementSequenceNum: true, + Operations: []Operation{&createAccount}, + BaseFee: MinBaseFee, + Timebounds: NewInfiniteTimeout(), + }, + ) + assert.NoError(t, err) + inner, err = inner.Sign(network.TestNetworkPassphrase, kp0) + assert.NoError(t, err) + + tx, err := NewFeeBumpTransaction( + FeeBumpTransactionParams{ + FeeAccount: kp1.Address(), + BaseFee: 2 * MinBaseFee, + Inner: inner, + }, + ) + assert.NoError(t, err) + tx, err = tx.Sign(network.TestNetworkPassphrase, kp1, kp2) + assert.NoError(t, err) + expected, err := tx.Base64() + assert.NoError(t, err) + signatures := tx.Signatures() + + otherTx, err := NewFeeBumpTransaction( + FeeBumpTransactionParams{ + FeeAccount: kp1.Address(), + BaseFee: 2 * MinBaseFee, + Inner: inner, + }, + ) + assert.NoError(t, err) + otherTx, err = otherTx.AddSignatureBase64( + network.TestNetworkPassphrase, + kp1.Address(), + base64.StdEncoding.EncodeToString(signatures[0].Signature), + ) + assert.NoError(t, err) + otherTx, err = otherTx.AddSignatureBase64( + network.TestNetworkPassphrase, + kp2.Address(), + base64.StdEncoding.EncodeToString(signatures[1].Signature), + ) + assert.NoError(t, err) + b64, err := tx.Base64() + assert.NoError(t, err) + + assert.Equal(t, expected, b64) +} + func TestFeeBumpRoundTrip(t *testing.T) { kp0, kp1 := newKeypair0(), newKeypair1() sourceAccount := NewSimpleAccount(kp0.Address(), 1) @@ -228,6 +384,9 @@ func TestFeeBumpRoundTrip(t *testing.T) { assert.Equal(t, int64(2*MinBaseFee), feeBumpTx.BaseFee()) assert.Equal(t, int64(4*MinBaseFee), feeBumpTx.MaxFee()) + outerHash, err := feeBumpTx.HashHex(network.TestNetworkPassphrase) + assert.NoError(t, err) + env, err := feeBumpTx.TxEnvelope() assert.NoError(t, err) assert.Equal(t, xdr.EnvelopeTypeEnvelopeTypeTxFeeBump, env.Type) @@ -259,6 +418,9 @@ func TestFeeBumpRoundTrip(t *testing.T) { _, ok = parsed.Transaction() assert.False(t, ok) + parsedHash, err := parsedFeeBump.HashHex(network.TestNetworkPassphrase) + assert.NoError(t, err) + assert.Equal(t, feeBumpTx.Signatures(), parsedFeeBump.Signatures()) assert.Equal(t, kp1.Address(), parsedFeeBump.FeeAccount()) assert.Equal(t, int64(2*MinBaseFee), parsedFeeBump.BaseFee()) @@ -272,4 +434,5 @@ func TestFeeBumpRoundTrip(t *testing.T) { b64, err = parsedFeeBump.Base64() assert.NoError(t, err) assert.Equal(t, expectedFeeBumpB64, b64) + assert.Equal(t, outerHash, parsedHash) } diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 67965bbca2..bd7e7cdcd9 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -440,6 +440,12 @@ func (t *FeeBumpTransaction) Hash(networkStr string) ([32]byte, error) { return network.HashTransactionInEnvelope(t.envelope, networkStr) } +// HashHex returns the network specific hash of this transaction +// encoded as a hexadecimal string. +func (t *FeeBumpTransaction) HashHex(network string) (string, error) { + return hashHex(t.envelope, network) +} + // Sign returns a new FeeBumpTransaction instance which extends the current instance // with additional signatures derived from the given list of keypair instances. func (t *FeeBumpTransaction) Sign(network string, kps ...*keypair.Full) (*FeeBumpTransaction, error) { @@ -454,6 +460,45 @@ func (t *FeeBumpTransaction) Sign(network string, kps ...*keypair.Full) (*FeeBum return newTx, nil } +// SignWithKeyString returns a new FeeBumpTransaction instance which extends the current instance +// with additional signatures derived from the given list of private key strings. +func (t *FeeBumpTransaction) SignWithKeyString(network string, keys ...string) (*FeeBumpTransaction, error) { + kps, err := stringsToKP(keys...) + if err != nil { + return nil, err + } + return t.Sign(network, kps...) +} + +// SignHashX returns a new FeeBumpTransaction instance which extends the current instance +// with HashX signature type. +// See description here: https://www.stellar.org/developers/guides/concepts/multi-sig.html#hashx. +func (t *FeeBumpTransaction) SignHashX(preimage []byte) (*FeeBumpTransaction, error) { + extendedSignatures, err := concatHashX(t.signatures, preimage) + if err != nil { + return nil, err + } + + newTx := new(FeeBumpTransaction) + *newTx = *t + newTx.signatures = extendedSignatures + return newTx, nil +} + +// AddSignatureBase64 returns a new FeeBumpTransaction instance which extends the current instance +// with an additional signature derived from the given base64-encoded signature. +func (t *FeeBumpTransaction) AddSignatureBase64(network, publicKey, signature string) (*FeeBumpTransaction, error) { + extendedSignatures, err := concatSignatureBase64(t.envelope, t.signatures, network, publicKey, signature) + if err != nil { + return nil, err + } + + newTx := new(FeeBumpTransaction) + *newTx = *t + newTx.signatures = extendedSignatures + return newTx, nil +} + // TxEnvelope returns the a xdr.TransactionEnvelope instance which is // equivalent to this transaction. func (t *FeeBumpTransaction) TxEnvelope() (xdr.TransactionEnvelope, error) {