diff --git a/app/ante_handler.go b/app/ante_handler.go index 07a356968..51768f9f7 100644 --- a/app/ante_handler.go +++ b/app/ante_handler.go @@ -82,7 +82,7 @@ func NewAnteHandler(options HandlerOptions, logger log.Logger) (sdk.AnteHandler, 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), } diff --git a/app/app.go b/app/app.go index cd8ceb0c8..5d77434ac 100644 --- a/app/app.go +++ b/app/app.go @@ -1125,7 +1125,7 @@ func New( HandlerOptions: ante.HandlerOptions{ FeegrantKeeper: app.FeeGrantKeeper, SignModeHandler: encodingConfig.TxConfig.SignModeHandler(), - SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + SigGasConsumer: DefaultSigVerificationGasConsumer, }, BankKeeper: app.BankKeeper, AccountKeeper: app.AccountKeeper, diff --git a/app/encoding.go b/app/encoding.go index d102f4380..f82c367ed 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -3,6 +3,8 @@ package app import ( "github.com/cosmos/cosmos-sdk/std" + ethcryptocodec "github.com/neutron-org/neutron/v5/x/crypto/codec" + "github.com/neutron-org/neutron/v5/app/params" ) @@ -11,6 +13,8 @@ func MakeEncodingConfig() params.EncodingConfig { encodingConfig := params.MakeEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) + ethcryptocodec.RegisterLegacyAminoCodec(encodingConfig.Amino) + ethcryptocodec.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) ModuleBasics.RegisterInterfaces(encodingConfig.InterfaceRegistry) return encodingConfig diff --git a/app/params/encoding.go b/app/params/encoding.go index 32fcf34ae..fe56f1152 100644 --- a/app/params/encoding.go +++ b/app/params/encoding.go @@ -2,6 +2,7 @@ package params import ( "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/address" @@ -9,6 +10,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/cosmos/gogoproto/proto" + + neutrontx "github.com/neutron-org/neutron/v5/tx" ) // EncodingConfig specifies the concrete encoding types to use for a given app. @@ -38,7 +41,18 @@ func MakeEncodingConfig() EncodingConfig { panic(err) } marshaler := codec.NewProtoCodec(reg) - txCfg := tx.NewTxConfig(marshaler, tx.DefaultSignModes) + + signingOptions, err := tx.NewDefaultSigningOptions() + if err != nil { + panic(err) + } + txCfg := tx.NewTxConfig( + marshaler, + tx.DefaultSignModes, + neutrontx.NewSignModeEIP191Handler(aminojson.SignModeHandlerOptions{ + FileResolver: signingOptions.FileResolver, + TypeResolver: signingOptions.TypeResolver, + })) return EncodingConfig{ InterfaceRegistry: reg, diff --git a/app/sigverify.go b/app/sigverify.go new file mode 100644 index 000000000..6328e95ae --- /dev/null +++ b/app/sigverify.go @@ -0,0 +1,465 @@ +package app + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/hex" + "fmt" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + "github.com/neutron-org/neutron/v5/x/crypto/ethsecp256k1" + + "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" + 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/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} +) + +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 +} + +// SetPubKeyDecorator sets PubKeys in context for any signer which does not already have pubkey set +// PubKeys must be set in context for all signers before any other sigverify decorators run +// CONTRACT: Tx must implement SigVerifiableTx interface +type SetPubKeyDecorator struct { + ak ante.AccountKeeper +} + +func NewSetPubKeyDecorator(ak ante.AccountKeeper) SetPubKeyDecorator { + return SetPubKeyDecorator{ + ak: ak, + } +} + +func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + + pubkeys, err := sigTx.GetPubKeys() + if err != nil { + return ctx, err + } + + signers, err := sigTx.GetSigners() + if err != nil { + return sdk.Context{}, err + } + + signerStrs := make([]string, len(signers)) + for i, pk := range pubkeys { + var err error + signerStrs[i], err = spkd.ak.AddressCodec().BytesToString(signers[i]) + if err != nil { + return sdk.Context{}, err + } + + // PublicKey was omitted from slice since it has already been set in context + if pk == nil { + if !simulate { + continue + } + pk = simSecp256k1Pubkey + } + // Only make check if simulate=false + if !simulate && !bytes.Equal(pk.Address(), signers[i]) && ctx.IsSigverifyTx() { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, + "pubKey does not match signer address %s with signer index: %d", signerStrs[i], i) + } + + acc, err := ante.GetSignerAcc(ctx, spkd.ak, signers[i]) + if err != nil { + return ctx, err + } + // account already has pubkey set,no need to reset + if acc.GetPubKey() != nil { + continue + } + err = acc.SetPubKey(pk) + if err != nil { + return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, err.Error()) + } + spkd.ak.SetAccount(ctx, acc) + } + + // Also emit the following events, so that txs can be indexed by these + // indices: + // - signature (via `tx.signature=''`), + // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + var events sdk.Events + for i, sig := range sigs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStrs[i], sig.Sequence)), + )) + + sigBzs, err := signatureDataToBz(sig.Data) + if err != nil { + return ctx, err + } + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } + } + + ctx.EventManager().EmitEvents(events) + + return next(ctx, tx, simulate) +} + +// 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 ante.AccountKeeper + signModeHandler *txsigning.HandlerMap +} + +func NewSigVerificationDecorator(ak ante.AccountKeeper, signModeHandler *txsigning.HandlerMap) SigVerificationDecorator { + return SigVerificationDecorator{ + ak: ak, + signModeHandler: signModeHandler, + } +} + +// 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 == signing.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) 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 := ante.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() && ctx.IsSigverifyTx() { + 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 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 +} + +// 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, fmt.Errorf("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) + } +} + +// 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) + } +} + +// APISignModesToInternal converts a protobuf SignMode array to a signing.SignMode array. +func APISignModesToInternal(modes []signingv1beta1.SignMode) ([]signing.SignMode, error) { + internalModes := make([]signing.SignMode, len(modes)) + for i, mode := range modes { + internalMode, err := APISignModeToInternal(mode) + if err != nil { + return nil, err + } + internalModes[i] = internalMode + } + return internalModes, nil +} + +// APISignModeToInternal converts a protobuf SignMode to a signing.SignMode. +func APISignModeToInternal(mode signingv1beta1.SignMode) (signing.SignMode, error) { + switch mode { + case signingv1beta1.SignMode_SIGN_MODE_DIRECT: + return signing.SignMode_SIGN_MODE_DIRECT, nil + case signingv1beta1.SignMode_SIGN_MODE_LEGACY_AMINO_JSON: + return signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, nil + case signingv1beta1.SignMode_SIGN_MODE_TEXTUAL: + return signing.SignMode_SIGN_MODE_TEXTUAL, nil + case signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX: + return signing.SignMode_SIGN_MODE_DIRECT_AUX, nil + case signingv1beta1.SignMode_SIGN_MODE_EIP_191: //nolint + return signing.SignMode_SIGN_MODE_EIP_191, nil //nolint + default: + return signing.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode %s", mode) + } +} + +// 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: //nolint + return signingv1beta1.SignMode_SIGN_MODE_EIP_191, nil //nolint + default: + return signingv1beta1.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode %s", mode) + } +} diff --git a/cmd/neutrond/root.go b/cmd/neutrond/root.go index 883178e3b..0d937ecdc 100644 --- a/cmd/neutrond/root.go +++ b/cmd/neutrond/root.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" + "github.com/neutron-org/neutron/v5/x/crypto/keyring" + "cosmossdk.io/log" "cosmossdk.io/store" "cosmossdk.io/store/snapshots" @@ -82,7 +84,8 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { WithAccountRetriever(authtypes.AccountRetriever{}). WithBroadcastMode(flags.BroadcastSync). WithHomeDir(app.DefaultNodeHome). - WithViper("") + WithViper(""). + WithKeyringOptions(keyring.Option()) // Allows you to add extra params to your client.toml // gas, gas-price, gas-adjustment, fees, note, etc. diff --git a/network/init.sh b/network/init.sh index 3a7d513a5..99951f785 100755 --- a/network/init.sh +++ b/network/init.sh @@ -83,7 +83,6 @@ sed -i -e 's#"tcp://localhost:1317"#"tcp://0.0.0.0:'"$RESTPORT"'"#g' "$CHAIN_DIR sed -i -e 's#"tcp://0.0.0.0:1317"#"tcp://0.0.0.0:'"$RESTPORT"'"#g' "$CHAIN_DIR/config/app.toml" sed -i -e 's#":8080"#":'"$ROSETTA_1"'"#g' "$CHAIN_DIR/config/app.toml" - sed -i -e 's/oracle_address = "localhost:8080"/oracle_address = '\""$ORACLE_ADDRESS"\"'/g' "$CHAIN_DIR/config/app.toml" sed -i -e 's/client_timeout = "2s"/client_timeout = '\""$ORACLE_CLIENT_TIMEOUT"\"'/g' "$CHAIN_DIR/config/app.toml" sed -i -e 's/metrics_enabled = true/metrics_enabled = '\""$ORACLE_METRICS_ENABLED"\"'/g' "$CHAIN_DIR/config/app.toml" diff --git a/proto/neutron/crypto/v1beta1/ethsecp256k1/keys.proto b/proto/neutron/crypto/v1beta1/ethsecp256k1/keys.proto new file mode 100644 index 000000000..c306ee23a --- /dev/null +++ b/proto/neutron/crypto/v1beta1/ethsecp256k1/keys.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package neutron.crypto.v1beta1.ethsecp256k1; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; + +option go_package = "github.com/neutron-org/neutron/v5/x/crypto/ethsecp256k1"; + +// PubKey defines a type alias for an ecdsa.PublicKey that implements +// Tendermint's PubKey interface. It represents the 33-byte compressed public +// key format. +message PubKey { + option (amino.name) = "eth/PubKeyEthSecp256k1"; + option (gogoproto.goproto_stringer) = false; + + bytes key = 1; +} + +// PrivKey defines a type alias for an ecdsa.PrivateKey that implements +// Tendermint's PrivateKey interface. +message PrivKey { + option (amino.name) = "eth/PrivKeyEthSecp256k1"; + + bytes key = 1; +} diff --git a/tx/eip191.go b/tx/eip191.go new file mode 100644 index 000000000..ab5ce663f --- /dev/null +++ b/tx/eip191.go @@ -0,0 +1,48 @@ +package tx + +import ( + "context" + "strconv" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" +) + +const EIP191MessagePrefix = "\x19Ethereum Signed Message:\n" + +// SignModeEIP191Handler defines the SIGN_MODE_DIRECT SignModeHandler +type SignModeEIP191Handler struct { + *aminojson.SignModeHandler +} + +// NewSignModeEIP191Handler returns a new SignModeEIP191Handler. +func NewSignModeEIP191Handler(options aminojson.SignModeHandlerOptions) *SignModeEIP191Handler { + return &SignModeEIP191Handler{ + SignModeHandler: aminojson.NewSignModeHandler(options), + } +} + +var _ signing.SignModeHandler = SignModeEIP191Handler{} + +// Mode implements signing.SignModeHandler.Mode. +func (SignModeEIP191Handler) Mode() signingv1beta1.SignMode { + return signingv1beta1.SignMode_SIGN_MODE_EIP_191 //nolint +} + +// GetSignBytes implements SignModeHandler.GetSignBytes +func (h SignModeEIP191Handler) GetSignBytes( + ctx context.Context, data signing.SignerData, txData signing.TxData, +) ([]byte, error) { + aminoJSONBz, err := h.SignModeHandler.GetSignBytes(ctx, data, txData) + if err != nil { + return nil, err + } + + srvBz := append(append( + []byte(EIP191MessagePrefix), + []byte(strconv.Itoa(len(aminoJSONBz)))..., + ), aminoJSONBz...) + + return srvBz, nil +} diff --git a/x/crypto/codec/amino.go b/x/crypto/codec/amino.go new file mode 100644 index 000000000..61a0ef962 --- /dev/null +++ b/x/crypto/codec/amino.go @@ -0,0 +1,19 @@ +package codec + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + + "github.com/neutron-org/neutron/v5/x/crypto/ethsecp256k1" +) + +// RegisterLegacyAminoCodec registers all crypto dependency types with the provided Amino +// codec. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(ðsecp256k1.PubKey{}, ethsecp256k1.PubKeyName, nil) + cdc.RegisterConcrete(ðsecp256k1.PrivKey{}, ethsecp256k1.PrivKeyName, nil) + + // NOTE: update SDK's amino codec to include the ethsecp256k1 keys. + // DO NOT REMOVE unless deprecated on the SDK. + legacy.Cdc = cdc +} diff --git a/x/crypto/codec/codec.go b/x/crypto/codec/codec.go new file mode 100644 index 000000000..322b08b67 --- /dev/null +++ b/x/crypto/codec/codec.go @@ -0,0 +1,14 @@ +package codec + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/neutron-org/neutron/v5/x/crypto/ethsecp256k1" +) + +// RegisterInterfaces register the ethsecp256k1 key concrete types. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*cryptotypes.PubKey)(nil), ðsecp256k1.PubKey{}) + registry.RegisterImplementations((*cryptotypes.PrivKey)(nil), ðsecp256k1.PrivKey{}) +} diff --git a/x/crypto/ethsecp256k1/benchmark_test.go b/x/crypto/ethsecp256k1/benchmark_test.go new file mode 100644 index 000000000..463aaef4d --- /dev/null +++ b/x/crypto/ethsecp256k1/benchmark_test.go @@ -0,0 +1,29 @@ +package ethsecp256k1 + +import ( + "fmt" + "testing" +) + +func BenchmarkGenerateKey(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = GenerateKey() + } +} + +func BenchmarkPubKey_VerifySignature(b *testing.B) { + privKey := GenerateKey() + pubKey := privKey.PubKey() + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + msg := []byte(fmt.Sprintf("%10d", i)) + sig, err := privKey.Sign(msg) + if err != nil { + b.Fatal(err) + } + pubKey.VerifySignature(msg, sig) + } +} diff --git a/x/crypto/ethsecp256k1/ethsecp256k1.go b/x/crypto/ethsecp256k1/ethsecp256k1.go new file mode 100644 index 000000000..23a318c89 --- /dev/null +++ b/x/crypto/ethsecp256k1/ethsecp256k1.go @@ -0,0 +1,284 @@ +package ethsecp256k1 + +import ( + "bytes" + "crypto/subtle" + "errors" + "fmt" + "io" + "math/big" + + errorsmod "cosmossdk.io/errors" + "github.com/cometbft/cometbft/crypto" + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "golang.org/x/crypto/sha3" + + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" +) + +const ( + // PrivKeySize defines the size of the PrivKey bytes + PrivKeySize = 32 + // PubKeySize defines the size of the PubKey bytes + PubKeySize = 33 + // UncompressedPubKeySize defines the size of the uncompressed PubKey bytes + UncompressedPubKeySize = 65 + // SignatureSize defines the size of the ECDSA signature with the recovery ID + SignatureSize = 65 + // KeyType is the string constant for the Secp256k1 algorithm + KeyType = "eth_secp256k1" +) + +// Amino encoding names +const ( + // PrivKeyName defines the amino encoding name for the EthSecp256k1 private key + PrivKeyName = "eth/PrivKeyEthSecp256k1" + // PubKeyName defines the amino encoding name for the EthSecp256k1 public key + PubKeyName = "eth/PubKeyEthSecp256k1" +) + +// ---------------------------------------------------------------------------- +// secp256k1 Private Key + +var ( + _ cryptotypes.PrivKey = &PrivKey{} + _ codec.AminoMarshaler = &PrivKey{} +) + +// GenerateKey generates a new random private key. It returns an error upon +// failure. +func GenerateKey() *PrivKey { + return &PrivKey{Key: genPrivKey(crypto.CReader())} +} + +// genPrivKey generates a new secp256k1 private key using the provided reader. +func genPrivKey(rand io.Reader) []byte { + var privKeyBytes [PrivKeySize]byte + d := new(big.Int) + for { + privKeyBytes = [PrivKeySize]byte{} + _, err := io.ReadFull(rand, privKeyBytes[:]) + if err != nil { + panic(err) + } + + d.SetBytes(privKeyBytes[:]) + // break if we found a valid point (i.e. > 0 and < N == curverOrder) + isValidFieldElement := 0 < d.Sign() && d.Cmp(secp256k1.S256().N) < 0 + if isValidFieldElement { + break + } + } + + return privKeyBytes[:] +} + +// Bytes returns the byte representation of the ECDSA Private Key. +func (privKey PrivKey) Bytes() []byte { + bz := make([]byte, len(privKey.Key)) + copy(bz, privKey.Key) + + return bz +} + +// PubKey returns the ECDSA private key's public key. If the privkey is not valid +// it returns a nil value. +func (privKey PrivKey) PubKey() cryptotypes.PubKey { + pubkeyObject := secp256k1.PrivKeyFromBytes(privKey.Key).PubKey() + pk := pubkeyObject.SerializeCompressed() + + return &PubKey{Key: pk} +} + +// Equals returns true if two ECDSA private keys are equal and false otherwise. +func (privKey PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool { + return privKey.Type() == other.Type() && subtle.ConstantTimeCompare(privKey.Bytes(), other.Bytes()) == 1 +} + +// Type returns eth_secp256k1 +func (privKey PrivKey) Type() string { + return KeyType +} + +// MarshalAmino overrides Amino binary marshaling. +func (privKey PrivKey) MarshalAmino() ([]byte, error) { + return privKey.Key, nil +} + +// UnmarshalAmino overrides Amino binary marshaling. +func (privKey *PrivKey) UnmarshalAmino(bz []byte) error { + if len(bz) != PrivKeySize { + return fmt.Errorf("invalid privkey size, expected %d got %d", PrivKeySize, len(bz)) + } + privKey.Key = bz + + return nil +} + +// MarshalAminoJSON overrides Amino JSON marshaling. +func (privKey PrivKey) MarshalAminoJSON() ([]byte, error) { + // When we marshal to Amino JSON, we don't marshal the "key" field itself, + // just its contents (i.e. the key bytes). + return privKey.MarshalAmino() +} + +// UnmarshalAminoJSON overrides Amino JSON marshaling. +func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error { + return privKey.UnmarshalAmino(bz) +} + +// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the +// provided hash of the message. The produced signature is 65 bytes +// where the last byte contains the recovery ID. +func (privKey PrivKey) Sign(digestBz []byte) ([]byte, error) { + priv := secp256k1.PrivKeyFromBytes(privKey.Key) + + sig := ecdsa.SignCompact(priv, keccak256(digestBz), false) + recid := sig[0] + + // remove the first byte which is compactSigRecoveryCode + return append(sig[1:], recid), nil +} + +// ---------------------------------------------------------------------------- +// secp256k1 Public Key + +var ( + _ cryptotypes.PubKey = &PubKey{} + _ codec.AminoMarshaler = &PubKey{} +) + +// Create a new PubKey object from a compressed or uncompressed byte slice. +func NewPubKeyFromBytes(key []byte) (*PubKey, error) { + if len(key) == UncompressedPubKeySize { + // compress the key + pub, err := secp256k1.ParsePubKey(key) + if err != nil { + return nil, err + } + key = pub.SerializeCompressed() + } else if len(key) != PubKeySize { + return nil, errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(key)) + } + + return &PubKey{Key: key}, nil +} + +// Address returns the address of the ECDSA public key. +// The function will return an empty address if the public key is invalid. +func (pubKey PubKey) Address() crypto.Address { + pub, err := secp256k1.ParsePubKey(pubKey.Key) + if err != nil { + panic(err) + } + + pubBytes := pub.SerializeUncompressed() + return crypto.Address(keccak256(pubBytes[1:])[12:]) +} + +// Bytes returns the raw bytes of the ECDSA public key. +func (pubKey PubKey) Bytes() []byte { + bz := make([]byte, len(pubKey.Key)) + copy(bz, pubKey.Key) + + return bz +} + +// String implements the fmt.Stringer interface. +func (pubKey PubKey) String() string { + return fmt.Sprintf("EthPubKeySecp256k1{%X}", pubKey.Key) +} + +// Type returns eth_secp256k1 +func (pubKey PubKey) Type() string { + return KeyType +} + +// Equals returns true if the pubkey type is the same and their bytes are deeply equal. +func (pubKey PubKey) Equals(other cryptotypes.PubKey) bool { + return pubKey.Type() == other.Type() && bytes.Equal(pubKey.Bytes(), other.Bytes()) +} + +// MarshalAmino overrides Amino binary marshaling. +func (pubKey PubKey) MarshalAmino() ([]byte, error) { + return pubKey.Key, nil +} + +// UnmarshalAmino overrides Amino binary marshaling. +func (pubKey *PubKey) UnmarshalAmino(bz []byte) error { + if len(bz) != PubKeySize { + return errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "invalid pubkey size, expected %d, got %d", PubKeySize, len(bz)) + } + pubKey.Key = bz + + return nil +} + +// MarshalAminoJSON overrides Amino JSON marshaling. +func (pubKey PubKey) MarshalAminoJSON() ([]byte, error) { + // When we marshal to Amino JSON, we don't marshal the "key" field itself, + // just its contents (i.e. the key bytes). + return pubKey.MarshalAmino() +} + +// UnmarshalAminoJSON overrides Amino JSON marshaling. +func (pubKey *PubKey) UnmarshalAminoJSON(bz []byte) error { + return pubKey.UnmarshalAmino(bz) +} + +// VerifySignature verifies that the ECDSA public key created a given signature over +// the provided message. It will calculate the Keccak256 hash of the message +// prior to verification and approve verification if the signature can be verified +// from either the original message or its EIP-712 representation. +// +// CONTRACT: The signature should be in [R || S] format. +func (pubKey PubKey) VerifySignature(msg, sig []byte) bool { + return pubKey.verifySignatureECDSA(msg, sig) +} + +// Perform standard ECDSA signature verification for the given raw bytes and signature. +func (pubKey PubKey) verifySignatureECDSA(msg, sigStr []byte) bool { + if len(sigStr) == SignatureSize { + // remove recovery ID (V) if contained in the signature + sigStr = sigStr[:len(sigStr)-1] + } + + if len(sigStr) != 64 { + return false + } + pub, err := secp256k1.ParsePubKey(pubKey.Key) + if err != nil { + return false + } + + // parse the signature, will return error if it is not in lower-S form + signature, err := signatureFromBytes(sigStr) + if err != nil { + return false + } + return signature.Verify(keccak256(msg), pub) +} + +// Read Signature struct from R || S. Caller needs to ensure +// that len(sigStr) == 64. +// Rejects malleable signatures (if S value if it is over half order). +func signatureFromBytes(sigStr []byte) (*ecdsa.Signature, error) { + var r secp256k1.ModNScalar + r.SetByteSlice(sigStr[:32]) + var s secp256k1.ModNScalar + s.SetByteSlice(sigStr[32:64]) + if s.IsOverHalfOrder() { + return nil, errors.New("signature is not in lower-S form") + } + + return ecdsa.NewSignature(&r, &s), nil +} + +func keccak256(bytes []byte) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(bytes) + return hasher.Sum(nil) +} diff --git a/x/crypto/ethsecp256k1/ethsecp256k1_test.go b/x/crypto/ethsecp256k1/ethsecp256k1_test.go new file mode 100644 index 000000000..7b8e41f67 --- /dev/null +++ b/x/crypto/ethsecp256k1/ethsecp256k1_test.go @@ -0,0 +1,261 @@ +package ethsecp256k1 + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +func TestPrivKey(t *testing.T) { + // validate type and equality + privKeyBz, err := hex.DecodeString("d9b18131efa344763bd5e3d1f7c9a12bdd3b62adf178fd25ec01b3594226b2d3") + require.NoError(t, err) + privKey := &PrivKey{ + Key: privKeyBz, + } + + require.Implements(t, (*cryptotypes.PrivKey)(nil), privKey) + + // validate inequality + privKey2 := GenerateKey() + require.False(t, privKey.Equals(privKey2)) + + // validate Ethereum address equality + addr := privKey.PubKey().Address() + require.NoError(t, err) + + expectedAddr, err := hex.DecodeString("ff4a64bddd522d3559b7dc2baa2de5364a7bc1d4") + require.NoError(t, err) + require.Equal(t, addr.Bytes(), expectedAddr) + + // validate we can sign some bytes + msg := []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", 11, "hello world")) + sig, err := privKey.Sign(msg) + require.NoError(t, err) + + require.Equal(t, hex.EncodeToString(sig), "351f94bfeacbce8c6aa1dc8f9aaa81e0f984df0352b41233b99c4576e486eb537471f3da6f62865e2f6720ea9a08e7aadb7d2d705f9879db0b5d5c0734f3b49f1b") +} + +func TestPrivKey_PubKey(t *testing.T) { + privKey := GenerateKey() + + // validate type and equality + pubKey := &PubKey{ + Key: privKey.PubKey().Bytes(), + } + require.Implements(t, (*cryptotypes.PubKey)(nil), pubKey) + + // validate inequality + privKey2 := GenerateKey() + require.False(t, pubKey.Equals(privKey2.PubKey())) + + // validate signature + msg := []byte("hello world") + sig, err := privKey.Sign(msg) + require.NoError(t, err) + + res := pubKey.VerifySignature(msg, sig) + require.True(t, res) +} + +func TestPubKeyAddressUncompressed(t *testing.T) { + // Given valid public key (uncompressed 65-byte key starting with 0x04) + pubKeyHex := "0404794d0d9aa382bb479bf05ef71c1527af06f649f2fa659f83e08b602b8fba48e2ef4c82ed6d77487e56e9e89a55785f2ae3e4a84f4eee8295ff4cde1e5c55a9" + expectedAddress := "A53AEF059604AD5B48DCA84E60F59CDACCF61C45" // Expected Ethereum address + + // Decode hex string into bytes + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + + // Create PubKey struct, directly injecting the public key bytes + pubKey := PubKey{Key: pubKeyBytes} + + // Compute address + computedAddress := pubKey.Address().String() + require.Equal(t, expectedAddress, computedAddress, "Derived address should match expected Ethereum address") + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for invalid public key length, but function did not panic") + } + }() + invalidPubKey := PubKey{Key: []byte("12345678901234567890")} // Incorrect length, + invalidPubKey.Address() // Should panic +} + +// raw message +var msg = "Hello, MetaMask!" + +// this is the message that is signed by the metamask +var formattedMsg = fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(msg), msg) + +// signature for the message +var sigHex = "3dadd2820aad62a5e545f7b18178708f0f63afd667f9d2535e43870b52e57a1f333f416aa9485deaed22ac1e2ffe35485afdb45989c797acd654b73a881ace1f1b" + +// public key +var pubKeyHex = "044c352d52ba4e507085205e9a029432defbc8d8f05ed828cbce0eb1a8823097723dc9caa6c60c17ad9073c2cdcdb409fe20110c40359607a64ca22d6607770655" + +// signature as bytes +var sigBytes, _ = hex.DecodeString(sigHex) + +// public key as bytes +var pubKeyBytes, _ = hex.DecodeString(pubKeyHex) + +var pubKey = PubKey{ + Key: pubKeyBytes, +} + +func TestVerifySignatureECDSA__uncompressedPublicKeyValid(t *testing.T) { + // Given data (message, signature, and public key) + // Given valid public key (uncompressed 65-byte key starting with 0x04) + pubKeyHex := "044c352d52ba4e507085205e9a029432defbc8d8f05ed828cbce0eb1a8823097723dc9caa6c60c17ad9073c2cdcdb409fe20110c40359607a64ca22d6607770655" + // Decode public key from hex + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + // Create pubKey struct + pubKey := PubKey{ + Key: pubKeyBytes, + } + + require.True(t, pubKey.VerifySignature([]byte(formattedMsg), sigBytes), "Valid signature should pass verification") +} + +func TestVerifySignatureECDSA__compressedPublicKeyValid(t *testing.T) { + // Given data (message, signature, and public key) + // Given valid public key (compressed 33-byte key starting with 0x02 or 0x03) + pubKeyHex := "034c352d52ba4e507085205e9a029432defbc8d8f05ed828cbce0eb1a882309772" + // Decode public key from hex + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + // Create pubKey struct + pubKey := PubKey{ + Key: pubKeyBytes, + } + + require.True(t, pubKey.VerifySignature([]byte(formattedMsg), sigBytes), "Valid signature should pass verification") +} + +func TestVerifySignatureECDSA__invalidSignatureFails(t *testing.T) { + modifiedSig := make([]byte, len(sigBytes)) + modifiedSig[10] ^= 0xFF // Flip one byte + + pubKey := PubKey{ + Key: pubKeyBytes, + } + require.False(t, pubKey.verifySignatureECDSA([]byte(formattedMsg), modifiedSig), "modified signature should fail verification") +} + +func TestVerifySignatureECDSA__invalidPubKeyFails(t *testing.T) { + invalidPubKey := PubKey{ + Key: []byte("09234230472347234723094723947023"), // Wrong length + } + require.False(t, invalidPubKey.verifySignatureECDSA([]byte(formattedMsg), sigBytes), + "invalid public key should fail verification", + ) +} + +func TestVerifySignatureECDSA__shortSignatureFails(t *testing.T) { + shortSig := sigBytes[:30] // Truncated signature + require.False(t, pubKey.verifySignatureECDSA([]byte(formattedMsg), shortSig), + "short signature should fail verification") +} + +func TestVerifySignatureECDSA__longSignatureFails(t *testing.T) { + longSig := make([]byte, len(sigBytes)+1) + longSig = append(longSig, 0x1b) + require.False(t, pubKey.verifySignatureECDSA([]byte(formattedMsg), longSig), + "long signature should fail verification") +} + +func TestVerifySignatureECDSA__badSignatureFails(t *testing.T) { + randomSig := make([]byte, len(sigBytes)) + for i := range randomSig { + randomSig[i] = byte(i) // Fill with random values + } + require.False(t, pubKey.verifySignatureECDSA([]byte(formattedMsg), + randomSig), "random signature should fail verification") +} + +func TestVerifySignatureECDSA__invalidSValueFails(t *testing.T) { + invalidSsig := make([]byte, len(sigBytes)) + copy(invalidSsig, sigBytes) + invalidSsig[32] |= 0x80 // flip the most significant bit of S => S > N/2 + require.False(t, pubKey.verifySignatureECDSA([]byte(formattedMsg), + invalidSsig), "signature with S > N/2 should fail") +} + +func TestVerifySignatureECDSA__badPublicKeyFails(t *testing.T) { + modifiedPubKeyBytes := make([]byte, len(pubKeyBytes)) + copy(modifiedPubKeyBytes, pubKeyBytes) + modifiedPubKeyBytes[5] ^= 0xFF // flip one byte - (not on the secp256k1 curve anymore) + modifiedPubKey := PubKey{Key: modifiedPubKeyBytes} + require.False(t, modifiedPubKey.verifySignatureECDSA([]byte(formattedMsg), + sigBytes), "modified public key should fail verification") +} + +func TestVerifySignatureECDSA__modifiedMessageFails(t *testing.T) { + modifiedMessage := "\x19Ethereum Signe Message:\n" + strconv.Itoa(len(msg)) + msg + require.False(t, pubKey.verifySignatureECDSA([]byte(modifiedMessage), sigBytes), + "slightly modified message should fail verification") +} + +func TestMarshalAmino(t *testing.T) { + aminoCdc := codec.NewLegacyAmino() + privKey := GenerateKey() + + pubKey := privKey.PubKey().(*PubKey) + + testCases := []struct { + desc string + msg codec.AminoMarshaler + typ interface{} + expBinary []byte + expJSON string + }{ + { + "ethsecp256k1 private key", + privKey, + &PrivKey{}, + append([]byte{32}, privKey.Bytes()...), // Length-prefixed. + "\"" + base64.StdEncoding.EncodeToString(privKey.Bytes()) + "\"", + }, + { + "ethsecp256k1 public key", + pubKey, + &PubKey{}, + append([]byte{33}, pubKey.Bytes()...), // Length-prefixed. + "\"" + base64.StdEncoding.EncodeToString(pubKey.Bytes()) + "\"", + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + // Do a round trip of encoding/decoding binary. + bz, err := aminoCdc.Marshal(tc.msg) + require.NoError(t, err) + require.Equal(t, tc.expBinary, bz) + + err = aminoCdc.Unmarshal(bz, tc.typ) + require.NoError(t, err) + + require.Equal(t, tc.msg, tc.typ) + + // Do a round trip of encoding/decoding JSON. + bz, err = aminoCdc.MarshalJSON(tc.msg) + require.NoError(t, err) + require.Equal(t, tc.expJSON, string(bz)) + + err = aminoCdc.UnmarshalJSON(bz, tc.typ) + require.NoError(t, err) + + require.Equal(t, tc.msg, tc.typ) + }) + } +} diff --git a/x/crypto/ethsecp256k1/keys.pb.go b/x/crypto/ethsecp256k1/keys.pb.go new file mode 100644 index 000000000..202995af9 --- /dev/null +++ b/x/crypto/ethsecp256k1/keys.pb.go @@ -0,0 +1,502 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: neutron/crypto/v1beta1/ethsecp256k1/keys.proto + +package ethsecp256k1 + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// PubKey defines a type alias for an ecdsa.PublicKey that implements +// Tendermint's PubKey interface. It represents the 33-byte compressed public +// key format. +type PubKey struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (m *PubKey) Reset() { *m = PubKey{} } +func (*PubKey) ProtoMessage() {} +func (*PubKey) Descriptor() ([]byte, []int) { + return fileDescriptor_fcb35f23873c68e7, []int{0} +} +func (m *PubKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PubKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PubKey.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 *PubKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_PubKey.Merge(m, src) +} +func (m *PubKey) XXX_Size() int { + return m.Size() +} +func (m *PubKey) XXX_DiscardUnknown() { + xxx_messageInfo_PubKey.DiscardUnknown(m) +} + +var xxx_messageInfo_PubKey proto.InternalMessageInfo + +func (m *PubKey) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +// PrivKey defines a type alias for an ecdsa.PrivateKey that implements +// Tendermint's PrivateKey interface. +type PrivKey struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (m *PrivKey) Reset() { *m = PrivKey{} } +func (m *PrivKey) String() string { return proto.CompactTextString(m) } +func (*PrivKey) ProtoMessage() {} +func (*PrivKey) Descriptor() ([]byte, []int) { + return fileDescriptor_fcb35f23873c68e7, []int{1} +} +func (m *PrivKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PrivKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PrivKey.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 *PrivKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_PrivKey.Merge(m, src) +} +func (m *PrivKey) XXX_Size() int { + return m.Size() +} +func (m *PrivKey) XXX_DiscardUnknown() { + xxx_messageInfo_PrivKey.DiscardUnknown(m) +} + +var xxx_messageInfo_PrivKey proto.InternalMessageInfo + +func (m *PrivKey) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func init() { + proto.RegisterType((*PubKey)(nil), "neutron.crypto.v1beta1.ethsecp256k1.PubKey") + proto.RegisterType((*PrivKey)(nil), "neutron.crypto.v1beta1.ethsecp256k1.PrivKey") +} + +func init() { + proto.RegisterFile("neutron/crypto/v1beta1/ethsecp256k1/keys.proto", fileDescriptor_fcb35f23873c68e7) +} + +var fileDescriptor_fcb35f23873c68e7 = []byte{ + // 243 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xcb, 0x4b, 0x2d, 0x2d, + 0x29, 0xca, 0xcf, 0xd3, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, + 0x49, 0x34, 0xd4, 0x4f, 0x2d, 0xc9, 0x28, 0x4e, 0x4d, 0x2e, 0x30, 0x32, 0x35, 0xcb, 0x36, 0xd4, + 0xcf, 0x4e, 0xad, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x52, 0x86, 0xaa, 0xd7, 0x83, + 0xa8, 0xd7, 0x83, 0xaa, 0xd7, 0x43, 0x56, 0x2f, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xaf, + 0x0f, 0x62, 0x41, 0xb4, 0x4a, 0x09, 0x26, 0xe6, 0x66, 0xe6, 0xe5, 0xeb, 0x83, 0x49, 0x88, 0x90, + 0x92, 0x35, 0x17, 0x5b, 0x40, 0x69, 0x92, 0x77, 0x6a, 0xa5, 0x90, 0x00, 0x17, 0x73, 0x76, 0x6a, + 0xa5, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x88, 0x69, 0x25, 0x3f, 0x63, 0x81, 0x3c, 0x43, + 0xd7, 0xf3, 0x0d, 0x5a, 0x62, 0xa9, 0x25, 0x19, 0xfa, 0x10, 0x65, 0xae, 0x25, 0x19, 0xc1, 0x30, + 0x5b, 0x94, 0x2c, 0xb9, 0xd8, 0x03, 0x8a, 0x32, 0xcb, 0xb0, 0xeb, 0x96, 0x01, 0xe9, 0x14, 0x07, + 0xeb, 0x84, 0xa8, 0x41, 0xd6, 0xea, 0x14, 0x78, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, + 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, + 0x0c, 0x51, 0xe6, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x50, 0xaf, + 0xea, 0xe6, 0x17, 0xa5, 0xc3, 0xd8, 0xfa, 0x65, 0xa6, 0xfa, 0x15, 0xb0, 0xb0, 0x42, 0xf6, 0x73, + 0x12, 0x1b, 0xd8, 0x47, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xba, 0xe5, 0x0a, 0xb1, 0x51, + 0x01, 0x00, 0x00, +} + +func (m *PubKey) 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 *PubKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PubKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintKeys(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PrivKey) 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 *PrivKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PrivKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintKeys(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintKeys(dAtA []byte, offset int, v uint64) int { + offset -= sovKeys(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *PubKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovKeys(uint64(l)) + } + return n +} + +func (m *PrivKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovKeys(uint64(l)) + } + return n +} + +func sovKeys(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozKeys(x uint64) (n int) { + return sovKeys(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *PubKey) 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 ErrIntOverflowKeys + } + 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: PubKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PubKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthKeys + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthKeys + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKeys(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthKeys + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrivKey) 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 ErrIntOverflowKeys + } + 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: PrivKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrivKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKeys + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthKeys + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthKeys + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKeys(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthKeys + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipKeys(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKeys + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthKeys + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupKeys + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthKeys + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthKeys = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowKeys = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupKeys = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/crypto/hd/algorithm.go b/x/crypto/hd/algorithm.go new file mode 100644 index 000000000..4547b3b57 --- /dev/null +++ b/x/crypto/hd/algorithm.go @@ -0,0 +1,66 @@ +package hd + +import ( + "github.com/cosmos/go-bip39" + + "github.com/neutron-org/neutron/v5/x/crypto/ethsecp256k1" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // EthSecp256k1Type defines the ECDSA secp256k1 used on Ethereum + EthSecp256k1Type = hd.PubKeyType(ethsecp256k1.KeyType) +) + +var ( + _ keyring.SignatureAlgo = EthSecp256k1 + + // EthSecp256k1 uses the Bitcoin secp256k1 ECDSA parameters. + EthSecp256k1 = ethSecp256k1Algo{} +) + +const DefaultFullBIP44Path = "m/44'/60'/0'/0/0" + +type ethSecp256k1Algo struct{} + +// Name returns eth_secp256k1 +func (s ethSecp256k1Algo) Name() hd.PubKeyType { + return EthSecp256k1Type +} + +// Derive derives and returns the secp256k1 private key for the given seed and HD path. +func (s ethSecp256k1Algo) Derive() hd.DeriveFn { + return func(mnemonic, bip39Passphrase, hdPath string) ([]byte, error) { + // override the default BIP44 path to match Ethereum derivation + if hdPath == sdk.GetConfig().GetFullBIP44Path() { + hdPath = DefaultFullBIP44Path + } + + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + if err != nil { + return nil, err + } + + masterPriv, ch := hd.ComputeMastersFromSeed(seed) + if len(hdPath) == 0 { + return masterPriv[:], nil + } + derivedKey, err := hd.DerivePrivateKeyForPath(masterPriv, ch, hdPath) + + return derivedKey, err + } +} + +// Generate generates a secp256k1 private key from the given bytes. +func (s ethSecp256k1Algo) Generate() hd.GenerateFn { + return func(bz []byte) types.PrivKey { + bzArr := make([]byte, ethsecp256k1.PrivKeySize) + copy(bzArr, bz) + + return ðsecp256k1.PrivKey{Key: bzArr} + } +} diff --git a/x/crypto/keyring/options.go b/x/crypto/keyring/options.go new file mode 100644 index 000000000..9ee500e9a --- /dev/null +++ b/x/crypto/keyring/options.go @@ -0,0 +1,28 @@ +package keyring + +import ( + cosmoshd "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/neutron-org/neutron/v5/x/crypto/hd" +) + +var ( + // SupportedAlgorithms defines the list of signing algorithms used: // TODO: check this + // - eth_secp256k1 (Ethereum) + // - secp256k1 (Cosmos SDK) + SupportedAlgorithms = keyring.SigningAlgoList{hd.EthSecp256k1, cosmoshd.Secp256k1} + // SupportedAlgorithmsLedger defines the list of signing algorithms used for the Ledger device: + // - secp256k1 (in order to comply with Cosmos SDK) + // The Ledger derivation function is responsible for all signing and address generation. + SupportedAlgorithmsLedger = keyring.SigningAlgoList{hd.EthSecp256k1, cosmoshd.Secp256k1} +) + +// EthSecp256k1Option defines a function keys options for the ethereum Secp256k1 curve. +// It supports eth_secp256k1 keys for accounts. +func Option() keyring.Option { + return func(options *keyring.Options) { + options.SupportedAlgos = SupportedAlgorithms + options.SupportedAlgosLedger = SupportedAlgorithmsLedger + } +}