Skip to content

Commit

Permalink
feat: eth secp256k1 (#203)
Browse files Browse the repository at this point in the history
* support eth-secp256k1

* expose sigverify

* remove unnecessary fork code

* add new pubkey from bytes

* remove unused codes
  • Loading branch information
beer-1 authored Jun 26, 2024
1 parent 9b6bbdb commit 8d7f6bb
Show file tree
Hide file tree
Showing 19 changed files with 1,487 additions and 15 deletions.
4 changes: 2 additions & 2 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {

sigGasConsumer := options.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = ante.DefaultSigVerificationGasConsumer
sigGasConsumer = DefaultSigVerificationGasConsumer
}

txFeeChecker := options.TxFeeChecker
Expand Down Expand Up @@ -85,7 +85,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCkeeper),
auctionante.NewAuctionDecorator(options.AuctionKeeper, options.TxEncoder, options.MevLane),
Expand Down
208 changes: 208 additions & 0 deletions app/ante/sigverify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package ante

import (
"encoding/hex"
"fmt"

"google.golang.org/protobuf/types/known/anypb"

errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
txsigning "cosmossdk.io/x/tx/signing"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/initia-labs/initia/crypto/ethsecp256k1"
)

var (
// simulation signature values used to estimate gas consumption
key = make([]byte, secp256k1.PubKeySize)
simSecp256k1Pubkey = &secp256k1.PubKey{Key: key}
)

func init() {
// This decodes a valid hex string into a sepc256k1Pubkey for use in transaction simulation
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(key, bz)
simSecp256k1Pubkey.Key = key
}

// SigVerificationDecorator verifies all signatures for a tx and return an error if any are invalid. Note,
// the SigVerificationDecorator will not check signatures on ReCheck.
//
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
// CONTRACT: Tx must implement SigVerifiableTx interface
type SigVerificationDecorator struct {
ak authante.AccountKeeper
signModeHandler *txsigning.HandlerMap
}

func NewSigVerificationDecorator(ak authante.AccountKeeper, signModeHandler *txsigning.HandlerMap) SigVerificationDecorator {
return SigVerificationDecorator{
ak: ak,
signModeHandler: signModeHandler,
}
}

func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
sigTx, ok := tx.(authsigning.Tx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}

// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
}

signers, err := sigTx.GetSigners()
if err != nil {
return ctx, err
}

// check that signer length and signature length are the same
if len(sigs) != len(signers) {
return ctx, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(sigs))
}

for i, sig := range sigs {
acc, err := authante.GetSignerAcc(ctx, svd.ak, signers[i])
if err != nil {
return ctx, err
}

// retrieve pubkey
pubKey := acc.GetPubKey()
if !simulate && pubKey == nil {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
}

// Check account sequence number.
if sig.Sequence != acc.GetSequence() {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrWrongSequence,
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
)
}

// retrieve signer data
genesis := ctx.BlockHeight() == 0
chainID := ctx.ChainID()
var accNum uint64
if !genesis {
accNum = acc.GetAccountNumber()
}

// no need to verify signatures on recheck tx
if !simulate && !ctx.IsReCheckTx() {
anyPk, _ := codectypes.NewAnyWithValue(pubKey)

signerData := txsigning.SignerData{
Address: acc.GetAddress().String(),
ChainID: chainID,
AccountNumber: accNum,
Sequence: acc.GetSequence(),
PubKey: &anypb.Any{
TypeUrl: anyPk.TypeUrl,
Value: anyPk.Value,
},
}
adaptableTx, ok := tx.(authsigning.V2AdaptableTx)
if !ok {
return ctx, fmt.Errorf("expected tx to implement V2AdaptableTx, got %T", tx)
}
txData := adaptableTx.GetSigningTxData()
err = verifySignature(ctx, pubKey, signerData, sig.Data, svd.signModeHandler, txData)
if err != nil {
var errMsg string
if authante.OnlyLegacyAminoSigners(sig.Data) {
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
// and therefore communicate sequence number as a potential cause of error.
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
} else {
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s): (%s)", accNum, chainID, err.Error())
}
return ctx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, errMsg)

}
}
}

return next(ctx, tx, simulate)
}

// defaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
// for signature verification based upon the public key type. The cost is fetched from the given params and is matched
// by the concrete type.
func DefaultSigVerificationGasConsumer(
meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params,
) error {
pubkey := sig.PubKey
switch pubkey := pubkey.(type) {
case *ed25519.PubKey:
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported")

case *secp256k1.PubKey, *ethsecp256k1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
return nil

case *secp256r1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
return nil

case multisig.PubKey:
multisignature, ok := sig.Data.(*signing.MultiSignatureData)
if !ok {
return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data)
}
err := consumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence)
if err != nil {
return err
}
return nil

default:
return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey)
}
}

// consumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature
func consumeMultisignatureVerificationGas(
meter storetypes.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey,
params types.Params, accSeq uint64,
) error {
size := sig.BitArray.Count()
sigIndex := 0

for i := 0; i < size; i++ {
if !sig.BitArray.GetIndex(i) {
continue
}
sigV2 := signing.SignatureV2{
PubKey: pubkey.GetPubKeys()[i],
Data: sig.Signatures[sigIndex],
Sequence: accSeq,
}
err := DefaultSigVerificationGasConsumer(meter, sigV2, params)
if err != nil {
return err
}
sigIndex++
}

return nil
}
79 changes: 79 additions & 0 deletions app/ante/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ante

import (
"context"
"fmt"

signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txsigning "cosmossdk.io/x/tx/signing"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

// internalSignModeToAPI converts a signing.SignMode to a protobuf SignMode.
func internalSignModeToAPI(mode signing.SignMode) (signingv1beta1.SignMode, error) {
switch mode {
case signing.SignMode_SIGN_MODE_DIRECT:
return signingv1beta1.SignMode_SIGN_MODE_DIRECT, nil
case signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON:
return signingv1beta1.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, nil
case signing.SignMode_SIGN_MODE_TEXTUAL:
return signingv1beta1.SignMode_SIGN_MODE_TEXTUAL, nil
case signing.SignMode_SIGN_MODE_DIRECT_AUX:
return signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX, nil
case signing.SignMode_SIGN_MODE_EIP_191:
return signingv1beta1.SignMode_SIGN_MODE_EIP_191, nil
default:
return signingv1beta1.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode %s", mode)
}
}

// verifySignature verifies a transaction signature contained in SignatureData abstracting over different signing
// modes. It differs from verifySignature in that it uses the new txsigning.TxData interface in x/tx.
func verifySignature(
ctx context.Context,
pubKey cryptotypes.PubKey,
signerData txsigning.SignerData,
signatureData signing.SignatureData,
handler *txsigning.HandlerMap,
txData txsigning.TxData,
) error {
switch data := signatureData.(type) {
case *signing.SingleSignatureData:
signMode, err := internalSignModeToAPI(data.SignMode)
if err != nil {
return err
}
signBytes, err := handler.GetSignBytes(ctx, signMode, signerData, txData)
if err != nil {
return err
}

if !pubKey.VerifySignature(signBytes, data.Signature) {
return fmt.Errorf("unable to verify single signer signature")
}

return nil

case *signing.MultiSignatureData:
multiPK, ok := pubKey.(multisig.PubKey)
if !ok {
return fmt.Errorf("expected %T, got %T", (multisig.PubKey)(nil), pubKey)
}
err := multiPK.VerifyMultisignature(func(mode signing.SignMode) ([]byte, error) {
signMode, err := internalSignModeToAPI(mode)
if err != nil {
return nil, err
}
return handler.GetSignBytes(ctx, signMode, signerData, txData)
}, data)
if err != nil {
return err
}
return nil
default:
return fmt.Errorf("unexpected SignatureData %T", signatureData)
}
}
3 changes: 3 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ import (
applanes "github.com/initia-labs/initia/app/lanes"
apporacle "github.com/initia-labs/initia/app/oracle"
"github.com/initia-labs/initia/app/params"
cryptocodec "github.com/initia-labs/initia/crypto/codec"
authzmodule "github.com/initia-labs/initia/x/authz/module"
"github.com/initia-labs/initia/x/bank"
bankkeeper "github.com/initia-labs/initia/x/bank/keeper"
Expand Down Expand Up @@ -325,6 +326,8 @@ func NewInitiaApp(
encodingConfig := params.MakeEncodingConfig()
std.RegisterLegacyAminoCodec(encodingConfig.Amino)
std.RegisterInterfaces(encodingConfig.InterfaceRegistry)
cryptocodec.RegisterLegacyAminoCodec(encodingConfig.Amino)
cryptocodec.RegisterInterfaces(encodingConfig.InterfaceRegistry)

appCodec := encodingConfig.Codec
legacyAmino := encodingConfig.Amino
Expand Down
19 changes: 18 additions & 1 deletion app/params/config.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
package params

import (
"cosmossdk.io/x/tx/signing/aminojson"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"

tx "github.com/initia-labs/initia/tx"
)

type config struct {
client.TxConfig
}

func NewClientTxConfig(protoCodec codec.ProtoCodecMarshaler) client.TxConfig {
return config{authtx.NewTxConfig(protoCodec, authtx.DefaultSignModes)}
signingOptions, err := authtx.NewDefaultSigningOptions()
if err != nil {
panic(err)
}

return config{
authtx.NewTxConfig(
protoCodec,
authtx.DefaultSignModes,
tx.NewSignModeEIP191Handler(aminojson.SignModeHandlerOptions{
FileResolver: signingOptions.FileResolver,
TypeResolver: signingOptions.TypeResolver,
}),
),
}
}

func (c config) TxDecoder() sdk.TxDecoder {
Expand Down
20 changes: 17 additions & 3 deletions app/params/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package params
import (
"cosmossdk.io/x/tx/signing"

"cosmossdk.io/x/tx/signing/aminojson"
"github.com/cosmos/cosmos-sdk/codec"
codecaddress "github.com/cosmos/cosmos-sdk/codec/address"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/tx"

authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/cosmos/gogoproto/proto"

"github.com/initia-labs/initia/tx"
)

// MakeEncodingConfig creates an EncodingConfig for an amino based test configuration.
Expand All @@ -23,7 +25,19 @@ func MakeEncodingConfig() EncodingConfig {
})
appCodec := codec.NewProtoCodec(interfaceRegistry)
legacyAmino := codec.NewLegacyAmino()
txConfig := tx.NewTxConfig(appCodec, tx.DefaultSignModes)
signingOptions, err := authtx.NewDefaultSigningOptions()
if err != nil {
panic(err)
}

txConfig := authtx.NewTxConfig(
appCodec,
authtx.DefaultSignModes,
tx.NewSignModeEIP191Handler(aminojson.SignModeHandlerOptions{
FileResolver: signingOptions.FileResolver,
TypeResolver: signingOptions.TypeResolver,
}),
)

return EncodingConfig{
InterfaceRegistry: interfaceRegistry,
Expand Down
Loading

0 comments on commit 8d7f6bb

Please sign in to comment.