feat(x/tx): implement SIGN_MODE_DIRECT_AUX handler (#15380)

Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
This commit is contained in:
Matt Kocubinski 2023-03-16 05:29:41 -05:00 committed by GitHub
parent c254d0c608
commit 4a35885fb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 257 additions and 1 deletions

View File

@ -0,0 +1,111 @@
package direct_aux
import (
"context"
"fmt"
"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing"
)
// SignModeHandler is the SIGN_MODE_DIRECT_AUX implementation of signing.SignModeHandler.
type SignModeHandler struct {
signersContext *signing.GetSignersContext
fileResolver protodesc.Resolver
typeResolver protoregistry.MessageTypeResolver
}
// SignModeHandlerOptions are the options for the SignModeHandler.
type SignModeHandlerOptions struct {
// FileResolver is the protodesc.Resolver to use for resolving proto files when unpacking any messages.
FileResolver protodesc.Resolver
// TypeResolver is the protoregistry.MessageTypeResolver to use for resolving proto types when unpacking any messages.
TypeResolver protoregistry.MessageTypeResolver
// SignersContext is the signing.GetSignersContext to use for getting signers.
SignersContext *signing.GetSignersContext
}
// NewSignModeHandler returns a new SignModeHandler.
func NewSignModeHandler(options SignModeHandlerOptions) SignModeHandler {
h := SignModeHandler{}
if options.FileResolver == nil {
h.fileResolver = protoregistry.GlobalFiles
} else {
h.fileResolver = options.FileResolver
}
if options.TypeResolver == nil {
h.typeResolver = protoregistry.GlobalTypes
} else {
h.typeResolver = options.TypeResolver
}
if options.SignersContext == nil {
h.signersContext = signing.NewGetSignersContext(signing.GetSignersOptions{ProtoFiles: h.fileResolver})
} else {
h.signersContext = options.SignersContext
}
return h
}
var _ signing.SignModeHandler = SignModeHandler{}
// Mode implements signing.SignModeHandler.Mode.
func (h SignModeHandler) Mode() signingv1beta1.SignMode {
return signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX
}
// getFirstSigner returns the first signer from the first message in the tx. It replicates behavior in
// https://github.com/cosmos/cosmos-sdk/blob/4a6a1e3cb8de459891cb0495052589673d14ef51/x/auth/tx/builder.go#L142
func (h SignModeHandler) getFirstSigner(txData signing.TxData) (string, error) {
for _, anyMsg := range txData.Body.Messages {
msg, err := anyutil.Unpack(anyMsg, h.fileResolver, h.typeResolver)
if err != nil {
return "", err
}
signer, err := h.signersContext.GetSigners(msg)
if err != nil {
return "", err
}
return signer[0], nil
}
return "", fmt.Errorf("no signer found")
}
// GetSignBytes implements signing.SignModeHandler.GetSignBytes.
func (h SignModeHandler) GetSignBytes(
_ context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) {
feePayer := txData.AuthInfo.Fee.Payer
if feePayer == "" {
fp, err := h.getFirstSigner(txData)
if err != nil {
return nil, err
}
feePayer = fp
}
if feePayer == signerData.Address {
return nil, fmt.Errorf("fee payer %s cannot sign with %s: unauthorized",
feePayer, signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX)
}
signDocDirectAux := &txv1beta1.SignDocDirectAux{
BodyBytes: txData.BodyBytes,
PublicKey: signerData.PubKey,
ChainId: signerData.ChainId,
AccountNumber: signerData.AccountNumber,
Sequence: signerData.Sequence,
Tip: txData.AuthInfo.Tip,
}
return proto.Marshal(signDocDirectAux)
}

View File

@ -0,0 +1,144 @@
package direct_aux_test
import (
"context"
"fmt"
"testing"
"github.com/cosmos/cosmos-proto/anyutil"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/api/cosmos/crypto/secp256k1"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing"
"cosmossdk.io/x/tx/signing/direct_aux"
)
func TestDirectAuxHandler(t *testing.T) {
feePayerAddr := "feePayer"
chainID := "test-chain"
memo := "sometestmemo"
msg, err := anyutil.New(&bankv1beta1.MsgSend{})
require.NoError(t, err)
accNum, accSeq := uint64(1), uint64(2) // Arbitrary account number/sequence
pk := &secp256k1.PubKey{
Key: make([]byte, 256),
}
anyPk, err := anyutil.New(pk)
require.NoError(t, err)
signerInfo := []*txv1beta1.SignerInfo{
{
PublicKey: anyPk,
ModeInfo: &txv1beta1.ModeInfo{
Sum: &txv1beta1.ModeInfo_Single_{
Single: &txv1beta1.ModeInfo_Single{
Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX,
},
},
},
Sequence: accSeq,
},
}
fee := &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
GasLimit: 20000,
Payer: feePayerAddr,
}
tip := &txv1beta1.Tip{Amount: []*basev1beta1.Coin{{Denom: "tip-token", Amount: "10"}}}
txBody := &txv1beta1.TxBody{
Messages: []*anypb.Any{msg},
Memo: memo,
}
authInfo := &txv1beta1.AuthInfo{
Fee: fee,
Tip: tip,
SignerInfos: signerInfo,
}
signingData := signing.SignerData{
ChainId: chainID,
AccountNumber: accNum,
Sequence: accSeq,
Address: "",
PubKey: anyPk,
}
bodyBz, err := proto.Marshal(txBody)
require.NoError(t, err)
authInfoBz, err := proto.Marshal(authInfo)
require.NoError(t, err)
txData := signing.TxData{
Body: txBody,
AuthInfo: authInfo,
AuthInfoBytes: authInfoBz,
BodyBytes: bodyBz,
}
modeHandler := direct_aux.NewSignModeHandler(direct_aux.SignModeHandlerOptions{})
t.Log("verify fee payer cannot use SIGN_MODE_DIRECT_AUX")
feePayerSigningData := signing.SignerData{
ChainId: chainID,
AccountNumber: accNum,
Address: feePayerAddr,
PubKey: anyPk,
}
_, err = modeHandler.GetSignBytes(context.Background(), feePayerSigningData, txData)
require.EqualError(t, err, fmt.Sprintf("fee payer %s cannot sign with %s: unauthorized",
feePayerAddr, signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX))
t.Log("verifying fee payer fallback to GetSigners cannot use SIGN_MODE_DIRECT_AUX")
feeWithNoPayer := &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
GasLimit: 20000,
}
authInfoWithNoFeePayer := &txv1beta1.AuthInfo{
Fee: feeWithNoPayer,
Tip: tip,
SignerInfos: signerInfo,
}
authInfoWithNoFeePayerBz, err := proto.Marshal(authInfoWithNoFeePayer)
require.NoError(t, err)
txDataWithNoFeePayer := signing.TxData{
Body: txBody,
BodyBytes: bodyBz,
AuthInfo: authInfoWithNoFeePayer,
AuthInfoBytes: authInfoWithNoFeePayerBz,
}
_, err = modeHandler.GetSignBytes(context.Background(), signingData, txDataWithNoFeePayer)
require.EqualError(t, err, fmt.Sprintf("fee payer %s cannot sign with %s: unauthorized", "",
signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX))
t.Log("verify GetSignBytes with generating sign bytes by marshaling signDocDirectAux")
signBytes, err := modeHandler.GetSignBytes(context.Background(), signingData, txData)
require.NoError(t, err)
require.NotNil(t, signBytes)
signDocDirectAux := &txv1beta1.SignDocDirectAux{
BodyBytes: bodyBz,
PublicKey: anyPk,
ChainId: chainID,
AccountNumber: accNum,
Sequence: accSeq,
Tip: tip,
}
expectedSignBytes, err := proto.Marshal(signDocDirectAux)
require.NoError(t, err)
require.Equal(t, expectedSignBytes, signBytes)
t.Log("verify GetSignBytes with false txBody data")
signDocDirectAux.BodyBytes = []byte("dfafdasfds")
expectedSignBytes, err = proto.Marshal(signDocDirectAux)
require.NoError(t, err)
require.NotEqual(t, expectedSignBytes, signBytes)
}

View File

@ -3,11 +3,12 @@ package signing
import (
"fmt"
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
)
// GetSignersContext is a context for retrieving the list of signers from a