diff --git a/client/tx/aux_builder.go b/client/tx/aux_builder.go
new file mode 100644
index 000000000000..1e0c1a91a77c
--- /dev/null
+++ b/client/tx/aux_builder.go
@@ -0,0 +1,212 @@
+package tx
+
+import (
+ "github.com/gogo/protobuf/proto"
+
+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/types/tx"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+ "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
+)
+
+// AuxTxBuilder is a client-side builder for creating an AuxSignerData.
+type AuxTxBuilder struct {
+ // msgs is used to store the sdk.Msgs that are added to the
+ // TxBuilder. It's also added inside body.Messages, because:
+ // - b.msgs is used for constructing the AMINO sign bz,
+ // - b.body is used for constructing the DIRECT_AUX sign bz.
+ msgs []sdk.Msg
+ body *tx.TxBody
+ auxSignerData *tx.AuxSignerData
+}
+
+// NewAuxTxBuilder creates a new client-side builder for constructing an
+// AuxSignerData.
+func NewAuxTxBuilder() AuxTxBuilder {
+ return AuxTxBuilder{}
+}
+
+// SetAddress sets the aux signer's bech32 address.
+func (b *AuxTxBuilder) SetAddress(addr string) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.Address = addr
+}
+
+// SetMemo sets a memo in the tx.
+func (b *AuxTxBuilder) SetMemo(memo string) {
+ b.checkEmptyFields()
+
+ b.body.Memo = memo
+}
+
+// SetTimeoutHeight sets a timeout height in the tx.
+func (b *AuxTxBuilder) SetTimeoutHeight(height uint64) {
+ b.checkEmptyFields()
+
+ b.body.TimeoutHeight = height
+}
+
+// SetMsgs sets an array of Msgs in the tx.
+func (b *AuxTxBuilder) SetMsgs(msgs ...sdk.Msg) error {
+ anys := make([]*codectypes.Any, len(msgs))
+ for i, msg := range msgs {
+ var err error
+ anys[i], err = codectypes.NewAnyWithValue(msg)
+ if err != nil {
+ return err
+ }
+ }
+
+ b.checkEmptyFields()
+
+ b.msgs = msgs
+ b.body.Messages = anys
+
+ return nil
+}
+
+// SetAccountNumber sets the aux signer's account number in the AuxSignerData.
+func (b *AuxTxBuilder) SetAccountNumber(accNum uint64) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.SignDoc.AccountNumber = accNum
+}
+
+// SetChainID sets the chain id in the AuxSignerData.
+func (b *AuxTxBuilder) SetChainID(chainID string) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.SignDoc.ChainId = chainID
+}
+
+// SetSequence sets the aux signer's sequence in the AuxSignerData.
+func (b *AuxTxBuilder) SetSequence(accSeq uint64) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.SignDoc.Sequence = accSeq
+}
+
+// SetPubKey sets the aux signer's pubkey in the AuxSignerData.
+func (b *AuxTxBuilder) SetPubKey(pk cryptotypes.PubKey) error {
+ any, err := codectypes.NewAnyWithValue(pk)
+ if err != nil {
+ return err
+ }
+
+ b.checkEmptyFields()
+
+ b.auxSignerData.SignDoc.PublicKey = any
+
+ return nil
+}
+
+// SetSignMode sets the aux signer's sign mode. Allowed sign modes are
+// DIRECT_AUX and LEGACY_AMINO_JSON.
+func (b *AuxTxBuilder) SetSignMode(mode signing.SignMode) error {
+ switch mode {
+ case signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON:
+ default:
+ return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+ }
+
+ b.auxSignerData.Mode = mode
+
+ return nil
+}
+
+// SetTip sets an optional tip.
+func (b *AuxTxBuilder) SetTip(tip *tx.Tip) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.SignDoc.Tip = tip
+}
+
+// SetSignature sets the aux signer's signature.
+func (b *AuxTxBuilder) SetSignature(sig []byte) {
+ b.checkEmptyFields()
+
+ b.auxSignerData.Sig = sig
+}
+
+// GetSignBytes returns the builder's sign bytes.
+func (b *AuxTxBuilder) GetSignBytes() ([]byte, error) {
+ auxTx := b.auxSignerData
+ if auxTx == nil {
+ return nil, sdkerrors.ErrLogic.Wrap("aux tx is nil, call setters on AuxTxBuilder first")
+ }
+
+ body := b.body
+ if body == nil {
+ return nil, sdkerrors.ErrLogic.Wrap("tx body is nil, call setters on AuxTxBuilder first")
+ }
+
+ sd := auxTx.SignDoc
+ if sd == nil {
+ return nil, sdkerrors.ErrLogic.Wrap("sign doc is nil, call setters on AuxTxBuilder first")
+ }
+
+ bodyBz, err := proto.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+
+ sd.BodyBytes = bodyBz
+
+ if err := b.auxSignerData.SignDoc.ValidateBasic(); err != nil {
+ return nil, err
+ }
+
+ var signBz []byte
+ switch b.auxSignerData.Mode {
+ case signing.SignMode_SIGN_MODE_DIRECT_AUX:
+ {
+ signBz, err = proto.Marshal(b.auxSignerData.SignDoc)
+ if err != nil {
+ return nil, err
+ }
+ }
+ case signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON:
+ {
+ signBz = legacytx.StdSignBytes(
+ b.auxSignerData.SignDoc.ChainId, b.auxSignerData.SignDoc.AccountNumber,
+ b.auxSignerData.SignDoc.Sequence, b.body.TimeoutHeight,
+ // Aux signer never signs over fee.
+ // For LEGACY_AMINO_JSON, we use the convention to sign
+ // over empty fees.
+ // ref: https://github.com/cosmos/cosmos-sdk/pull/10348
+ legacytx.StdFee{},
+ b.msgs, b.body.Memo, b.auxSignerData.SignDoc.Tip,
+ )
+ }
+ default:
+ return nil, sdkerrors.ErrInvalidRequest.Wrapf("got unknown sign mode %s", b.auxSignerData.Mode)
+ }
+
+ return signBz, nil
+}
+
+// GetAuxSignerData returns the builder's AuxSignerData.
+func (b *AuxTxBuilder) GetAuxSignerData() (tx.AuxSignerData, error) {
+ if err := b.auxSignerData.ValidateBasic(); err != nil {
+ return tx.AuxSignerData{}, err
+ }
+
+ return *b.auxSignerData, nil
+}
+
+func (b *AuxTxBuilder) checkEmptyFields() {
+ if b.body == nil {
+ b.body = &tx.TxBody{}
+ }
+
+ if b.auxSignerData == nil {
+ b.auxSignerData = &tx.AuxSignerData{}
+ if b.auxSignerData.SignDoc == nil {
+ b.auxSignerData.SignDoc = &tx.SignDocDirectAux{}
+ }
+ }
+}
diff --git a/client/tx/aux_builder_test.go b/client/tx/aux_builder_test.go
new file mode 100644
index 000000000000..385c98f4c456
--- /dev/null
+++ b/client/tx/aux_builder_test.go
@@ -0,0 +1,254 @@
+package tx_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/cosmos/cosmos-sdk/client/tx"
+ "github.com/cosmos/cosmos-sdk/codec"
+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+ "github.com/cosmos/cosmos-sdk/simapp"
+ "github.com/cosmos/cosmos-sdk/testutil/testdata"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ typestx "github.com/cosmos/cosmos-sdk/types/tx"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+)
+
+func TestAuxTxBuilder(t *testing.T) {
+ encCfg := simapp.MakeTestEncodingConfig()
+ testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
+
+ var b tx.AuxTxBuilder
+
+ testcases := []struct {
+ name string
+ malleate func() error
+ expErr bool
+ expErrStr string
+ }{
+ {
+ "cannot set SIGN_MODE_DIRECT",
+ func() error {
+ return b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT)
+ },
+ true, "AuxTxBuilder can only sign with SIGN_MODE_DIRECT_AUX or SIGN_MODE_LEGACY_AMINO_JSON",
+ },
+ {
+ "cannot set invalid pubkey",
+ func() error {
+ return b.SetPubKey(cryptotypes.PubKey(nil))
+ },
+ true, "failed packing protobuf message to Any",
+ },
+ {
+ "cannot set invalid Msg",
+ func() error {
+ return b.SetMsgs(sdk.Msg(nil))
+ },
+ true, "failed packing protobuf message to Any",
+ },
+ {
+ "GetSignBytes body should not be nil",
+ func() error {
+ _, err := b.GetSignBytes()
+ return err
+ },
+ true, "aux tx is nil, call setters on AuxTxBuilder first",
+ },
+ {
+ "GetSignBytes pubkey should not be nil",
+ func() error {
+ b.SetMsgs(msg1)
+
+ _, err := b.GetSignBytes()
+ return err
+ },
+ true, "public key cannot be empty: invalid pubkey",
+ },
+ {
+ "GetSignBytes invalid sign mode",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+
+ _, err := b.GetSignBytes()
+ return err
+ },
+ true, "got unknown sign mode SIGN_MODE_UNSPECIFIED",
+ },
+ {
+ "GetSignBytes tipper should not be nil (if tip is set)",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(&typestx.Tip{})
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ return err
+ },
+ true, "tipper cannot be empty",
+ },
+ {
+ "GetSignBytes works for DIRECT_AUX",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ return err
+ },
+ false, "",
+ },
+ {
+ "GetAuxSignerData address should not be empty",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ require.NoError(t, err)
+
+ _, err = b.GetAuxSignerData()
+ return err
+ },
+ true, "address cannot be empty: invalid request",
+ },
+ {
+ "GetAuxSignerData signature should not be empty",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ b.SetAddress(addr1.String())
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ require.NoError(t, err)
+
+ _, err = b.GetAuxSignerData()
+ return err
+ },
+ true, "signature cannot be empty: no signatures supplied",
+ },
+ {
+ "GetAuxSignerData works for DIRECT_AUX",
+ func() error {
+ b.SetAccountNumber(1)
+ b.SetSequence(2)
+ b.SetTimeoutHeight(timeoutHeight)
+ b.SetMemo(memo)
+ b.SetChainID(chainID)
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ b.SetAddress(addr1.String())
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ require.NoError(t, err)
+ b.SetSignature(rawSig)
+
+ auxSignerData, err := b.GetAuxSignerData()
+
+ // Make sure auxSignerData is correctly populated
+ checkCorrectData(t, encCfg.Codec, auxSignerData, signing.SignMode_SIGN_MODE_DIRECT_AUX)
+
+ return err
+ },
+ false, "",
+ },
+ {
+ "GetSignBytes works for LEGACY_AMINO_JSON",
+ func() error {
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ b.SetAddress(addr1.String())
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ return err
+ },
+ false, "",
+ },
+ {
+ "GetAuxSignerData works for LEGACY_AMINO_JSON",
+ func() error {
+ b.SetAccountNumber(1)
+ b.SetSequence(2)
+ b.SetTimeoutHeight(timeoutHeight)
+ b.SetMemo(memo)
+ b.SetChainID(chainID)
+ b.SetMsgs(msg1)
+ b.SetPubKey(pub1)
+ b.SetTip(tip)
+ b.SetAddress(addr1.String())
+ err := b.SetSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+ require.NoError(t, err)
+
+ _, err = b.GetSignBytes()
+ require.NoError(t, err)
+ b.SetSignature(rawSig)
+
+ auxSignerData, err := b.GetAuxSignerData()
+
+ // Make sure auxSignerData is correctly populated
+ checkCorrectData(t, encCfg.Codec, auxSignerData, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+
+ return err
+ },
+ false, "",
+ },
+ }
+
+ for _, tc := range testcases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ b = tx.NewAuxTxBuilder()
+ err := tc.malleate()
+
+ if tc.expErr {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tc.expErrStr)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+// checkCorrectData that the auxSignerData's content matches the inputs we gave.
+func checkCorrectData(t *testing.T, cdc codec.Codec, auxSignerData typestx.AuxSignerData, signMode signing.SignMode) {
+ pkAny, err := codectypes.NewAnyWithValue(pub1)
+ require.NoError(t, err)
+ msgAny, err := codectypes.NewAnyWithValue(msg1)
+ require.NoError(t, err)
+
+ var body typestx.TxBody
+ err = cdc.Unmarshal(auxSignerData.SignDoc.BodyBytes, &body)
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(1), auxSignerData.SignDoc.AccountNumber)
+ require.Equal(t, uint64(2), auxSignerData.SignDoc.Sequence)
+ require.Equal(t, timeoutHeight, body.TimeoutHeight)
+ require.Equal(t, memo, body.Memo)
+ require.Equal(t, chainID, auxSignerData.SignDoc.ChainId)
+ require.Equal(t, msgAny, body.GetMessages()[0])
+ require.Equal(t, pkAny, auxSignerData.SignDoc.PublicKey)
+ require.Equal(t, tip, auxSignerData.SignDoc.Tip)
+ require.Equal(t, signMode, auxSignerData.Mode)
+ require.Equal(t, rawSig, auxSignerData.Sig)
+}
diff --git a/client/tx/legacy_test.go b/client/tx/legacy_test.go
index 65949a038fa1..8d5b451b7c09 100644
--- a/client/tx/legacy_test.go
+++ b/client/tx/legacy_test.go
@@ -13,6 +13,7 @@ import (
"github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
+ typestx "github.com/cosmos/cosmos-sdk/types/tx"
signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
@@ -29,15 +30,19 @@ var (
fee = types.NewCoins(types.NewInt64Coin("bam", 100))
_, pub1, addr1 = testdata.KeyTestPubAddr()
_, _, addr2 = testdata.KeyTestPubAddr()
+ rawSig = []byte("dummy")
sig = signing2.SignatureV2{
PubKey: pub1,
Data: &signing2.SingleSignatureData{
SignMode: signing2.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
- Signature: []byte("dummy"),
+ Signature: rawSig,
},
}
msg0 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 1)))
msg1 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 2)))
+
+ chainID = "test-chain"
+ tip = &typestx.Tip{Tipper: addr1.String(), Amount: testdata.NewTestFeeAmount()}
)
func buildTestTx(t *testing.T, builder client.TxBuilder) {
diff --git a/client/tx_config.go b/client/tx_config.go
index c9db30ff0d7d..cb82a06e6686 100644
--- a/client/tx_config.go
+++ b/client/tx_config.go
@@ -44,6 +44,8 @@ type (
SetGasLimit(limit uint64)
SetTip(tip *tx.Tip)
SetTimeoutHeight(height uint64)
+ SetFeePayer(feeGranter sdk.AccAddress)
SetFeeGranter(feeGranter sdk.AccAddress)
+ AddAuxSignerData(tx.AuxSignerData) error
}
)
diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md
index 4fb7c12d4524..af45ac474ab3 100644
--- a/docs/core/proto-docs.md
+++ b/docs/core/proto-docs.md
@@ -661,6 +661,7 @@
- [cosmos/tx/v1beta1/tx.proto](#cosmos/tx/v1beta1/tx.proto)
- [AuthInfo](#cosmos.tx.v1beta1.AuthInfo)
+ - [AuxSignerData](#cosmos.tx.v1beta1.AuxSignerData)
- [Fee](#cosmos.tx.v1beta1.Fee)
- [ModeInfo](#cosmos.tx.v1beta1.ModeInfo)
- [ModeInfo.Multi](#cosmos.tx.v1beta1.ModeInfo.Multi)
@@ -9475,6 +9476,29 @@ Since: cosmos-sdk 0.45 |
+
+
+### AuxSignerData
+AuxSignerData is the intermediary format that an auxiliary signer (e.g. a
+tipper) builds and sends to the fee payer (who will build and broadcast the
+actual tx). AuxSignerData is not a valid tx in itself, and will be rejected
+by the node if sent directly as-is.
+
+Since: cosmos-sdk 0.45
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| `address` | [string](#string) | | address is the bech32-encoded address of the auxiliary signer. If using AuxSignerData across different chains, the bech32 prefix of the target chain (where the final transaction is broadcasted) should be used. |
+| `sign_doc` | [SignDocDirectAux](#cosmos.tx.v1beta1.SignDocDirectAux) | | sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer signs. Note: we use the same sign doc even if we're signing with LEGACY_AMINO_JSON. |
+| `mode` | [cosmos.tx.signing.v1beta1.SignMode](#cosmos.tx.signing.v1beta1.SignMode) | | mode is the signing mode of the single signer |
+| `sig` | [bytes](#bytes) | | sig is the signature of the sign doc. |
+
+
+
+
+
+
### Fee
diff --git a/proto/cosmos/group/v1beta1/types.proto b/proto/cosmos/group/v1beta1/types.proto
index 7ef8ad43bd06..5c4a8f99dd47 100644
--- a/proto/cosmos/group/v1beta1/types.proto
+++ b/proto/cosmos/group/v1beta1/types.proto
@@ -9,7 +9,6 @@ import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "cosmos_proto/cosmos.proto";
import "google/protobuf/any.proto";
-import "cosmos_proto/cosmos.proto";
// Member represents a group member with an account address,
// non-zero weight and metadata.
diff --git a/proto/cosmos/tx/v1beta1/tx.proto b/proto/cosmos/tx/v1beta1/tx.proto
index a26cad5d54df..5f63b9364bdd 100644
--- a/proto/cosmos/tx/v1beta1/tx.proto
+++ b/proto/cosmos/tx/v1beta1/tx.proto
@@ -221,3 +221,24 @@ message Tip {
// tipper is the address of the account paying for the tip
string tipper = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}
+
+// AuxSignerData is the intermediary format that an auxiliary signer (e.g. a
+// tipper) builds and sends to the fee payer (who will build and broadcast the
+// actual tx). AuxSignerData is not a valid tx in itself, and will be rejected
+// by the node if sent directly as-is.
+//
+// Since: cosmos-sdk 0.45
+message AuxSignerData {
+ // address is the bech32-encoded address of the auxiliary signer. If using
+ // AuxSignerData across different chains, the bech32 prefix of the target
+ // chain (where the final transaction is broadcasted) should be used.
+ string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
+ // sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer
+ // signs. Note: we use the same sign doc even if we're signing with
+ // LEGACY_AMINO_JSON.
+ SignDocDirectAux sign_doc = 2;
+ // mode is the signing mode of the single signer
+ cosmos.tx.signing.v1beta1.SignMode mode = 3;
+ // sig is the signature of the sign doc.
+ bytes sig = 4;
+}
diff --git a/types/tx/aux.go b/types/tx/aux.go
new file mode 100644
index 000000000000..2c9a71262252
--- /dev/null
+++ b/types/tx/aux.go
@@ -0,0 +1,54 @@
+package tx
+
+import (
+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+)
+
+// ValidateBasic performs stateless validation of the sign doc.
+func (s *SignDocDirectAux) ValidateBasic() error {
+ if len(s.BodyBytes) == 0 {
+ return sdkerrors.ErrInvalidRequest.Wrap("body bytes cannot be empty")
+ }
+
+ if s.PublicKey == nil {
+ return sdkerrors.ErrInvalidPubKey.Wrap("public key cannot be empty")
+ }
+
+ if s.Tip != nil {
+ if s.Tip.Tipper == "" {
+ return sdkerrors.ErrInvalidRequest.Wrap("tipper cannot be empty")
+ }
+ }
+
+ return nil
+}
+
+// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method
+func (s *SignDocDirectAux) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+ return unpacker.UnpackAny(s.PublicKey, new(cryptotypes.PubKey))
+}
+
+// ValidateBasic performs stateless validation of the auxiliary tx.
+func (a *AuxSignerData) ValidateBasic() error {
+ if a.Address == "" {
+ return sdkerrors.ErrInvalidRequest.Wrapf("address cannot be empty")
+ }
+
+ if a.Mode != signing.SignMode_SIGN_MODE_DIRECT_AUX && a.Mode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON {
+ return sdkerrors.ErrInvalidRequest.Wrapf("AuxTxBuilder can only sign with %s or %s", signing.SignMode_SIGN_MODE_DIRECT_AUX, signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+ }
+
+ if len(a.Sig) == 0 {
+ return sdkerrors.ErrNoSignatures.Wrap("signature cannot be empty")
+ }
+
+ return a.GetSignDoc().ValidateBasic()
+}
+
+// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method
+func (a *AuxSignerData) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+ return a.GetSignDoc().UnpackInterfaces(unpacker)
+}
diff --git a/types/tx/aux_test.go b/types/tx/aux_test.go
new file mode 100644
index 000000000000..02b39f0f356e
--- /dev/null
+++ b/types/tx/aux_test.go
@@ -0,0 +1,83 @@
+package tx_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+ "github.com/cosmos/cosmos-sdk/testutil/testdata"
+ "github.com/cosmos/cosmos-sdk/types/tx"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+)
+
+func TestSignDocDirectAux(t *testing.T) {
+ bodyBz := []byte{42}
+ _, pk, addr := testdata.KeyTestPubAddr()
+ pkAny, err := codectypes.NewAnyWithValue(pk)
+ require.NoError(t, err)
+
+ testcases := []struct {
+ name string
+ sd tx.SignDocDirectAux
+ expErr bool
+ }{
+ {"empty bodyBz", tx.SignDocDirectAux{}, true},
+ {"empty pubkey", tx.SignDocDirectAux{BodyBytes: bodyBz}, true},
+ {"empty tipper", tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny, Tip: &tx.Tip{Amount: testdata.NewTestFeeAmount()}}, true},
+ {"happy case w/o tip", tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny}, false},
+ {"happy case w/ tip", tx.SignDocDirectAux{
+ BodyBytes: bodyBz,
+ PublicKey: pkAny,
+ Tip: &tx.Tip{Tipper: addr.String(), Amount: testdata.NewTestFeeAmount()},
+ }, false},
+ }
+
+ for _, tc := range testcases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ err := tc.sd.ValidateBasic()
+
+ if tc.expErr {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestAuxSignerData(t *testing.T) {
+ bodyBz := []byte{42}
+ _, pk, addr := testdata.KeyTestPubAddr()
+ pkAny, err := codectypes.NewAnyWithValue(pk)
+ require.NoError(t, err)
+ sig := []byte{42}
+ sd := &tx.SignDocDirectAux{BodyBytes: bodyBz, PublicKey: pkAny}
+
+ testcases := []struct {
+ name string
+ sd tx.AuxSignerData
+ expErr bool
+ }{
+ {"empty address", tx.AuxSignerData{}, true},
+ {"empty sign mode", tx.AuxSignerData{Address: addr.String()}, true},
+ {"SIGN_MODE_DIRECT", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode(signing.SignMode_SIGN_MODE_DIRECT)}, true},
+ {"no sig", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)}, true},
+ {"happy case WITH DIRECT_AUX", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode_SIGN_MODE_DIRECT_AUX, SignDoc: sd, Sig: sig}, false},
+ {"happy case WITH DIRECT_AUX", tx.AuxSignerData{Address: addr.String(), Mode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, SignDoc: sd, Sig: sig}, false},
+ }
+
+ for _, tc := range testcases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ err := tc.sd.ValidateBasic()
+
+ if tc.expErr {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/types/tx/tx.pb.go b/types/tx/tx.pb.go
index 6a7df6c004c3..f28f9fb6a227 100644
--- a/types/tx/tx.pb.go
+++ b/types/tx/tx.pb.go
@@ -907,6 +907,88 @@ func (m *Tip) GetTipper() string {
return ""
}
+// AuxSignerData is the intermediary format that an auxiliary signer (e.g. a
+// tipper) builds and sends to the fee payer (who will build and broadcast the
+// actual tx). AuxSignerData is not a valid tx in itself, and will be rejected
+// by the node if sent directly as-is.
+//
+// Since: cosmos-sdk 0.45
+type AuxSignerData struct {
+ // address is the bech32-encoded address of the auxiliary signer. If using
+ // AuxSignerData across different chains, the bech32 prefix of the target
+ // chain (where the final transaction is broadcasted) should be used.
+ Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+ // sign_doc is the SIGN_MOD_DIRECT_AUX sign doc that the auxiliary signer
+ // signs. Note: we use the same sign doc even if we're signing with
+ // LEGACY_AMINO_JSON.
+ SignDoc *SignDocDirectAux `protobuf:"bytes,2,opt,name=sign_doc,json=signDoc,proto3" json:"sign_doc,omitempty"`
+ // mode is the signing mode of the single signer
+ Mode signing.SignMode `protobuf:"varint,3,opt,name=mode,proto3,enum=cosmos.tx.signing.v1beta1.SignMode" json:"mode,omitempty"`
+ // sig is the signature of the sign doc.
+ Sig []byte `protobuf:"bytes,4,opt,name=sig,proto3" json:"sig,omitempty"`
+}
+
+func (m *AuxSignerData) Reset() { *m = AuxSignerData{} }
+func (m *AuxSignerData) String() string { return proto.CompactTextString(m) }
+func (*AuxSignerData) ProtoMessage() {}
+func (*AuxSignerData) Descriptor() ([]byte, []int) {
+ return fileDescriptor_96d1575ffde80842, []int{10}
+}
+func (m *AuxSignerData) XXX_Unmarshal(b []byte) error {
+ return m.Unmarshal(b)
+}
+func (m *AuxSignerData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ if deterministic {
+ return xxx_messageInfo_AuxSignerData.Marshal(b, m, deterministic)
+ } else {
+ b = b[:cap(b)]
+ n, err := m.MarshalToSizedBuffer(b)
+ if err != nil {
+ return nil, err
+ }
+ return b[:n], nil
+ }
+}
+func (m *AuxSignerData) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_AuxSignerData.Merge(m, src)
+}
+func (m *AuxSignerData) XXX_Size() int {
+ return m.Size()
+}
+func (m *AuxSignerData) XXX_DiscardUnknown() {
+ xxx_messageInfo_AuxSignerData.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AuxSignerData proto.InternalMessageInfo
+
+func (m *AuxSignerData) GetAddress() string {
+ if m != nil {
+ return m.Address
+ }
+ return ""
+}
+
+func (m *AuxSignerData) GetSignDoc() *SignDocDirectAux {
+ if m != nil {
+ return m.SignDoc
+ }
+ return nil
+}
+
+func (m *AuxSignerData) GetMode() signing.SignMode {
+ if m != nil {
+ return m.Mode
+ }
+ return signing.SignMode_SIGN_MODE_UNSPECIFIED
+}
+
+func (m *AuxSignerData) GetSig() []byte {
+ if m != nil {
+ return m.Sig
+ }
+ return nil
+}
+
func init() {
proto.RegisterType((*Tx)(nil), "cosmos.tx.v1beta1.Tx")
proto.RegisterType((*TxRaw)(nil), "cosmos.tx.v1beta1.TxRaw")
@@ -920,72 +1002,77 @@ func init() {
proto.RegisterType((*ModeInfo_Multi)(nil), "cosmos.tx.v1beta1.ModeInfo.Multi")
proto.RegisterType((*Fee)(nil), "cosmos.tx.v1beta1.Fee")
proto.RegisterType((*Tip)(nil), "cosmos.tx.v1beta1.Tip")
+ proto.RegisterType((*AuxSignerData)(nil), "cosmos.tx.v1beta1.AuxSignerData")
}
func init() { proto.RegisterFile("cosmos/tx/v1beta1/tx.proto", fileDescriptor_96d1575ffde80842) }
var fileDescriptor_96d1575ffde80842 = []byte{
- // 957 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0x1b, 0x45,
- 0x14, 0xf7, 0x66, 0x6d, 0xc7, 0x7e, 0x6d, 0xfa, 0x67, 0x14, 0x21, 0xc7, 0x51, 0xdd, 0x60, 0x54,
- 0xf0, 0x25, 0xbb, 0x69, 0x7a, 0x68, 0x41, 0x48, 0x60, 0xb7, 0x54, 0xa9, 0x4a, 0x41, 0x9a, 0xe4,
- 0xd4, 0xcb, 0x6a, 0x76, 0x3d, 0x59, 0x8f, 0xea, 0x9d, 0x59, 0x76, 0x66, 0xc1, 0xfb, 0x21, 0x90,
- 0x2a, 0x24, 0xc4, 0x95, 0x33, 0x67, 0x3e, 0x44, 0x8f, 0x15, 0x27, 0x4e, 0x50, 0x25, 0x47, 0x24,
- 0xbe, 0x02, 0x68, 0x66, 0x67, 0x37, 0x69, 0x09, 0x76, 0x25, 0x38, 0xed, 0xcc, 0x9b, 0xdf, 0xfb,
- 0xcd, 0xef, 0xfd, 0xd9, 0x37, 0xd0, 0x8f, 0x84, 0x4c, 0x84, 0xf4, 0xd5, 0xc2, 0xff, 0xfa, 0x76,
- 0x48, 0x15, 0xb9, 0xed, 0xab, 0x85, 0x97, 0x66, 0x42, 0x09, 0x74, 0xbd, 0x3c, 0xf3, 0xd4, 0xc2,
- 0xb3, 0x67, 0xfd, 0xcd, 0x58, 0xc4, 0xc2, 0x9c, 0xfa, 0x7a, 0x55, 0x02, 0xfb, 0xbb, 0x96, 0x24,
- 0xca, 0x8a, 0x54, 0x09, 0x3f, 0xc9, 0xe7, 0x8a, 0x49, 0x16, 0xd7, 0x8c, 0x95, 0xc1, 0xc2, 0x07,
- 0x16, 0x1e, 0x12, 0x49, 0x6b, 0x4c, 0x24, 0x18, 0xb7, 0xe7, 0x1f, 0x9c, 0x69, 0x92, 0x2c, 0xe6,
- 0x8c, 0x9f, 0x31, 0xd9, 0xbd, 0x05, 0x6e, 0xc5, 0x42, 0xc4, 0x73, 0xea, 0x9b, 0x5d, 0x98, 0x1f,
- 0xfb, 0x84, 0x17, 0xd5, 0x51, 0xc9, 0x11, 0x94, 0x5a, 0x6d, 0x20, 0x66, 0x33, 0xfc, 0xd6, 0x81,
- 0xb5, 0xa3, 0x05, 0xda, 0x85, 0x66, 0x28, 0xa6, 0x45, 0xcf, 0xd9, 0x71, 0x46, 0x97, 0xf6, 0xb7,
- 0xbc, 0x7f, 0x04, 0xeb, 0x1d, 0x2d, 0x26, 0x62, 0x5a, 0x60, 0x03, 0x43, 0xf7, 0xa0, 0x4b, 0x72,
- 0x35, 0x0b, 0x18, 0x3f, 0x16, 0xbd, 0x35, 0xe3, 0xb3, 0x7d, 0x81, 0xcf, 0x38, 0x57, 0xb3, 0x47,
- 0xfc, 0x58, 0xe0, 0x0e, 0xb1, 0x2b, 0x34, 0x00, 0xd0, 0xb2, 0x89, 0xca, 0x33, 0x2a, 0x7b, 0xee,
- 0x8e, 0x3b, 0xba, 0x8c, 0xcf, 0x59, 0x86, 0x1c, 0x5a, 0x47, 0x0b, 0x4c, 0xbe, 0x41, 0x37, 0x00,
- 0xf4, 0x55, 0x41, 0x58, 0x28, 0x2a, 0x8d, 0xae, 0xcb, 0xb8, 0xab, 0x2d, 0x13, 0x6d, 0x40, 0xef,
- 0xc3, 0xd5, 0x5a, 0x81, 0xc5, 0xac, 0x19, 0xcc, 0x46, 0x75, 0x55, 0x89, 0x5b, 0x75, 0xdf, 0x77,
- 0x0e, 0xac, 0x1f, 0xb2, 0x98, 0x3f, 0x10, 0xd1, 0xff, 0x75, 0xe5, 0x16, 0x74, 0xa2, 0x19, 0x61,
- 0x3c, 0x60, 0xd3, 0x9e, 0xbb, 0xe3, 0x8c, 0xba, 0x78, 0xdd, 0xec, 0x1f, 0x4d, 0xd1, 0x2d, 0xb8,
- 0x42, 0xa2, 0x48, 0xe4, 0x5c, 0x05, 0x3c, 0x4f, 0x42, 0x9a, 0xf5, 0x9a, 0x3b, 0xce, 0xa8, 0x89,
- 0x37, 0xac, 0xf5, 0x0b, 0x63, 0x1c, 0xfe, 0xe9, 0xc0, 0x35, 0x2b, 0xea, 0x01, 0xcb, 0x68, 0xa4,
- 0xc6, 0xf9, 0x62, 0x95, 0xba, 0x3b, 0x00, 0x69, 0x1e, 0xce, 0x59, 0x14, 0x3c, 0xa3, 0x85, 0xad,
- 0xc9, 0xa6, 0x57, 0xf6, 0x84, 0x57, 0xf5, 0x84, 0x37, 0xe6, 0x05, 0xee, 0x96, 0xb8, 0xc7, 0xb4,
- 0xf8, 0xef, 0x52, 0x51, 0x1f, 0x3a, 0x92, 0x7e, 0x95, 0x53, 0x1e, 0xd1, 0x5e, 0xcb, 0x00, 0xea,
- 0x3d, 0x1a, 0x81, 0xab, 0x58, 0xda, 0x6b, 0x1b, 0x2d, 0xef, 0x5c, 0xd4, 0x53, 0x2c, 0xc5, 0x1a,
- 0x32, 0xfc, 0x7e, 0x0d, 0xda, 0x65, 0x83, 0xa1, 0x3d, 0xe8, 0x24, 0x54, 0x4a, 0x12, 0x9b, 0x20,
- 0xdd, 0x7f, 0x8d, 0xa2, 0x46, 0x21, 0x04, 0xcd, 0x84, 0x26, 0x65, 0x1f, 0x76, 0xb1, 0x59, 0x6b,
- 0xf5, 0x8a, 0x25, 0x54, 0xe4, 0x2a, 0x98, 0x51, 0x16, 0xcf, 0x94, 0x09, 0xaf, 0x89, 0x37, 0xac,
- 0xf5, 0xc0, 0x18, 0xd1, 0x04, 0xae, 0xd3, 0x85, 0xa2, 0x5c, 0x32, 0xc1, 0x03, 0x91, 0x2a, 0x26,
- 0xb8, 0xec, 0xfd, 0xb5, 0xbe, 0xe4, 0xda, 0x6b, 0x35, 0xfe, 0xcb, 0x12, 0x8e, 0x9e, 0xc2, 0x80,
- 0x0b, 0x1e, 0x44, 0x19, 0x53, 0x2c, 0x22, 0xf3, 0xe0, 0x02, 0xc2, 0xab, 0x4b, 0x08, 0xb7, 0xb9,
- 0xe0, 0xf7, 0xad, 0xef, 0x67, 0x6f, 0x70, 0x0f, 0x7f, 0x74, 0xa0, 0x53, 0xfd, 0x44, 0xe8, 0x53,
- 0xb8, 0xac, 0x1b, 0x97, 0x66, 0xa6, 0x03, 0xab, 0xec, 0xdc, 0xb8, 0x20, 0xaf, 0x87, 0x06, 0x66,
- 0xfe, 0xbc, 0x4b, 0xb2, 0x5e, 0x4b, 0x5d, 0x90, 0x63, 0x4a, 0x6d, 0x73, 0x5c, 0x54, 0x90, 0x87,
- 0x94, 0x62, 0x0d, 0xa9, 0x4a, 0xe7, 0xae, 0x2e, 0xdd, 0x0f, 0x0e, 0xc0, 0xd9, 0x7d, 0x6f, 0xb4,
- 0xa1, 0xf3, 0x76, 0x6d, 0x78, 0x0f, 0xba, 0x89, 0x98, 0xd2, 0x55, 0xe3, 0xe4, 0x89, 0x98, 0xd2,
- 0x72, 0x9c, 0x24, 0x76, 0xf5, 0x5a, 0xfb, 0xb9, 0xaf, 0xb7, 0xdf, 0xf0, 0xd5, 0x1a, 0x74, 0x2a,
- 0x17, 0xf4, 0x31, 0xb4, 0x25, 0xe3, 0xf1, 0x9c, 0x5a, 0x4d, 0xc3, 0x25, 0xfc, 0xde, 0xa1, 0x41,
- 0x1e, 0x34, 0xb0, 0xf5, 0x41, 0x1f, 0x42, 0xcb, 0x8c, 0x6d, 0x2b, 0xee, 0xdd, 0x65, 0xce, 0x4f,
- 0x34, 0xf0, 0xa0, 0x81, 0x4b, 0x8f, 0xfe, 0x18, 0xda, 0x25, 0x1d, 0xba, 0x0b, 0x4d, 0xad, 0xdb,
- 0x08, 0xb8, 0xb2, 0xff, 0xde, 0x39, 0x8e, 0x6a, 0x90, 0x9f, 0xaf, 0x9f, 0xe6, 0xc3, 0xc6, 0xa1,
- 0xff, 0xdc, 0x81, 0x96, 0x61, 0x45, 0x8f, 0xa1, 0x13, 0x32, 0x45, 0xb2, 0x8c, 0x54, 0xb9, 0xf5,
- 0x2b, 0x9a, 0xf2, 0xb9, 0xf1, 0xea, 0xd7, 0xa5, 0xe2, 0xba, 0x2f, 0x92, 0x94, 0x44, 0x6a, 0xc2,
- 0xd4, 0x58, 0xbb, 0xe1, 0x9a, 0x00, 0x7d, 0x04, 0x50, 0x67, 0x5d, 0x8f, 0x32, 0x77, 0x55, 0xda,
- 0xbb, 0x55, 0xda, 0xe5, 0xa4, 0x05, 0xae, 0xcc, 0x93, 0xe1, 0x1f, 0x0e, 0xb8, 0x0f, 0x29, 0x45,
- 0x11, 0xb4, 0x49, 0xa2, 0xa7, 0x82, 0x6d, 0xca, 0xfa, 0x01, 0xd1, 0xaf, 0xda, 0x39, 0x29, 0x8c,
- 0x4f, 0xf6, 0x5e, 0xfc, 0x76, 0xb3, 0xf1, 0xd3, 0xef, 0x37, 0x47, 0x31, 0x53, 0xb3, 0x3c, 0xf4,
- 0x22, 0x91, 0xf8, 0xd5, 0x8b, 0x69, 0x3e, 0xbb, 0x72, 0xfa, 0xcc, 0x57, 0x45, 0x4a, 0xa5, 0x71,
- 0x90, 0xd8, 0x52, 0xa3, 0x6d, 0xe8, 0xc6, 0x44, 0x06, 0x73, 0x96, 0x30, 0x65, 0x0a, 0xd1, 0xc4,
- 0x9d, 0x98, 0xc8, 0xcf, 0xf5, 0x1e, 0x79, 0xd0, 0x4a, 0x49, 0x41, 0xb3, 0x72, 0x8c, 0x4d, 0x7a,
- 0xbf, 0xfc, 0xbc, 0xbb, 0x69, 0x35, 0x8c, 0xa7, 0xd3, 0x8c, 0x4a, 0x79, 0xa8, 0x32, 0xc6, 0x63,
- 0x5c, 0xc2, 0xd0, 0x3e, 0xac, 0xc7, 0x19, 0xe1, 0xca, 0xce, 0xb5, 0x65, 0x1e, 0x15, 0x70, 0x98,
- 0x82, 0x7b, 0xc4, 0x52, 0x74, 0xf7, 0xed, 0x83, 0x6d, 0xea, 0x60, 0xeb, 0x00, 0xf6, 0xa0, 0xad,
- 0x58, 0x9a, 0xd2, 0xac, 0x1c, 0x55, 0x4b, 0xae, 0xb4, 0xb8, 0xc9, 0x27, 0x2f, 0x4e, 0x06, 0xce,
- 0xcb, 0x93, 0x81, 0xf3, 0xea, 0x64, 0xe0, 0x3c, 0x3f, 0x1d, 0x34, 0x5e, 0x9e, 0x0e, 0x1a, 0xbf,
- 0x9e, 0x0e, 0x1a, 0x4f, 0x6f, 0xad, 0x4e, 0x9f, 0xaf, 0x16, 0x61, 0xdb, 0xfc, 0x72, 0x77, 0xfe,
- 0x0e, 0x00, 0x00, 0xff, 0xff, 0x82, 0x38, 0x16, 0x01, 0xda, 0x08, 0x00, 0x00,
+ // 1018 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x41, 0x6f, 0x1b, 0x45,
+ 0x14, 0xf6, 0x66, 0x6d, 0xc7, 0x7e, 0x4d, 0xda, 0x74, 0x14, 0x21, 0xc7, 0x51, 0xdd, 0xe0, 0xaa,
+ 0xe0, 0x4b, 0x76, 0xd3, 0xf4, 0xd0, 0x82, 0x10, 0x60, 0x37, 0x54, 0xa9, 0x4a, 0x41, 0x9a, 0xe4,
+ 0xd4, 0xcb, 0x6a, 0xbc, 0x9e, 0xac, 0x47, 0xf5, 0xce, 0x2c, 0x3b, 0xb3, 0x60, 0xff, 0x08, 0xa4,
+ 0x0a, 0x09, 0x71, 0xe5, 0xcc, 0x99, 0x1f, 0xd1, 0x13, 0xaa, 0x38, 0x71, 0x82, 0x2a, 0x39, 0x22,
+ 0xf1, 0x17, 0x40, 0x33, 0x3b, 0xbb, 0x49, 0x8b, 0x6b, 0x17, 0xd1, 0xd3, 0xce, 0xbc, 0xf9, 0xde,
+ 0x37, 0xdf, 0x9b, 0xf9, 0xf6, 0x0d, 0xb4, 0x43, 0x21, 0x63, 0x21, 0x7d, 0x35, 0xf5, 0xbf, 0xbe,
+ 0x35, 0xa4, 0x8a, 0xdc, 0xf2, 0xd5, 0xd4, 0x4b, 0x52, 0xa1, 0x04, 0xba, 0x9a, 0xaf, 0x79, 0x6a,
+ 0xea, 0xd9, 0xb5, 0xf6, 0x66, 0x24, 0x22, 0x61, 0x56, 0x7d, 0x3d, 0xca, 0x81, 0xed, 0x5d, 0x4b,
+ 0x12, 0xa6, 0xb3, 0x44, 0x09, 0x3f, 0xce, 0x26, 0x8a, 0x49, 0x16, 0x95, 0x8c, 0x45, 0xc0, 0xc2,
+ 0x3b, 0x16, 0x3e, 0x24, 0x92, 0x96, 0x98, 0x50, 0x30, 0x6e, 0xd7, 0xdf, 0x3f, 0xd7, 0x24, 0x59,
+ 0xc4, 0x19, 0x3f, 0x67, 0xb2, 0x73, 0x0b, 0xdc, 0x8a, 0x84, 0x88, 0x26, 0xd4, 0x37, 0xb3, 0x61,
+ 0x76, 0xe2, 0x13, 0x3e, 0x2b, 0x96, 0x72, 0x8e, 0x20, 0xd7, 0x6a, 0x0b, 0x31, 0x93, 0xee, 0xb7,
+ 0x0e, 0xac, 0x1c, 0x4f, 0xd1, 0x2e, 0x54, 0x87, 0x62, 0x34, 0x6b, 0x39, 0x3b, 0x4e, 0xef, 0xd2,
+ 0xfe, 0x96, 0xf7, 0xaf, 0x62, 0xbd, 0xe3, 0xe9, 0x40, 0x8c, 0x66, 0xd8, 0xc0, 0xd0, 0x5d, 0x68,
+ 0x92, 0x4c, 0x8d, 0x03, 0xc6, 0x4f, 0x44, 0x6b, 0xc5, 0xe4, 0x6c, 0xcf, 0xc9, 0xe9, 0x67, 0x6a,
+ 0xfc, 0x80, 0x9f, 0x08, 0xdc, 0x20, 0x76, 0x84, 0x3a, 0x00, 0x5a, 0x36, 0x51, 0x59, 0x4a, 0x65,
+ 0xcb, 0xdd, 0x71, 0x7b, 0x6b, 0xf8, 0x42, 0xa4, 0xcb, 0xa1, 0x76, 0x3c, 0xc5, 0xe4, 0x1b, 0x74,
+ 0x0d, 0x40, 0x6f, 0x15, 0x0c, 0x67, 0x8a, 0x4a, 0xa3, 0x6b, 0x0d, 0x37, 0x75, 0x64, 0xa0, 0x03,
+ 0xe8, 0x3d, 0xb8, 0x52, 0x2a, 0xb0, 0x98, 0x15, 0x83, 0x59, 0x2f, 0xb6, 0xca, 0x71, 0xcb, 0xf6,
+ 0xfb, 0xce, 0x81, 0xd5, 0x23, 0x16, 0xf1, 0x03, 0x11, 0xbe, 0xad, 0x2d, 0xb7, 0xa0, 0x11, 0x8e,
+ 0x09, 0xe3, 0x01, 0x1b, 0xb5, 0xdc, 0x1d, 0xa7, 0xd7, 0xc4, 0xab, 0x66, 0xfe, 0x60, 0x84, 0x6e,
+ 0xc2, 0x65, 0x12, 0x86, 0x22, 0xe3, 0x2a, 0xe0, 0x59, 0x3c, 0xa4, 0x69, 0xab, 0xba, 0xe3, 0xf4,
+ 0xaa, 0x78, 0xdd, 0x46, 0xbf, 0x30, 0xc1, 0xee, 0x5f, 0x0e, 0x6c, 0x58, 0x51, 0x07, 0x2c, 0xa5,
+ 0xa1, 0xea, 0x67, 0xd3, 0x65, 0xea, 0x6e, 0x03, 0x24, 0xd9, 0x70, 0xc2, 0xc2, 0xe0, 0x09, 0x9d,
+ 0xd9, 0x3b, 0xd9, 0xf4, 0x72, 0x4f, 0x78, 0x85, 0x27, 0xbc, 0x3e, 0x9f, 0xe1, 0x66, 0x8e, 0x7b,
+ 0x48, 0x67, 0xff, 0x5f, 0x2a, 0x6a, 0x43, 0x43, 0xd2, 0xaf, 0x32, 0xca, 0x43, 0xda, 0xaa, 0x19,
+ 0x40, 0x39, 0x47, 0x3d, 0x70, 0x15, 0x4b, 0x5a, 0x75, 0xa3, 0xe5, 0x9d, 0x79, 0x9e, 0x62, 0x09,
+ 0xd6, 0x90, 0xee, 0xf7, 0x2b, 0x50, 0xcf, 0x0d, 0x86, 0xf6, 0xa0, 0x11, 0x53, 0x29, 0x49, 0x64,
+ 0x8a, 0x74, 0x5f, 0x5b, 0x45, 0x89, 0x42, 0x08, 0xaa, 0x31, 0x8d, 0x73, 0x1f, 0x36, 0xb1, 0x19,
+ 0x6b, 0xf5, 0x8a, 0xc5, 0x54, 0x64, 0x2a, 0x18, 0x53, 0x16, 0x8d, 0x95, 0x29, 0xaf, 0x8a, 0xd7,
+ 0x6d, 0xf4, 0xd0, 0x04, 0xd1, 0x00, 0xae, 0xd2, 0xa9, 0xa2, 0x5c, 0x32, 0xc1, 0x03, 0x91, 0x28,
+ 0x26, 0xb8, 0x6c, 0xfd, 0xbd, 0xba, 0x60, 0xdb, 0x8d, 0x12, 0xff, 0x65, 0x0e, 0x47, 0x8f, 0xa1,
+ 0xc3, 0x05, 0x0f, 0xc2, 0x94, 0x29, 0x16, 0x92, 0x49, 0x30, 0x87, 0xf0, 0xca, 0x02, 0xc2, 0x6d,
+ 0x2e, 0xf8, 0x3d, 0x9b, 0xfb, 0xd9, 0x2b, 0xdc, 0xdd, 0x1f, 0x1d, 0x68, 0x14, 0x3f, 0x11, 0xfa,
+ 0x14, 0xd6, 0xb4, 0x71, 0x69, 0x6a, 0x1c, 0x58, 0x9c, 0xce, 0xb5, 0x39, 0xe7, 0x7a, 0x64, 0x60,
+ 0xe6, 0xcf, 0xbb, 0x24, 0xcb, 0xb1, 0xd4, 0x17, 0x72, 0x42, 0xa9, 0x35, 0xc7, 0xbc, 0x0b, 0xb9,
+ 0x4f, 0x29, 0xd6, 0x90, 0xe2, 0xea, 0xdc, 0xe5, 0x57, 0xf7, 0x83, 0x03, 0x70, 0xbe, 0xdf, 0x2b,
+ 0x36, 0x74, 0xde, 0xcc, 0x86, 0x77, 0xa1, 0x19, 0x8b, 0x11, 0x5d, 0xd6, 0x4e, 0x1e, 0x89, 0x11,
+ 0xcd, 0xdb, 0x49, 0x6c, 0x47, 0x2f, 0xd9, 0xcf, 0x7d, 0xd9, 0x7e, 0xdd, 0x17, 0x2b, 0xd0, 0x28,
+ 0x52, 0xd0, 0x47, 0x50, 0x97, 0x8c, 0x47, 0x13, 0x6a, 0x35, 0x75, 0x17, 0xf0, 0x7b, 0x47, 0x06,
+ 0x79, 0x58, 0xc1, 0x36, 0x07, 0x7d, 0x00, 0x35, 0xd3, 0xb6, 0xad, 0xb8, 0x77, 0x17, 0x25, 0x3f,
+ 0xd2, 0xc0, 0xc3, 0x0a, 0xce, 0x33, 0xda, 0x7d, 0xa8, 0xe7, 0x74, 0xe8, 0x0e, 0x54, 0xb5, 0x6e,
+ 0x23, 0xe0, 0xf2, 0xfe, 0x8d, 0x0b, 0x1c, 0x45, 0x23, 0xbf, 0x78, 0x7f, 0x9a, 0x0f, 0x9b, 0x84,
+ 0xf6, 0x53, 0x07, 0x6a, 0x86, 0x15, 0x3d, 0x84, 0xc6, 0x90, 0x29, 0x92, 0xa6, 0xa4, 0x38, 0x5b,
+ 0xbf, 0xa0, 0xc9, 0x9f, 0x1b, 0xaf, 0x7c, 0x5d, 0x0a, 0xae, 0x7b, 0x22, 0x4e, 0x48, 0xa8, 0x06,
+ 0x4c, 0xf5, 0x75, 0x1a, 0x2e, 0x09, 0xd0, 0x87, 0x00, 0xe5, 0xa9, 0xeb, 0x56, 0xe6, 0x2e, 0x3b,
+ 0xf6, 0x66, 0x71, 0xec, 0x72, 0x50, 0x03, 0x57, 0x66, 0x71, 0xf7, 0x4f, 0x07, 0xdc, 0xfb, 0x94,
+ 0xa2, 0x10, 0xea, 0x24, 0xd6, 0x5d, 0xc1, 0x9a, 0xb2, 0x7c, 0x40, 0xf4, 0xab, 0x76, 0x41, 0x0a,
+ 0xe3, 0x83, 0xbd, 0x67, 0xbf, 0x5f, 0xaf, 0xfc, 0xf4, 0xc7, 0xf5, 0x5e, 0xc4, 0xd4, 0x38, 0x1b,
+ 0x7a, 0xa1, 0x88, 0xfd, 0xe2, 0xc5, 0x34, 0x9f, 0x5d, 0x39, 0x7a, 0xe2, 0xab, 0x59, 0x42, 0xa5,
+ 0x49, 0x90, 0xd8, 0x52, 0xa3, 0x6d, 0x68, 0x46, 0x44, 0x06, 0x13, 0x16, 0x33, 0x65, 0x2e, 0xa2,
+ 0x8a, 0x1b, 0x11, 0x91, 0x9f, 0xeb, 0x39, 0xf2, 0xa0, 0x96, 0x90, 0x19, 0x4d, 0xf3, 0x36, 0x36,
+ 0x68, 0xfd, 0xfa, 0xf3, 0xee, 0xa6, 0xd5, 0xd0, 0x1f, 0x8d, 0x52, 0x2a, 0xe5, 0x91, 0x4a, 0x19,
+ 0x8f, 0x70, 0x0e, 0x43, 0xfb, 0xb0, 0x1a, 0xa5, 0x84, 0x2b, 0xdb, 0xd7, 0x16, 0x65, 0x14, 0xc0,
+ 0x6e, 0x02, 0xee, 0x31, 0x4b, 0xd0, 0x9d, 0x37, 0x2f, 0xb6, 0xaa, 0x8b, 0x2d, 0x0b, 0xd8, 0x83,
+ 0xba, 0x62, 0x49, 0x42, 0xd3, 0xbc, 0x55, 0x2d, 0xd8, 0xd2, 0xe2, 0xba, 0xbf, 0x38, 0xb0, 0xde,
+ 0xcf, 0xa6, 0xf9, 0xff, 0x75, 0x40, 0x14, 0xd1, 0xba, 0x49, 0x0e, 0x35, 0x06, 0x58, 0xa8, 0xdb,
+ 0x02, 0xd1, 0xc7, 0xd0, 0xd0, 0x0e, 0x0b, 0x46, 0x22, 0xb4, 0x06, 0xbe, 0xf1, 0x9a, 0xa6, 0x71,
+ 0xf1, 0xc1, 0xc1, 0xab, 0xd2, 0xbe, 0x8b, 0x85, 0x71, 0xdd, 0xff, 0x68, 0x5c, 0xb4, 0x01, 0xae,
+ 0x64, 0x91, 0x39, 0xe0, 0x35, 0xac, 0x87, 0x83, 0x4f, 0x9e, 0x9d, 0x76, 0x9c, 0xe7, 0xa7, 0x1d,
+ 0xe7, 0xc5, 0x69, 0xc7, 0x79, 0x7a, 0xd6, 0xa9, 0x3c, 0x3f, 0xeb, 0x54, 0x7e, 0x3b, 0xeb, 0x54,
+ 0x1e, 0xdf, 0x5c, 0xee, 0x07, 0x5f, 0x4d, 0x87, 0x75, 0xd3, 0x43, 0x6e, 0xff, 0x13, 0x00, 0x00,
+ 0xff, 0xff, 0x83, 0x68, 0x7c, 0x6c, 0xab, 0x09, 0x00, 0x00,
}
func (m *Tx) Marshal() (dAtA []byte, err error) {
@@ -1655,6 +1742,60 @@ func (m *Tip) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
+func (m *AuxSignerData) Marshal() (dAtA []byte, err error) {
+ size := m.Size()
+ dAtA = make([]byte, size)
+ n, err := m.MarshalToSizedBuffer(dAtA[:size])
+ if err != nil {
+ return nil, err
+ }
+ return dAtA[:n], nil
+}
+
+func (m *AuxSignerData) MarshalTo(dAtA []byte) (int, error) {
+ size := m.Size()
+ return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *AuxSignerData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+ i := len(dAtA)
+ _ = i
+ var l int
+ _ = l
+ if len(m.Sig) > 0 {
+ i -= len(m.Sig)
+ copy(dAtA[i:], m.Sig)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Sig)))
+ i--
+ dAtA[i] = 0x22
+ }
+ if m.Mode != 0 {
+ i = encodeVarintTx(dAtA, i, uint64(m.Mode))
+ i--
+ dAtA[i] = 0x18
+ }
+ if m.SignDoc != nil {
+ {
+ size, err := m.SignDoc.MarshalToSizedBuffer(dAtA[:i])
+ if err != nil {
+ return 0, err
+ }
+ i -= size
+ i = encodeVarintTx(dAtA, i, uint64(size))
+ }
+ i--
+ dAtA[i] = 0x12
+ }
+ if len(m.Address) > 0 {
+ i -= len(m.Address)
+ copy(dAtA[i:], m.Address)
+ i = encodeVarintTx(dAtA, i, uint64(len(m.Address)))
+ i--
+ dAtA[i] = 0xa
+ }
+ return len(dAtA) - i, nil
+}
+
func encodeVarintTx(dAtA []byte, offset int, v uint64) int {
offset -= sovTx(v)
base := offset
@@ -1956,6 +2097,30 @@ func (m *Tip) Size() (n int) {
return n
}
+func (m *AuxSignerData) Size() (n int) {
+ if m == nil {
+ return 0
+ }
+ var l int
+ _ = l
+ l = len(m.Address)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ if m.SignDoc != nil {
+ l = m.SignDoc.Size()
+ n += 1 + l + sovTx(uint64(l))
+ }
+ if m.Mode != 0 {
+ n += 1 + sovTx(uint64(m.Mode))
+ }
+ l = len(m.Sig)
+ if l > 0 {
+ n += 1 + l + sovTx(uint64(l))
+ }
+ return n
+}
+
func sovTx(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
@@ -3753,6 +3918,177 @@ func (m *Tip) Unmarshal(dAtA []byte) error {
}
return nil
}
+func (m *AuxSignerData) Unmarshal(dAtA []byte) error {
+ l := len(dAtA)
+ iNdEx := 0
+ for iNdEx < l {
+ preIndex := iNdEx
+ var wire uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ wire |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ fieldNum := int32(wire >> 3)
+ wireType := int(wire & 0x7)
+ if wireType == 4 {
+ return fmt.Errorf("proto: AuxSignerData: wiretype end group for non-group")
+ }
+ if fieldNum <= 0 {
+ return fmt.Errorf("proto: AuxSignerData: illegal tag %d (wire type %d)", fieldNum, wire)
+ }
+ switch fieldNum {
+ case 1:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
+ }
+ var stringLen uint64
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ stringLen |= uint64(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ intStringLen := int(stringLen)
+ if intStringLen < 0 {
+ return ErrInvalidLengthTx
+ }
+ postIndex := iNdEx + intStringLen
+ if postIndex < 0 {
+ return ErrInvalidLengthTx
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Address = string(dAtA[iNdEx:postIndex])
+ iNdEx = postIndex
+ case 2:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field SignDoc", wireType)
+ }
+ var msglen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ msglen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if msglen < 0 {
+ return ErrInvalidLengthTx
+ }
+ postIndex := iNdEx + msglen
+ if postIndex < 0 {
+ return ErrInvalidLengthTx
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ if m.SignDoc == nil {
+ m.SignDoc = &SignDocDirectAux{}
+ }
+ if err := m.SignDoc.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+ return err
+ }
+ iNdEx = postIndex
+ case 3:
+ if wireType != 0 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType)
+ }
+ m.Mode = 0
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ m.Mode |= signing.SignMode(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ case 4:
+ if wireType != 2 {
+ return fmt.Errorf("proto: wrong wireType = %d for field Sig", wireType)
+ }
+ var byteLen int
+ for shift := uint(0); ; shift += 7 {
+ if shift >= 64 {
+ return ErrIntOverflowTx
+ }
+ if iNdEx >= l {
+ return io.ErrUnexpectedEOF
+ }
+ b := dAtA[iNdEx]
+ iNdEx++
+ byteLen |= int(b&0x7F) << shift
+ if b < 0x80 {
+ break
+ }
+ }
+ if byteLen < 0 {
+ return ErrInvalidLengthTx
+ }
+ postIndex := iNdEx + byteLen
+ if postIndex < 0 {
+ return ErrInvalidLengthTx
+ }
+ if postIndex > l {
+ return io.ErrUnexpectedEOF
+ }
+ m.Sig = append(m.Sig[:0], dAtA[iNdEx:postIndex]...)
+ if m.Sig == nil {
+ m.Sig = []byte{}
+ }
+ iNdEx = postIndex
+ default:
+ iNdEx = preIndex
+ skippy, err := skipTx(dAtA[iNdEx:])
+ if err != nil {
+ return err
+ }
+ if (skippy < 0) || (iNdEx+skippy) < 0 {
+ return ErrInvalidLengthTx
+ }
+ if (iNdEx + skippy) > l {
+ return io.ErrUnexpectedEOF
+ }
+ iNdEx += skippy
+ }
+ }
+
+ if iNdEx > l {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+}
func skipTx(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
diff --git a/x/auth/migrations/legacytx/stdtx_builder.go b/x/auth/migrations/legacytx/stdtx_builder.go
index af848d13eb8c..0c0454034947 100644
--- a/x/auth/migrations/legacytx/stdtx_builder.go
+++ b/x/auth/migrations/legacytx/stdtx_builder.go
@@ -74,6 +74,14 @@ func (s *StdTxBuilder) SetTimeoutHeight(height uint64) {
// SetFeeGranter does nothing for stdtx
func (s *StdTxBuilder) SetFeeGranter(_ sdk.AccAddress) {}
+// SetFeePayer does nothing for stdtx
+func (s *StdTxBuilder) SetFeePayer(_ sdk.AccAddress) {}
+
+// AddAuxSignerData returns an error for StdTxBuilder.
+func (s *StdTxBuilder) AddAuxSignerData(_ tx.AuxSignerData) error {
+ return sdkerrors.ErrLogic.Wrap("cannot use AuxSignerData with StdTxBuilder")
+}
+
// StdTxConfig is a context.TxConfig for StdTx
type StdTxConfig struct {
Cdc *codec.LegacyAmino
diff --git a/x/auth/tx/aux_test.go b/x/auth/tx/aux_test.go
new file mode 100644
index 000000000000..72774712b3ce
--- /dev/null
+++ b/x/auth/tx/aux_test.go
@@ -0,0 +1,147 @@
+package tx_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ clienttx "github.com/cosmos/cosmos-sdk/client/tx"
+ "github.com/cosmos/cosmos-sdk/simapp"
+ "github.com/cosmos/cosmos-sdk/testutil/testdata"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ txtypes "github.com/cosmos/cosmos-sdk/types/tx"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+ authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
+)
+
+// TestBuilderWithAux creates a tx with 2 aux signers:
+// - 1st one is tipper,
+// - 2nd one is just an aux signer.
+// Then it tests integrating the 2 AuxSignerData into a
+// client.TxBuilder created by the fee payer.
+func TestBuilderWithAux(t *testing.T) {
+ encCfg := simapp.MakeTestEncodingConfig()
+ testdata.RegisterInterfaces(encCfg.InterfaceRegistry)
+
+ // The final TX has 3 signers, in this order.
+ tipperPriv, tipperPk, tipperAddr := testdata.KeyTestPubAddr()
+ aux2Priv, aux2Pk, aux2Addr := testdata.KeyTestPubAddr()
+ feepayerPriv, feepayerPk, feepayerAddr := testdata.KeyTestPubAddr()
+
+ msg := testdata.NewTestMsg(tipperAddr, aux2Addr)
+ memo := "test-memo"
+ tip := &txtypes.Tip{Tipper: tipperAddr.String(), Amount: sdk.NewCoins(sdk.NewCoin("tip-denom", sdk.NewIntFromUint64(123)))}
+ chainID := "test-chain"
+ gas := testdata.NewTestGasLimit()
+ fee := testdata.NewTestFeeAmount()
+
+ // Create an AuxTxBuilder for tipper (1st signer)
+ tipperBuilder := clienttx.NewAuxTxBuilder()
+ tipperBuilder.SetAddress(tipperAddr.String())
+ tipperBuilder.SetAccountNumber(1)
+ tipperBuilder.SetSequence(2)
+ tipperBuilder.SetTimeoutHeight(3)
+ tipperBuilder.SetMemo(memo)
+ tipperBuilder.SetChainID(chainID)
+ tipperBuilder.SetMsgs(msg)
+ tipperBuilder.SetPubKey(tipperPk)
+ tipperBuilder.SetTip(tip)
+ err := tipperBuilder.SetSignMode(signing.SignMode_SIGN_MODE_DIRECT_AUX)
+ require.NoError(t, err)
+ signBz, err := tipperBuilder.GetSignBytes()
+ require.NoError(t, err)
+ tipperSig, err := tipperPriv.Sign(signBz)
+ require.NoError(t, err)
+ tipperBuilder.SetSignature(tipperSig)
+ tipperSignerData, err := tipperBuilder.GetAuxSignerData()
+ require.NoError(t, err)
+
+ // Create an AuxTxBuilder for aux2 (2nd signer)
+ aux2Builder := clienttx.NewAuxTxBuilder()
+ aux2Builder.SetAddress(aux2Addr.String())
+ aux2Builder.SetAccountNumber(11)
+ aux2Builder.SetSequence(12)
+ aux2Builder.SetTimeoutHeight(3)
+ aux2Builder.SetMemo(memo)
+ aux2Builder.SetChainID(chainID)
+ aux2Builder.SetMsgs(msg)
+ aux2Builder.SetPubKey(aux2Pk)
+ aux2Builder.SetTip(tip)
+ err = aux2Builder.SetSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
+ require.NoError(t, err)
+ signBz, err = aux2Builder.GetSignBytes()
+ require.NoError(t, err)
+ aux2Sig, err := aux2Priv.Sign(signBz)
+ require.NoError(t, err)
+ aux2Builder.SetSignature(aux2Sig)
+ aux2SignerData, err := aux2Builder.GetAuxSignerData()
+ require.NoError(t, err)
+
+ // Fee payer (3rd and last signer) creates a TxBuilder.
+ w := encCfg.TxConfig.NewTxBuilder()
+ // Note: we're testing calling AddAuxSignerData in the wrong order.
+ err = w.AddAuxSignerData(aux2SignerData)
+ require.NoError(t, err)
+ err = w.AddAuxSignerData(tipperSignerData)
+ require.NoError(t, err)
+ w.SetFeePayer(feepayerAddr)
+ w.SetFeeAmount(fee)
+ w.SetGasLimit(gas)
+ sigs, err := w.(authsigning.SigVerifiableTx).GetSignaturesV2()
+ require.NoError(t, err)
+ tipperSigV2 := sigs[0]
+ aux2SigV2 := sigs[1]
+ // Set all signer infos.
+ w.SetSignatures(tipperSigV2, aux2SigV2, signing.SignatureV2{
+ PubKey: feepayerPk,
+ Sequence: 15,
+ })
+ signBz, err = encCfg.TxConfig.SignModeHandler().GetSignBytes(
+ signing.SignMode_SIGN_MODE_DIRECT,
+ authsigning.SignerData{Address: feepayerAddr.String(), ChainID: chainID, AccountNumber: 11, Sequence: 15, SignerIndex: 1},
+ w.GetTx(),
+ )
+ require.NoError(t, err)
+ feepayerSig, err := feepayerPriv.Sign(signBz)
+ require.NoError(t, err)
+ // Set all signatures.
+ w.SetSignatures(tipperSigV2, aux2SigV2, signing.SignatureV2{
+ PubKey: feepayerPk,
+ Data: &signing.SingleSignatureData{
+ SignMode: signing.SignMode_SIGN_MODE_DIRECT,
+ Signature: feepayerSig,
+ },
+ Sequence: 22,
+ })
+
+ // Make sure tx is correct.
+ txBz, err := encCfg.TxConfig.TxEncoder()(w.GetTx())
+ require.NoError(t, err)
+ tx, err := encCfg.TxConfig.TxDecoder()(txBz)
+ require.NoError(t, err)
+ require.Equal(t, tx.(sdk.FeeTx).FeePayer(), feepayerAddr)
+ require.Equal(t, tx.(sdk.FeeTx).GetFee(), fee)
+ require.Equal(t, tx.(sdk.FeeTx).GetGas(), gas)
+ require.Equal(t, tip, tx.(txtypes.TipTx).GetTip())
+ require.Equal(t, msg, tx.GetMsgs()[0])
+ require.Equal(t, memo, tx.(sdk.TxWithMemo).GetMemo())
+ require.Equal(t, uint64(3), tx.(sdk.TxWithTimeoutHeight).GetTimeoutHeight())
+ sigs, err = tx.(authsigning.Tx).GetSignaturesV2()
+ require.NoError(t, err)
+ require.Len(t, sigs, 3)
+ require.Equal(t, signing.SignatureV2{
+ PubKey: tipperPk,
+ Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_DIRECT_AUX, Signature: tipperSig},
+ Sequence: 2,
+ }, sigs[0])
+ require.Equal(t, signing.SignatureV2{
+ PubKey: aux2Pk,
+ Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, Signature: aux2Sig},
+ Sequence: 12,
+ }, sigs[1])
+ require.Equal(t, signing.SignatureV2{
+ PubKey: feepayerPk,
+ Data: &signing.SingleSignatureData{SignMode: signing.SignMode_SIGN_MODE_DIRECT, Signature: feepayerSig},
+ Sequence: 22,
+ }, sigs[2])
+}
diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go
index d3b83d5018bb..91e3546c74d7 100644
--- a/x/auth/tx/builder.go
+++ b/x/auth/tx/builder.go
@@ -4,6 +4,7 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/cosmos/cosmos-sdk/client"
+ "github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -17,6 +18,8 @@ import (
// wrapper is a wrapper around the tx.Tx proto.Message which retain the raw
// body and auth_info bytes.
type wrapper struct {
+ cdc codec.Codec
+
tx *tx.Tx
// bodyBz represents the protobuf encoding of TxBody. This should be encoding
@@ -46,8 +49,9 @@ type ExtensionOptionsTxBuilder interface {
SetNonCriticalExtensionOptions(...*codectypes.Any)
}
-func newBuilder() *wrapper {
+func newBuilder(cdc codec.Codec) *wrapper {
return &wrapper{
+ cdc: cdc,
tx: &tx.Tx{
Body: &tx.TxBody{},
AuthInfo: &tx.AuthInfo{
@@ -315,10 +319,28 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) {
w.authInfoBz = nil
}
+func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) {
+ if w.tx.AuthInfo.SignerInfos == nil {
+ w.tx.AuthInfo.SignerInfos = make([]*tx.SignerInfo, len(w.GetSigners()))
+ }
+
+ w.tx.AuthInfo.SignerInfos[index] = info
+ // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo
+ w.authInfoBz = nil
+}
+
func (w *wrapper) setSignatures(sigs [][]byte) {
w.tx.Signatures = sigs
}
+func (w *wrapper) setSignatureAtIndex(index int, sig []byte) {
+ if w.tx.Signatures == nil {
+ w.tx.Signatures = make([][]byte, len(w.GetSigners()))
+ }
+
+ w.tx.Signatures[index] = sig
+}
+
func (w *wrapper) GetTx() authsigning.Tx {
return w
}
@@ -357,3 +379,49 @@ func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) {
w.tx.Body.NonCriticalExtensionOptions = extOpts
w.bodyBz = nil
}
+
+func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error {
+ err := data.ValidateBasic()
+ if err != nil {
+ return err
+ }
+
+ w.bodyBz = data.SignDoc.BodyBytes
+
+ var body tx.TxBody
+ err = w.cdc.Unmarshal(w.bodyBz, &body)
+ if err != nil {
+ return err
+ }
+
+ w.SetMemo(body.Memo)
+ w.SetTimeoutHeight(body.TimeoutHeight)
+ w.SetExtensionOptions(body.ExtensionOptions...)
+ w.SetNonCriticalExtensionOptions(body.NonCriticalExtensionOptions...)
+ msgs := make([]sdk.Msg, len(body.Messages))
+ for i, msgAny := range body.Messages {
+ msgs[i] = msgAny.GetCachedValue().(sdk.Msg)
+ }
+ w.SetMsgs(msgs...)
+ w.SetTip(data.GetSignDoc().GetTip())
+
+ // Get the aux signer's index in GetSigners.
+ signerIndex := -1
+ for i, signer := range w.GetSigners() {
+ if signer.String() == data.Address {
+ signerIndex = i
+ }
+ }
+ if signerIndex < 0 {
+ return sdkerrors.ErrLogic.Wrapf("address %s is not a signer", data.Address)
+ }
+
+ w.setSignerInfoAtIndex(signerIndex, &tx.SignerInfo{
+ PublicKey: data.SignDoc.PublicKey,
+ ModeInfo: &tx.ModeInfo{Sum: &tx.ModeInfo_Single_{Single: &tx.ModeInfo_Single{Mode: data.Mode}}},
+ Sequence: data.SignDoc.Sequence,
+ })
+ w.setSignatureAtIndex(signerIndex, data.Sig)
+
+ return nil
+}
diff --git a/x/auth/tx/builder_test.go b/x/auth/tx/builder_test.go
index e45a67116378..f7122dd39218 100644
--- a/x/auth/tx/builder_test.go
+++ b/x/auth/tx/builder_test.go
@@ -19,7 +19,7 @@ func TestTxBuilder(t *testing.T) {
_, pubkey, addr := testdata.KeyTestPubAddr()
marshaler := codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
- txBuilder := newBuilder()
+ txBuilder := newBuilder(nil)
memo := "sometestmemo"
msgs := []sdk.Msg{testdata.NewTestMsg(addr)}
@@ -136,7 +136,7 @@ func TestBuilderValidateBasic(t *testing.T) {
// require to fail validation upon invalid fee
badFeeAmount := testdata.NewTestFeeAmount()
badFeeAmount[0].Amount = sdk.NewInt(-5)
- txBuilder := newBuilder()
+ txBuilder := newBuilder(nil)
var sig1, sig2 signing.SignatureV2
sig1 = signing.SignatureV2{
@@ -277,7 +277,7 @@ func TestBuilderFeePayer(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
// setup basic tx
- txBuilder := newBuilder()
+ txBuilder := newBuilder(nil)
err := txBuilder.SetMsgs(msgs...)
require.NoError(t, err)
txBuilder.SetGasLimit(200000)
@@ -301,7 +301,7 @@ func TestBuilderFeeGranter(t *testing.T) {
feeAmount := testdata.NewTestFeeAmount()
msgs := []sdk.Msg{msg1}
- txBuilder := newBuilder()
+ txBuilder := newBuilder(nil)
err := txBuilder.SetMsgs(msgs...)
require.NoError(t, err)
txBuilder.SetGasLimit(200000)
diff --git a/x/auth/tx/config.go b/x/auth/tx/config.go
index 8402423dbf77..f850014b657d 100644
--- a/x/auth/tx/config.go
+++ b/x/auth/tx/config.go
@@ -35,7 +35,7 @@ func NewTxConfig(protoCodec codec.ProtoCodecMarshaler, enabledSignModes []signin
}
func (g config) NewTxBuilder() client.TxBuilder {
- return newBuilder()
+ return newBuilder(g.protoCodec)
}
// WrapTxBuilder returns a builder from provided transaction
diff --git a/x/auth/tx/encode_decode_test.go b/x/auth/tx/encode_decode_test.go
index d4d5ad7554d0..5991f6653450 100644
--- a/x/auth/tx/encode_decode_test.go
+++ b/x/auth/tx/encode_decode_test.go
@@ -24,7 +24,7 @@ func TestDefaultTxDecoderError(t *testing.T) {
encoder := DefaultTxEncoder()
decoder := DefaultTxDecoder(cdc)
- builder := newBuilder()
+ builder := newBuilder(nil)
err := builder.SetMsgs(testdata.NewTestMsg())
require.NoError(t, err)
diff --git a/x/auth/tx/legacy_amino_json_test.go b/x/auth/tx/legacy_amino_json_test.go
index 9734e7704eea..7bb4770fa6bd 100644
--- a/x/auth/tx/legacy_amino_json_test.go
+++ b/x/auth/tx/legacy_amino_json_test.go
@@ -86,7 +86,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) {
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
- bldr := newBuilder()
+ bldr := newBuilder(nil)
buildTx(t, bldr)
tx := bldr.GetTx()
tc.malleate(bldr)
@@ -104,7 +104,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) {
})
}
- bldr := newBuilder()
+ bldr := newBuilder(nil)
buildTx(t, bldr)
tx := bldr.GetTx()
signingData := signing.SignerData{
@@ -120,7 +120,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) {
require.Error(t, err)
// expect error with extension options
- bldr = newBuilder()
+ bldr = newBuilder(nil)
buildTx(t, bldr)
any, err := cdctypes.NewAnyWithValue(testdata.NewTestMsg())
require.NoError(t, err)
@@ -130,7 +130,7 @@ func TestLegacyAminoJSONHandler_GetSignBytes(t *testing.T) {
require.Error(t, err)
// expect error with non-critical extension options
- bldr = newBuilder()
+ bldr = newBuilder(nil)
buildTx(t, bldr)
bldr.tx.Body.NonCriticalExtensionOptions = []*cdctypes.Any{any}
tx = bldr.GetTx()