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

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

Merged
merged 13 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
111 changes: 111 additions & 0 deletions x/tx/signing/direct_aux/direct_aux.go
Original file line number Diff line number Diff line change
@@ -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)
}
144 changes: 144 additions & 0 deletions x/tx/signing/direct_aux/direct_aux_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 5 additions & 3 deletions x/tx/signing/get_signers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@ 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
// message where signers are specified by the cosmos.msg.v1.signer protobuf
// option.
type GetSignersContext struct {
protoFiles *protoregistry.Files
protoFiles protodesc.Resolver
getSignersFuncs map[protoreflect.FullName]getSignersFunc
}

// GetSignersOptions are options for creating GetSignersContext.
type GetSignersOptions struct {
// ProtoFiles are the protobuf files to use for resolving message descriptors.
// If it is nil, the global protobuf registry will be used.
ProtoFiles *protoregistry.Files
ProtoFiles protodesc.Resolver
}

// NewGetSignersContext creates a new GetSignersContext using the provided options.
Expand Down