Co-authored-by: Alex | Interchain Labs <alex@skip.money> Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io> Co-authored-by: Marko <marko@baricevic.me>
715 lines
23 KiB
Go
715 lines
23 KiB
Go
package ante
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
|
|
secp256k1dcrd "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
|
|
"cosmossdk.io/core/event"
|
|
"cosmossdk.io/core/gas"
|
|
"cosmossdk.io/core/transaction"
|
|
errorsmod "cosmossdk.io/errors"
|
|
txsigning "cosmossdk.io/x/tx/signing"
|
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
|
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
"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"
|
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
)
|
|
|
|
var (
|
|
// simulation signature values used to estimate gas consumption
|
|
key = make([]byte, secp256k1.PubKeySize)
|
|
simSecp256k1Pubkey = &secp256k1.PubKey{Key: key}
|
|
simSecp256k1Sig [64]byte
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// SignatureVerificationGasConsumer is the type of function that is used to both
|
|
// consume gas when verifying signatures and also to accept or reject different types of pubkeys
|
|
// This is where apps can define their own PubKey
|
|
type SignatureVerificationGasConsumer = func(meter gas.Meter, sig signing.SignatureV2, params types.Params) error
|
|
|
|
type AccountAbstractionKeeper interface {
|
|
IsAbstractedAccount(ctx context.Context, addr []byte) (bool, error)
|
|
AuthenticateAccount(ctx context.Context, signer []byte, bundler string, rawTx *tx.TxRaw, protoTx *tx.Tx, signIndex uint32) error
|
|
}
|
|
|
|
// SigVerificationDecorator verifies all signatures for a tx and returns an
|
|
// error if any are invalid.
|
|
// It will populate an account's public key if that is not present only if
|
|
// PubKey.Address() == Account.Address().
|
|
// Note, the SigVerificationDecorator will not check
|
|
// signatures on ReCheckTx. It will also increase the sequence number, and consume
|
|
// gas for signature verification.
|
|
//
|
|
// In cases where unordered or parallel transactions are desired, it is recommended
|
|
// to set unordered=true with a reasonable timeout_height value, in which case
|
|
// this nonce verification and increment will be skipped.
|
|
//
|
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
|
type SigVerificationDecorator struct {
|
|
ak AccountKeeper
|
|
aaKeeper AccountAbstractionKeeper
|
|
signModeHandler *txsigning.HandlerMap
|
|
sigGasConsumer SignatureVerificationGasConsumer
|
|
extraVerifyIsOnCurve func(pubKey cryptotypes.PubKey) (bool, error)
|
|
}
|
|
|
|
func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler *txsigning.HandlerMap, sigGasConsumer SignatureVerificationGasConsumer, aaKeeper AccountAbstractionKeeper) SigVerificationDecorator {
|
|
return NewSigVerificationDecoratorWithVerifyOnCurve(ak, signModeHandler, sigGasConsumer, aaKeeper, nil)
|
|
}
|
|
|
|
func NewSigVerificationDecoratorWithVerifyOnCurve(ak AccountKeeper, signModeHandler *txsigning.HandlerMap, sigGasConsumer SignatureVerificationGasConsumer, aaKeeper AccountAbstractionKeeper, verifyFn func(pubKey cryptotypes.PubKey) (bool, error)) SigVerificationDecorator {
|
|
return SigVerificationDecorator{
|
|
aaKeeper: aaKeeper,
|
|
ak: ak,
|
|
signModeHandler: signModeHandler,
|
|
sigGasConsumer: sigGasConsumer,
|
|
extraVerifyIsOnCurve: verifyFn,
|
|
}
|
|
}
|
|
|
|
// OnlyLegacyAminoSigners checks SignatureData to see if all
|
|
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
|
|
// then the corresponding SignatureV2 struct will not have account sequence
|
|
// explicitly set, and we should skip the explicit verification of sig.Sequence
|
|
// in the SigVerificationDecorator's AnteHandler function.
|
|
func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
|
|
switch v := sigData.(type) {
|
|
case *signing.SingleSignatureData:
|
|
return v.SignMode == apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
|
|
case *signing.MultiSignatureData:
|
|
for _, s := range v.Signatures {
|
|
if !OnlyLegacyAminoSigners(s) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (svd SigVerificationDecorator) VerifyIsOnCurve(pubKey cryptotypes.PubKey) error {
|
|
if svd.extraVerifyIsOnCurve != nil {
|
|
handled, err := svd.extraVerifyIsOnCurve(pubKey)
|
|
if handled {
|
|
return err
|
|
}
|
|
}
|
|
// when simulating pubKey.Key will always be nil
|
|
if pubKey.Bytes() == nil {
|
|
return nil
|
|
}
|
|
|
|
switch typedPubKey := pubKey.(type) {
|
|
case *ed25519.PubKey:
|
|
if !typedPubKey.IsOnCurve() {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "ed25519 key is not on curve")
|
|
}
|
|
case *secp256k1.PubKey:
|
|
pubKeyObject, err := secp256k1dcrd.ParsePubKey(typedPubKey.Bytes())
|
|
if err != nil {
|
|
if errors.Is(err, secp256k1dcrd.ErrPubKeyNotOnCurve) {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "secp256k1 key is not on curve")
|
|
}
|
|
return err
|
|
}
|
|
if !pubKeyObject.IsOnCurve() {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "secp256k1 key is not on curve")
|
|
}
|
|
|
|
case *secp256r1.PubKey:
|
|
pubKeyObject := typedPubKey.Key.PublicKey
|
|
if !pubKeyObject.IsOnCurve(pubKeyObject.X, pubKeyObject.Y) {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "secp256r1 key is not on curve")
|
|
}
|
|
|
|
case multisig.PubKey:
|
|
pubKeysObjects := typedPubKey.GetPubKeys()
|
|
ok := true
|
|
for _, pubKeyObject := range pubKeysObjects {
|
|
if err := svd.VerifyIsOnCurve(pubKeyObject); err != nil {
|
|
ok = false
|
|
break
|
|
}
|
|
}
|
|
if !ok {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "some keys are not on curve")
|
|
}
|
|
|
|
default:
|
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unsupported key type: %T", typedPubKey)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := svd.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
func (svd SigVerificationDecorator) ValidateTx(ctx context.Context, tx transaction.Tx) error {
|
|
sigTx, ok := tx.(authsigning.Tx)
|
|
if !ok {
|
|
return 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.
|
|
signatures, err := sigTx.GetSignaturesV2()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signers, err := sigTx.GetSigners()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check that signer length and signature length are the same
|
|
if len(signatures) != len(signers) {
|
|
return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(signatures))
|
|
}
|
|
|
|
pubKeys, err := sigTx.GetPubKeys()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// NOTE: the tx_wrapper implementation returns nil, in case the pubkey is not populated.
|
|
// so we can always expect the pubkey of the signer to be at the same index as the signer
|
|
// itself. If this does not work, it's a failure in the implementation of the interface.
|
|
// we're erroring, but most likely we should be panicking.
|
|
if len(pubKeys) != len(signers) {
|
|
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "invalid number of pubkeys; expected %d, got %d", len(signers), len(pubKeys))
|
|
}
|
|
|
|
for i := range signers {
|
|
err = svd.authenticate(ctx, sigTx, signers[i], signatures[i], pubKeys[i], i)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
eventMgr := svd.ak.GetEnvironment().EventService.EventManager(ctx)
|
|
events := [][]event.Attribute{}
|
|
for i, sig := range signatures {
|
|
signerStr, err := svd.ak.AddressCodec().BytesToString(signers[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
events = append(events, []event.Attribute{event.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStr, sig.Sequence))})
|
|
|
|
sigBzs, err := signatureDataToBz(sig.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, sigBz := range sigBzs {
|
|
events = append(events, []event.Attribute{event.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz))})
|
|
}
|
|
}
|
|
|
|
for _, v := range events {
|
|
if err := eventMgr.EmitKV(sdk.EventTypeTx, v...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// authenticate the authentication of the TX for a specific tx signer.
|
|
func (svd SigVerificationDecorator) authenticate(ctx context.Context, tx authsigning.Tx, signer []byte, sig signing.SignatureV2, txPubKey cryptotypes.PubKey, signerIndex int) error {
|
|
// first we check if it's an AA
|
|
if svd.aaKeeper != nil {
|
|
isAa, err := svd.aaKeeper.IsAbstractedAccount(ctx, signer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isAa {
|
|
return svd.authenticateAbstractedAccount(ctx, tx, signer, signerIndex)
|
|
}
|
|
}
|
|
|
|
// not an AA, proceed with standard auth flow.
|
|
|
|
// newlyCreated is a flag that indicates if the account was newly created.
|
|
// This is only the case when the user is sending their first tx.
|
|
newlyCreated := false
|
|
acc := GetSignerAcc(ctx, svd.ak, signer)
|
|
if acc == nil {
|
|
// If the account is nil, we assume this is the account's first tx. In this case, the account needs to be
|
|
// created, but the sign doc should use account number 0. This is because the account number is
|
|
// not known until the account is created when the tx was signed, the account number was unknown
|
|
// and 0 was set.
|
|
acc = svd.ak.NewAccountWithAddress(ctx, txPubKey.Address().Bytes())
|
|
newlyCreated = true
|
|
}
|
|
|
|
// the account is without a pubkey, let's attempt to check if in the
|
|
// tx we were correctly provided a valid pubkey.
|
|
if acc.GetPubKey() == nil {
|
|
err := svd.setPubKey(ctx, acc, txPubKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := svd.consumeSignatureGas(ctx, acc.GetPubKey(), sig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svd.verifySig(ctx, tx, acc, sig, newlyCreated)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svd.increaseSequence(tx, acc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// update account changes in state.
|
|
svd.ak.SetAccount(ctx, acc)
|
|
return nil
|
|
}
|
|
|
|
// consumeSignatureGas will consume gas according to the pub-key being verified.
|
|
func (svd SigVerificationDecorator) consumeSignatureGas(
|
|
ctx context.Context,
|
|
pubKey cryptotypes.PubKey,
|
|
signature signing.SignatureV2,
|
|
) error {
|
|
if svd.ak.GetEnvironment().TransactionService.ExecMode(ctx) == transaction.ExecModeSimulate && pubKey == nil {
|
|
pubKey = simSecp256k1Pubkey
|
|
}
|
|
|
|
// make a SignatureV2 with PubKey filled in from above
|
|
signature = signing.SignatureV2{
|
|
PubKey: pubKey,
|
|
Data: signature.Data,
|
|
Sequence: signature.Sequence,
|
|
}
|
|
|
|
return svd.sigGasConsumer(svd.ak.GetEnvironment().GasService.GasMeter(ctx), signature, svd.ak.GetParams(ctx))
|
|
}
|
|
|
|
// verifySig will verify the signature of the provided signer account.
|
|
func (svd SigVerificationDecorator) verifySig(ctx context.Context, tx sdk.Tx, acc sdk.AccountI, sig signing.SignatureV2, newlyCreated bool) error {
|
|
execMode := svd.ak.GetEnvironment().TransactionService.ExecMode(ctx)
|
|
unorderedTx, ok := tx.(sdk.TxWithUnordered)
|
|
isUnordered := ok && unorderedTx.GetUnordered()
|
|
|
|
// only check sequence if the tx is not unordered
|
|
if !isUnordered {
|
|
if execMode == transaction.ExecModeCheck {
|
|
if sig.Sequence < acc.GetSequence() {
|
|
return errorsmod.Wrapf(
|
|
sdkerrors.ErrWrongSequence,
|
|
"account sequence mismatch: expected higher than or equal to %d, got %d", acc.GetSequence(), sig.Sequence,
|
|
)
|
|
}
|
|
} else if sig.Sequence != acc.GetSequence() {
|
|
return errorsmod.Wrapf(
|
|
sdkerrors.ErrWrongSequence,
|
|
"account sequence mismatch: expected %d, got %d", acc.GetSequence(), sig.Sequence,
|
|
)
|
|
}
|
|
}
|
|
|
|
// we're in simulation mode, or in ReCheckTx, or context is not
|
|
// on sig verify tx, then we do not need to verify the signatures
|
|
// in the tx.
|
|
if execMode == transaction.ExecModeSimulate ||
|
|
isRecheckTx(ctx, svd.ak.GetEnvironment().TransactionService) ||
|
|
!isSigverifyTx(ctx) {
|
|
return nil
|
|
}
|
|
|
|
// retrieve pubkey
|
|
pubKey := acc.GetPubKey()
|
|
if pubKey == nil {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
|
|
}
|
|
|
|
// retrieve signer data
|
|
hinfo := svd.ak.GetEnvironment().HeaderService.HeaderInfo(ctx)
|
|
genesis := hinfo.Height == 0
|
|
chainID := hinfo.ChainID
|
|
var accNum uint64
|
|
// if we are not in genesis use the account number from the account
|
|
if !genesis {
|
|
accNum = acc.GetAccountNumber()
|
|
}
|
|
|
|
// if the account number is 0 and the account is signing, the sign doc will not have an account number
|
|
if acc.GetSequence() == 0 && newlyCreated {
|
|
// If the account sequence is 0, and we're in genesis, then we're
|
|
// dealing with an account that has been generated but never used.
|
|
// in this case, we should not verify signatures.
|
|
accNum = 0
|
|
}
|
|
|
|
anyPk, _ := codectypes.NewAnyWithValue(pubKey)
|
|
|
|
signerData := txsigning.SignerData{
|
|
Address: acc.GetAddress().String(),
|
|
ChainID: chainID,
|
|
AccountNumber: accNum,
|
|
Sequence: sig.Sequence,
|
|
PubKey: &anypb.Any{
|
|
TypeUrl: anyPk.TypeUrl,
|
|
Value: anyPk.Value,
|
|
},
|
|
}
|
|
adaptableTx, ok := tx.(authsigning.V2AdaptableTx)
|
|
if !ok {
|
|
return fmt.Errorf("expected tx to implement V2AdaptableTx, got %T", tx)
|
|
}
|
|
txData := adaptableTx.GetSigningTxData()
|
|
err := authsigning.VerifySignature(ctx, pubKey, signerData, sig.Data, svd.signModeHandler, txData)
|
|
if err != nil {
|
|
var errMsg string
|
|
if 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): (%s)", accNum, acc.GetSequence(), chainID, err.Error())
|
|
} else {
|
|
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s): (%s)", accNum, chainID, err.Error())
|
|
}
|
|
return errorsmod.Wrap(sdkerrors.ErrUnauthorized, errMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setPubKey will attempt to set the pubkey for the account given the list of available public keys.
|
|
// This must be called only in case the account has not a pubkey set yet.
|
|
func (svd SigVerificationDecorator) setPubKey(ctx context.Context, acc sdk.AccountI, txPubKey cryptotypes.PubKey) error {
|
|
// if we're not in sig verify then we can just skip.
|
|
if !isSigverifyTx(ctx) {
|
|
return nil
|
|
}
|
|
|
|
// if the pubkey is nil then we don't have any pubkey to set
|
|
// for this account, which also means we cannot do signature
|
|
// verification.
|
|
if txPubKey == nil {
|
|
// if we're not in simulation mode, and we do not have a valid pubkey
|
|
// for this signer, then we simply error.
|
|
if svd.ak.GetEnvironment().TransactionService.ExecMode(ctx) != transaction.ExecModeSimulate {
|
|
return fmt.Errorf("the account %s is without a pubkey and did not provide a pubkey in the tx to set it", acc.GetAddress().String())
|
|
}
|
|
// if we're in simulation mode, then we can populate the pubkey with the
|
|
// sim one and simply return.
|
|
txPubKey = simSecp256k1Pubkey
|
|
return acc.SetPubKey(txPubKey)
|
|
}
|
|
|
|
// this code path is taken when a user has received tokens but not submitted their first transaction
|
|
// if the address does not match the pubkey, then we error.
|
|
// TODO: in the future the relationship between address and pubkey should be more flexible.
|
|
if !acc.GetAddress().Equals(sdk.AccAddress(txPubKey.Address().Bytes())) {
|
|
return sdkerrors.ErrInvalidPubKey.Wrapf("the account %s cannot be claimed by public key with address %x", acc.GetAddress(), txPubKey.Address())
|
|
}
|
|
|
|
err := svd.VerifyIsOnCurve(txPubKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// we set the pubkey in the account, without setting it in state.
|
|
// this will be done by the increaseSequenceAndUpdateAccount method.
|
|
return acc.SetPubKey(txPubKey)
|
|
}
|
|
|
|
// increaseSequence will increase the provided account interface sequence, unless
|
|
// the tx is unordered.
|
|
func (svd SigVerificationDecorator) increaseSequence(tx authsigning.Tx, acc sdk.AccountI) error {
|
|
// Bypass incrementing sequence for transactions with unordered set to true.
|
|
// The actual parameters of the un-ordered tx will be checked in a separate
|
|
// decorator.
|
|
unorderedTx, ok := tx.(sdk.TxWithUnordered)
|
|
if ok && unorderedTx.GetUnordered() {
|
|
return nil
|
|
}
|
|
|
|
return acc.SetSequence(acc.GetSequence() + 1)
|
|
}
|
|
|
|
// authenticateAbstractedAccount computes an AA authentication instruction and invokes the auth flow on the AA.
|
|
func (svd SigVerificationDecorator) authenticateAbstractedAccount(ctx context.Context, authTx authsigning.Tx, signer []byte, index int) error {
|
|
// the bundler is the AA itself.
|
|
selfBundler, err := svd.ak.AddressCodec().BytesToString(signer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
infoTx := authTx.(interface {
|
|
AsTxRaw() (*tx.TxRaw, error)
|
|
AsTx() (*tx.Tx, error)
|
|
})
|
|
|
|
txRaw, err := infoTx.AsTxRaw()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get raw tx: %w", err)
|
|
}
|
|
|
|
protoTx, err := infoTx.AsTx()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get proto tx: %w", err)
|
|
}
|
|
|
|
return svd.aaKeeper.AuthenticateAccount(ctx, signer, selfBundler, txRaw, protoTx, uint32(index))
|
|
}
|
|
|
|
// ValidateSigCountDecorator takes in Params and returns errors if there are too many signatures in the tx for the given params
|
|
// otherwise it calls next AnteHandler
|
|
// Use this decorator to set parameterized limit on number of signatures in tx
|
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
|
type ValidateSigCountDecorator struct {
|
|
ak AccountKeeper
|
|
}
|
|
|
|
func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator {
|
|
return ValidateSigCountDecorator{
|
|
ak: ak,
|
|
}
|
|
}
|
|
|
|
// AnteHandle implements an ante decorator for ValidateSigCountDecorator
|
|
func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := vscd.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator for ValidateSigCountDecorator
|
|
func (vscd ValidateSigCountDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
|
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
|
if !ok {
|
|
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
|
|
}
|
|
|
|
params := vscd.ak.GetParams(ctx)
|
|
pubKeys, err := sigTx.GetPubKeys()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sigCount := 0
|
|
for _, pk := range pubKeys {
|
|
sigCount += CountSubKeys(pk)
|
|
if uint64(sigCount) > params.TxSigLimit {
|
|
return errorsmod.Wrapf(sdkerrors.ErrTooManySignatures, "signatures: %d, limit: %d", sigCount, params.TxSigLimit)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 gas.Meter, sig signing.SignatureV2, params types.Params) error {
|
|
pubkey := sig.PubKey
|
|
|
|
switch pubkey := pubkey.(type) {
|
|
case *ed25519.PubKey:
|
|
return meter.Consume(params.SigVerifyCostED25519, "ante verify: ed25519")
|
|
|
|
case *secp256k1.PubKey:
|
|
return meter.Consume(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
|
|
|
|
case *secp256r1.PubKey:
|
|
return meter.Consume(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
|
|
|
|
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 gas.Meter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
|
|
params types.Params, accSeq uint64,
|
|
) error {
|
|
// if BitArray is nil, it means tx has been built for simulation.
|
|
if sig.BitArray == nil {
|
|
return multisignatureSimulationVerificationGas(meter, sig, pubKey, params, accSeq)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// multisignatureSimulationVerificationGas consume gas for verifying a simulation multisig pubKey signature. As it's
|
|
// a simulation tx the number of signatures its equal to the multisig threshold.
|
|
func multisignatureSimulationVerificationGas(
|
|
meter gas.Meter, sig *signing.MultiSignatureData, pubKey multisig.PubKey,
|
|
params types.Params, accSeq uint64,
|
|
) error {
|
|
for i := 0; i < len(sig.Signatures); i++ {
|
|
sigV2 := signing.SignatureV2{
|
|
PubKey: pubKey.GetPubKeys()[i],
|
|
Data: sig.Signatures[i],
|
|
Sequence: accSeq,
|
|
}
|
|
|
|
err := DefaultSigVerificationGasConsumer(meter, sigV2, params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSignerAcc returns an account for a given address that is expected to sign
|
|
// a transaction.
|
|
func GetSignerAcc(ctx context.Context, ak AccountKeeper, addr sdk.AccAddress) sdk.AccountI {
|
|
return ak.GetAccount(ctx, addr)
|
|
}
|
|
|
|
// CountSubKeys counts the total number of keys for a multi-sig public key.
|
|
// A non-multisig, i.e. a regular signature, it naturally a count of 1. If it is a multisig,
|
|
// then it recursively calls it on its pubkeys.
|
|
func CountSubKeys(pub cryptotypes.PubKey) int {
|
|
if pub == nil {
|
|
return 0
|
|
}
|
|
|
|
v, ok := pub.(*kmultisig.LegacyAminoPubKey)
|
|
if !ok {
|
|
return 1
|
|
}
|
|
|
|
numKeys := 0
|
|
for _, subkey := range v.GetPubKeys() {
|
|
numKeys += CountSubKeys(subkey)
|
|
}
|
|
|
|
return numKeys
|
|
}
|
|
|
|
// signatureDataToBz converts a SignatureData into raw bytes signature.
|
|
// For SingleSignatureData, it returns the signature raw bytes.
|
|
// For MultiSignatureData, it returns an array of all individual signatures,
|
|
// as well as the aggregated signature.
|
|
func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
|
|
if data == nil {
|
|
return nil, errors.New("got empty SignatureData")
|
|
}
|
|
|
|
switch data := data.(type) {
|
|
case *signing.SingleSignatureData:
|
|
return [][]byte{data.Signature}, nil
|
|
|
|
case *signing.MultiSignatureData:
|
|
sigs := [][]byte{}
|
|
var err error
|
|
|
|
for _, d := range data.Signatures {
|
|
nestedSigs, err := signatureDataToBz(d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigs = append(sigs, nestedSigs...)
|
|
}
|
|
|
|
multiSignature := cryptotypes.MultiSignature{
|
|
Signatures: sigs,
|
|
}
|
|
|
|
aggregatedSig, err := multiSignature.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigs = append(sigs, aggregatedSig)
|
|
return sigs, nil
|
|
|
|
default:
|
|
return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data)
|
|
}
|
|
}
|
|
|
|
// isSigverifyTx will always return true, unless the context is a sdk.Context, in which case we will return the
|
|
// value of IsSigverifyTx.
|
|
func isSigverifyTx(ctx context.Context) bool {
|
|
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
|
|
return sdkCtx.IsSigverifyTx()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func isRecheckTx(ctx context.Context, txSvc transaction.Service) bool {
|
|
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
|
|
return sdkCtx.IsReCheckTx()
|
|
}
|
|
return txSvc.ExecMode(ctx) == transaction.ExecModeReCheck
|
|
}
|