From 2d0025e87d977f56e9e96a8e4f066b49e908cd9a Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 20:14:09 +0200 Subject: [PATCH 01/14] add wallets types --- go.mod | 9 +- go.sum | 1 - wallets/accounts/accounts.go | 80 +++++ wallets/ledger/ledger.go | 190 ++++++++++++ wallets/ledger/ledger_suite_test.go | 155 ++++++++++ wallets/ledger/ledger_test.go | 326 +++++++++++++++++++++ wallets/ledger/mocks/wallet.go | 198 +++++++++++++ wallets/ledger/wallet_test.go | 47 +++ wallets/usbwallet/hub.go | 219 ++++++++++++++ wallets/usbwallet/ledger.go | 433 ++++++++++++++++++++++++++++ wallets/usbwallet/wallet.go | 413 ++++++++++++++++++++++++++ 11 files changed, 2066 insertions(+), 5 deletions(-) create mode 100644 wallets/accounts/accounts.go create mode 100644 wallets/ledger/ledger.go create mode 100644 wallets/ledger/ledger_suite_test.go create mode 100644 wallets/ledger/ledger_test.go create mode 100644 wallets/ledger/mocks/wallet.go create mode 100644 wallets/ledger/wallet_test.go create mode 100644 wallets/usbwallet/hub.go create mode 100644 wallets/usbwallet/ledger.go create mode 100644 wallets/usbwallet/wallet.go diff --git a/go.mod b/go.mod index 5ba46dc..80abbf6 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,15 @@ require ( cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d github.com/cometbft/cometbft v0.37.9 github.com/cosmos/cosmos-sdk v0.47.12 + github.com/cosmos/gogoproto v1.4.10 + github.com/cosmos/ibc-go/v7 v7.6.0 github.com/ethereum/go-ethereum v1.11.5 github.com/evmos/evmos/v19 v19.0.0-20240731212153-b36241652b57 github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.3 github.com/tidwall/sjson v1.2.5 + github.com/zondax/hid v0.9.2 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/text v0.16.0 ) @@ -56,9 +60,7 @@ require ( github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/gogoproto v1.4.10 // indirect github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583 // indirect - github.com/cosmos/ibc-go/v7 v7.6.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect @@ -171,6 +173,7 @@ require ( github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect @@ -183,7 +186,6 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/zbiljic/go-filelock v0.0.0-20170914061330-1dbf7103ab7d // indirect - github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect go.opencensus.io v0.24.0 // indirect @@ -194,7 +196,6 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sync v0.7.0 // indirect diff --git a/go.sum b/go.sum index 6f1d904..a4a9fc8 100644 --- a/go.sum +++ b/go.sum @@ -435,7 +435,6 @@ github.com/evmos/cosmos-sdk v0.47.12-evmos.2 h1:NODyhYKCqu8JNLeR6b6ff0+TS3KYdcBi github.com/evmos/cosmos-sdk v0.47.12-evmos.2/go.mod h1:ADjORYzUQqQv/FxDi0H0K5gW/rAk1CiDR3ZKsExfJV0= github.com/evmos/evmos/v19 v19.0.0-20240731212153-b36241652b57 h1:C+JOScyVYgoASWrdIBmCYPBRbOpQgvGMCc8URWdq+xQ= github.com/evmos/evmos/v19 v19.0.0-20240731212153-b36241652b57/go.mod h1:HEPvi70nAyQyzYaDqtB2x33lwQ80wKVIyTRNnufjTg8= -github.com/evmos/evmos/v19 v19.0.0/go.mod h1:0BtH6AsIRvAaNmSIfIYGH3AaXgWtq8ZBTdmYV08VZjE= github.com/evmos/go-ethereum v1.10.26-evmos-rc4 h1:vwDVMScuB2KSu8ze5oWUuxm6v3bMUp6dL3PWvJNJY+I= github.com/evmos/go-ethereum v1.10.26-evmos-rc4/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/wallets/accounts/accounts.go b/wallets/accounts/accounts.go new file mode 100644 index 0000000..4fac98d --- /dev/null +++ b/wallets/accounts/accounts.go @@ -0,0 +1,80 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package accounts + +import ( + "crypto/ecdsa" + + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/signer/core/apitypes" +) + +// Account represents an Ethereum account located at a specific location defined +// by the optional URL field. +type Account struct { + Address common.Address `json:"address"` // Ethereum account address derived from the key + PublicKey *ecdsa.PublicKey `json:"publicKey"` // Public key corresponding to the account address +} + +// Wallet represents a software or hardware wallet that might contain one or more +// accounts (derived from the same seed). +type Wallet interface { + // URL retrieves the canonical path under which this wallet is reachable. It is + // used by upper layers to define a sorting order over all wallets from multiple + // backends. + URL() gethaccounts.URL + + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. It is not meant to unlock or + // decrypt account keys, rather simply to establish a connection to hardware + // wallets and/or to access derivation seeds. + // + // The passphrase parameter may or may not be used by the implementation of a + // particular wallet instance. The reason there is no password-less open method + // is to strive towards a uniform wallet handling, oblivious to the different + // backend providers. + // + // Please note, if you open a wallet, you must close it to release any allocated + // resources (especially important when working with hardware wallets). + Open(passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Accounts retrieves the list of signing accounts the wallet is currently aware + // of. For hierarchical deterministic wallets, the list will not be exhaustive, + // rather only contain the accounts explicitly pinned during account derivation. + Accounts() []Account + + // Contains returns whether an account is part of this particular wallet or not. + Contains(account Account) bool + + // Derive attempts to explicitly derive a hierarchical deterministic account at + // the specified derivation path. If requested, the derived account will be added + // to the wallet's tracked account list. + Derive(path gethaccounts.DerivationPath, pin bool) (Account, error) + + // SignTypedData signs a TypedData object using EIP-712 encoding + SignTypedData(account Account, typedData apitypes.TypedData) ([]byte, error) +} + +// Backend is a "wallet provider" that may contain a batch of accounts they can +// sign transactions with and upon request, do so. +type Backend interface { + // Wallets retrieves the list of wallets the backend is currently aware of. + // + // The returned wallets are not opened by default. For software HD wallets this + // means that no base seeds are decrypted, and for hardware wallets that no actual + // connection is established. + // + // The resulting wallet list will be sorted alphabetically based on its internal + // URL assigned by the backend. Since wallets (especially hardware) may come and + // go, the same wallet might appear at a different positions in the list during + // subsequent retrievals. + Wallets() []Wallet +} diff --git a/wallets/ledger/ledger.go b/wallets/ledger/ledger.go new file mode 100644 index 0000000..f84dacf --- /dev/null +++ b/wallets/ledger/ledger.go @@ -0,0 +1,190 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package ledger + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + + sdkledger "github.com/cosmos/cosmos-sdk/crypto/ledger" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + + "github.com/evmos/os/ethereum/eip712" + "github.com/evmos/os/wallets/accounts" + "github.com/evmos/os/wallets/usbwallet" +) + +// Secp256k1DerivationFn defines the derivation function used on the Cosmos SDK Keyring. +type Secp256k1DerivationFn func() (sdkledger.SECP256K1, error) + +func EvmosLedgerDerivation() Secp256k1DerivationFn { + evmosSECP256K1 := new(EvmosSECP256K1) + + return func() (sdkledger.SECP256K1, error) { + return evmosSECP256K1.connectToLedgerApp() + } +} + +var _ sdkledger.SECP256K1 = &EvmosSECP256K1{} + +// EvmosSECP256K1 defines a wrapper of the Ethereum App to +// for compatibility with Cosmos SDK chains. +type EvmosSECP256K1 struct { + *usbwallet.Hub + PrimaryWallet accounts.Wallet +} + +// Close closes the associated primary wallet. Any requests on +// the object after a successful Close() should not work +func (e EvmosSECP256K1) Close() error { + if e.PrimaryWallet == nil { + return errors.New("could not close Ledger: no wallet found") + } + + return e.PrimaryWallet.Close() +} + +// GetPublicKeySECP256K1 returns the public key associated with the address derived from +// the provided hdPath using the primary wallet +func (e EvmosSECP256K1) GetPublicKeySECP256K1(hdPath []uint32) ([]byte, error) { + if e.PrimaryWallet == nil { + return nil, errors.New("could not get Ledger public key: no wallet found") + } + + // Re-open wallet in case it was closed. Do not handle the error here (see SignSECP256K1) + _ = e.PrimaryWallet.Open("") + + account, err := e.PrimaryWallet.Derive(hdPath, true) + if err != nil { + return nil, errors.New("unable to derive public key, please retry") + } + + pubkeyBz := crypto.FromECDSAPub(account.PublicKey) + + return pubkeyBz, nil +} + +// GetAddressPubKeySECP256K1 takes in the HD path as well as a "Human Readable Prefix" (HRP, e.g. "evmos") +// to return the public key bytes in secp256k1 format as well as the account address. +func (e EvmosSECP256K1) GetAddressPubKeySECP256K1(hdPath []uint32, hrp string) ([]byte, string, error) { + if e.PrimaryWallet == nil { + return nil, "", errors.New("could not get Ledger address: no wallet found") + } + + // Re-open wallet in case it was closed. Ignore the error here (see SignSECP256K1) + _ = e.PrimaryWallet.Open("") + + account, err := e.PrimaryWallet.Derive(hdPath, true) + if err != nil { + return nil, "", errors.New("unable to derive Ledger address, please open the Ethereum app and retry") + } + + address, err := sdk.Bech32ifyAddressBytes(hrp, account.Address.Bytes()) + if err != nil { + return nil, "", err + } + + pubkeyBz := crypto.FromECDSAPub(account.PublicKey) + + return pubkeyBz, address, nil +} + +// SignSECP256K1 returns the signature bytes generated from signing a transaction +// using the EIP712 signature. +func (e EvmosSECP256K1) SignSECP256K1(hdPath []uint32, signDocBytes []byte) ([]byte, error) { + fmt.Printf("Generating payload, please check your Ledger...\n") + + if e.PrimaryWallet == nil { + return nil, errors.New("unable to sign with Ledger: no wallet found") + } + + // Re-open wallet in case it was closed. Since an error occurs if the wallet is already open, + // ignore the error. Any errors due to the wallet being closed will surface later on. + _ = e.PrimaryWallet.Open("") + + // Derive requested account + account, err := e.PrimaryWallet.Derive(hdPath, true) + if err != nil { + return nil, errors.New("unable to derive Ledger address, please open the Ethereum app and retry") + } + + typedData, err := eip712.GetEIP712TypedDataForMsg(signDocBytes) + if err != nil { + return nil, err + } + + // Display EIP-712 message hash for user to verify + if err := e.displayEIP712Hash(typedData); err != nil { + return nil, fmt.Errorf("unable to generate EIP-712 hash for object: %w", err) + } + + // Sign with EIP712 signature + signature, err := e.PrimaryWallet.SignTypedData(account, typedData) + if err != nil { + return nil, fmt.Errorf("error generating signature, please retry: %w", err) + } + + return signature, nil +} + +// displayEIP712Hash is a helper function to display the EIP-712 hashes. +// This allows users to verify the hashed message they are signing via Ledger. +func (e EvmosSECP256K1) displayEIP712Hash(typedData apitypes.TypedData) error { + domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) + if err != nil { + return err + } + typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) + if err != nil { + return err + } + + fmt.Printf("Signing the following payload with EIP-712:\n") + fmt.Printf("- Domain: %s\n", bytesToHexString(domainSeparator)) + fmt.Printf("- Message: %s\n", bytesToHexString(typedDataHash)) + + return nil +} + +func (e *EvmosSECP256K1) connectToLedgerApp() (sdkledger.SECP256K1, error) { + // Instantiate new Ledger object + ledger, err := usbwallet.NewLedgerHub() + if err != nil { + return nil, err + } + + if ledger == nil { + return nil, errors.New("no hardware wallets detected") + } + + e.Hub = ledger + wallets := e.Wallets() + + // No wallets detected; throw an error + if len(wallets) == 0 { + return nil, errors.New("no hardware wallets detected") + } + + // Default to use first wallet found + primaryWallet := wallets[0] + + // Open wallet for the first time. Unlike with other cases, we want to handle the error here. + if err := primaryWallet.Open(""); err != nil { + return nil, err + } + + e.PrimaryWallet = primaryWallet + + return e, nil +} + +// bytesToHexString is a helper function to convert a slice of bytes to a +// string in hex-format. +func bytesToHexString(bytes []byte) string { + return "0x" + strings.ToUpper(hex.EncodeToString(bytes)) +} diff --git a/wallets/ledger/ledger_suite_test.go b/wallets/ledger/ledger_suite_test.go new file mode 100644 index 0000000..123bf75 --- /dev/null +++ b/wallets/ledger/ledger_suite_test.go @@ -0,0 +1,155 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package ledger_test + +import ( + "encoding/hex" + "regexp" + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + codecTypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + cryptoTypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + txTypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + auxTx "github.com/cosmos/cosmos-sdk/x/auth/tx" + bankTypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/evmos/os/wallets/ledger" + "github.com/evmos/os/wallets/ledger/mocks" + "github.com/evmos/os/wallets/usbwallet" +) + +type LedgerTestSuite struct { + suite.Suite + txAmino []byte + txProtobuf []byte + ledger ledger.EvmosSECP256K1 + mockWallet *mocks.Wallet + hrp string +} + +func TestLedgerTestSuite(t *testing.T) { + suite.Run(t, new(LedgerTestSuite)) +} + +func (suite *LedgerTestSuite) SetupTest() { + suite.hrp = "evmos" + + suite.txAmino = suite.getMockTxAmino() + suite.txProtobuf = suite.getMockTxProtobuf() + + hub, err := usbwallet.NewLedgerHub() + suite.Require().NoError(err) + + mockWallet := new(mocks.Wallet) + suite.mockWallet = mockWallet + suite.ledger = ledger.EvmosSECP256K1{Hub: hub, PrimaryWallet: mockWallet} +} + +func (suite *LedgerTestSuite) newPubKey(pk string) (res cryptoTypes.PubKey) { + pkBytes, err := hex.DecodeString(pk) + suite.Require().NoError(err) + + pubkey := &ed25519.PubKey{Key: pkBytes} + + return pubkey +} + +func (suite *LedgerTestSuite) getMockTxAmino() []byte { + whitespaceRegex := regexp.MustCompile(`\s+`) + tmp := whitespaceRegex.ReplaceAllString( + `{ + "account_number": "0", + "chain_id":"evmos_9000-1", + "fee":{ + "amount":[{"amount":"150","denom":"atom"}], + "gas":"20000" + }, + "memo":"memo", + "msgs":[{ + "type":"cosmos-sdk/MsgSend", + "value":{ + "amount":[{"amount":"150","denom":"atom"}], + "from_address":"cosmos1r5sckdd808qvg7p8d0auaw896zcluqfd7djffp", + "to_address":"cosmos10t8ca2w09ykd6ph0agdz5stvgau47whhaggl9a" + } + }], + "sequence":"6" + }`, + "", + ) + + return []byte(tmp) +} + +func (suite *LedgerTestSuite) getMockTxProtobuf() []byte { + marshaler := codec.NewProtoCodec(codecTypes.NewInterfaceRegistry()) + + memo := "memo" + msg := bankTypes.NewMsgSend( + sdk.MustAccAddressFromBech32("cosmos1r5sckdd808qvg7p8d0auaw896zcluqfd7djffp"), + sdk.MustAccAddressFromBech32("cosmos10t8ca2w09ykd6ph0agdz5stvgau47whhaggl9a"), + []sdk.Coin{ + { + Denom: "atom", + Amount: math.NewIntFromUint64(150), + }, + }, + ) + + msgAsAny, err := codecTypes.NewAnyWithValue(msg) + suite.Require().NoError(err) + + body := &txTypes.TxBody{ + Messages: []*codecTypes.Any{ + msgAsAny, + }, + Memo: memo, + } + + pubKey := suite.newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50") + + pubKeyAsAny, err := codecTypes.NewAnyWithValue(pubKey) + suite.Require().NoError(err) + + signingMode := txTypes.ModeInfo_Single_{ + Single: &txTypes.ModeInfo_Single{ + Mode: signing.SignMode_SIGN_MODE_DIRECT, + }, + } + + signerInfo := &txTypes.SignerInfo{ + PublicKey: pubKeyAsAny, + ModeInfo: &txTypes.ModeInfo{ + Sum: &signingMode, + }, + Sequence: 6, + } + + fee := txTypes.Fee{Amount: sdk.NewCoins(sdk.NewInt64Coin("atom", 150)), GasLimit: 20000} + + authInfo := &txTypes.AuthInfo{ + SignerInfos: []*txTypes.SignerInfo{signerInfo}, + Fee: &fee, + } + + bodyBytes := marshaler.MustMarshal(body) + authInfoBytes := marshaler.MustMarshal(authInfo) + + signBytes, err := auxTx.DirectSignBytes( + bodyBytes, + authInfoBytes, + "evmos_9000-1", + 0, + ) + suite.Require().NoError(err) + + return signBytes +} diff --git a/wallets/ledger/ledger_test.go b/wallets/ledger/ledger_test.go new file mode 100644 index 0000000..957214a --- /dev/null +++ b/wallets/ledger/ledger_test.go @@ -0,0 +1,326 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package ledger_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/evmos/v19/app" + "github.com/evmos/evmos/v19/encoding" + "github.com/evmos/os/ethereum/eip712" + "github.com/evmos/os/wallets/accounts" + "github.com/evmos/os/wallets/ledger" +) + +// Test Mnemonic: +// glow spread dentist swamp people siren hint muscle first sausage castle metal cycle abandon accident logic again around mix dial knee organ episode usual + +// Load encoding config for sign doc encoding/decoding +func init() { + config := encoding.MakeConfig(app.ModuleBasics) + eip712.SetEncodingConfig(config) + sdk.GetConfig().SetBech32PrefixForAccount("cosmos", "") +} + +func (suite *LedgerTestSuite) TestEvmosLedgerDerivation() { + testCases := []struct { + name string + mockFunc func() + expPass bool + }{ + { + "fail - no hardware wallets detected", + func() {}, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + derivationFunc := ledger.EvmosLedgerDerivation() + _, err := derivationFunc() + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *LedgerTestSuite) TestClose() { + testCases := []struct { + name string + mockFunc func() + expPass bool + }{ + { + "fail - can't find Ledger device", + func() { + suite.ledger.PrimaryWallet = nil + }, + false, + }, + { + "pass - wallet closed successfully", + func() { + RegisterClose(suite.mockWallet) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.mockFunc() + err := suite.ledger.Close() + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *LedgerTestSuite) TestSignatures() { + privKey, err := crypto.GenerateKey() + suite.Require().NoError(err) + addr := crypto.PubkeyToAddress(privKey.PublicKey) + account := accounts.Account{ + Address: addr, + PublicKey: &privKey.PublicKey, + } + + testCases := []struct { + name string + tx []byte + mockFunc func() + expPass bool + }{ + { + "fail - can't find Ledger device", + suite.txAmino, + func() { + suite.ledger.PrimaryWallet = nil + }, + false, + }, + { + "fail - unable to derive Ledger address", + suite.txAmino, + func() { + RegisterOpen(suite.mockWallet) + RegisterDeriveError(suite.mockWallet) + }, + false, + }, + { + "fail - error generating signature", + suite.txAmino, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + RegisterSignTypedDataError(suite.mockWallet, account, suite.txAmino) + }, + false, + }, + { + "pass - test ledger amino signature", + suite.txAmino, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + RegisterSignTypedData(suite.mockWallet, account, suite.txAmino) + }, + true, + }, + { + "pass - test ledger protobuf signature", + suite.txProtobuf, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + RegisterSignTypedData(suite.mockWallet, account, suite.txProtobuf) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.mockFunc() + _, err := suite.ledger.SignSECP256K1(gethaccounts.DefaultBaseDerivationPath, tc.tx) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *LedgerTestSuite) TestSignatureEquivalence() { + privKey, err := crypto.GenerateKey() + suite.Require().NoError(err) + addr := crypto.PubkeyToAddress(privKey.PublicKey) + account := accounts.Account{ + Address: addr, + PublicKey: &privKey.PublicKey, + } + + testCases := []struct { + name string + txProtobuf []byte + txAmino []byte + mockFunc func() + expPass bool + }{ + { + "pass - signatures are equivalent", + suite.txProtobuf, + suite.txAmino, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + RegisterSignTypedData(suite.mockWallet, account, suite.txProtobuf) + RegisterSignTypedData(suite.mockWallet, account, suite.txAmino) + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.mockFunc() + protoSignature, err := suite.ledger.SignSECP256K1(gethaccounts.DefaultBaseDerivationPath, tc.txProtobuf) + suite.Require().NoError(err) + aminoSignature, err := suite.ledger.SignSECP256K1(gethaccounts.DefaultBaseDerivationPath, tc.txAmino) + suite.Require().NoError(err) + if tc.expPass { + suite.Require().Equal(protoSignature, aminoSignature) + } else { + suite.Require().NotEqual(protoSignature, aminoSignature) + } + }) + } +} + +func (suite *LedgerTestSuite) TestGetAddressPubKeySECP256K1() { + privKey, err := crypto.GenerateKey() + suite.Require().NoError(err) + + addr := crypto.PubkeyToAddress(privKey.PublicKey) + expAddr, err := sdk.Bech32ifyAddressBytes("evmos", common.HexToAddress(addr.String()).Bytes()) + suite.Require().NoError(err) + + testCases := []struct { + name string + expPass bool + mockFunc func() + }{ + { + "fail - can't find Ledger device", + false, + func() { + suite.ledger.PrimaryWallet = nil + }, + }, + { + "fail - unable to derive Ledger address", + false, + func() { + RegisterOpen(suite.mockWallet) + RegisterDeriveError(suite.mockWallet) + }, + }, + { + "fail - bech32 prefix empty", + false, + func() { + suite.hrp = "" + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + }, + }, + { + "pass - get ledger address", + true, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.mockFunc() + _, addr, err := suite.ledger.GetAddressPubKeySECP256K1(gethaccounts.DefaultBaseDerivationPath, suite.hrp) + if tc.expPass { + suite.Require().NoError(err, "Could not get wallet address") + suite.Require().Equal(expAddr, addr) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *LedgerTestSuite) TestGetPublicKeySECP256K1() { + privKey, err := crypto.GenerateKey() + suite.Require().NoError(err) + addr := crypto.PubkeyToAddress(privKey.PublicKey) + expPubkeyBz := crypto.FromECDSAPub(&privKey.PublicKey) + testCases := []struct { + name string + expPass bool + mockFunc func() + }{ + { + "fail - can't find Ledger device", + false, + func() { + suite.ledger.PrimaryWallet = nil + }, + }, + { + "fail - unable to derive Ledger address", + false, + func() { + RegisterOpen(suite.mockWallet) + RegisterDeriveError(suite.mockWallet) + }, + }, + { + "pass - get ledger public key", + true, + func() { + RegisterOpen(suite.mockWallet) + RegisterDerive(suite.mockWallet, addr, &privKey.PublicKey) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.mockFunc() + pubKeyBz, err := suite.ledger.GetPublicKeySECP256K1(gethaccounts.DefaultBaseDerivationPath) + if tc.expPass { + suite.Require().NoError(err, "Could not get wallet address") + suite.Require().Equal(expPubkeyBz, pubKeyBz) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/wallets/ledger/mocks/wallet.go b/wallets/ledger/mocks/wallet.go new file mode 100644 index 0000000..9454cf3 --- /dev/null +++ b/wallets/ledger/mocks/wallet.go @@ -0,0 +1,198 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +// Code generated by mockery v2.14.1. DO NOT EDIT. + +package mocks + +import ( + apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes" + accounts "github.com/evmos/os/wallets/accounts" + + big "math/big" + + go_ethereumaccounts "github.com/ethereum/go-ethereum/accounts" + + mock "github.com/stretchr/testify/mock" + + types "github.com/ethereum/go-ethereum/core/types" +) + +// Wallet is an autogenerated mock type for the Wallet type +type Wallet struct { + mock.Mock +} + +// Accounts provides a mock function with given fields: +func (_m *Wallet) Accounts() []accounts.Account { + ret := _m.Called() + + var r0 []accounts.Account + if rf, ok := ret.Get(0).(func() []accounts.Account); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]accounts.Account) + } + } + + return r0 +} + +// Close provides a mock function with given fields: +func (_m *Wallet) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Contains provides a mock function with given fields: account +func (_m *Wallet) Contains(account accounts.Account) bool { + ret := _m.Called(account) + + var r0 bool + if rf, ok := ret.Get(0).(func(accounts.Account) bool); ok { + r0 = rf(account) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Derive provides a mock function with given fields: path, pin +func (_m *Wallet) Derive(path go_ethereumaccounts.DerivationPath, pin bool) (accounts.Account, error) { + ret := _m.Called(path, pin) + + var r0 accounts.Account + if rf, ok := ret.Get(0).(func(go_ethereumaccounts.DerivationPath, bool) accounts.Account); ok { + r0 = rf(path, pin) + } else { + r0 = ret.Get(0).(accounts.Account) + } + + var r1 error + if rf, ok := ret.Get(1).(func(go_ethereumaccounts.DerivationPath, bool) error); ok { + r1 = rf(path, pin) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Open provides a mock function with given fields: passphrase +func (_m *Wallet) Open(passphrase string) error { + ret := _m.Called(passphrase) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(passphrase) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SignTx provides a mock function with given fields: account, tx, chainID +func (_m *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) ([]byte, error) { + ret := _m.Called(account, tx, chainID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(accounts.Account, *types.Transaction, *big.Int) []byte); ok { + r0 = rf(account, tx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(accounts.Account, *types.Transaction, *big.Int) error); ok { + r1 = rf(account, tx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SignTypedData provides a mock function with given fields: account, typedData +func (_m *Wallet) SignTypedData(account accounts.Account, typedData apitypes.TypedData) ([]byte, error) { + ret := _m.Called(account, typedData) + + var r0 []byte + if rf, ok := ret.Get(0).(func(accounts.Account, apitypes.TypedData) []byte); ok { + r0 = rf(account, typedData) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(accounts.Account, apitypes.TypedData) error); ok { + r1 = rf(account, typedData) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Status provides a mock function with given fields: +func (_m *Wallet) Status() (string, error) { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// URL provides a mock function with given fields: +func (_m *Wallet) URL() go_ethereumaccounts.URL { + ret := _m.Called() + + var r0 go_ethereumaccounts.URL + if rf, ok := ret.Get(0).(func() go_ethereumaccounts.URL); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(go_ethereumaccounts.URL) + } + + return r0 +} + +type mockConstructorTestingTNewWallet interface { + mock.TestingT + Cleanup(func()) +} + +// NewWallet creates a new instance of Wallet. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewWallet(t mockConstructorTestingTNewWallet) *Wallet { + mock := &Wallet{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/wallets/ledger/wallet_test.go b/wallets/ledger/wallet_test.go new file mode 100644 index 0000000..d95acac --- /dev/null +++ b/wallets/ledger/wallet_test.go @@ -0,0 +1,47 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package ledger_test + +import ( + "crypto/ecdsa" + "errors" + + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/evmos/os/ethereum/eip712" + "github.com/evmos/os/wallets/accounts" + "github.com/evmos/os/wallets/ledger/mocks" +) + +func RegisterDerive(mockWallet *mocks.Wallet, addr common.Address, publicKey *ecdsa.PublicKey) { + mockWallet.On("Derive", gethaccounts.DefaultBaseDerivationPath, true). + Return(accounts.Account{Address: addr, PublicKey: publicKey}, nil) +} + +func RegisterDeriveError(mockWallet *mocks.Wallet) { + mockWallet.On("Derive", gethaccounts.DefaultBaseDerivationPath, true). + Return(accounts.Account{}, errors.New("unable to derive Ledger address, please open the Ethereum app and retry")) +} + +func RegisterOpen(mockWallet *mocks.Wallet) { + mockWallet.On("Open", ""). + Return(nil) +} + +func RegisterClose(mockWallet *mocks.Wallet) { + mockWallet.On("Close"). + Return(nil) +} + +func RegisterSignTypedData(mockWallet *mocks.Wallet, account accounts.Account, typedDataBz []byte) { + typedData, _ := eip712.GetEIP712TypedDataForMsg(typedDataBz) + mockWallet.On("SignTypedData", account, typedData). + Return([]byte{}, nil) +} + +func RegisterSignTypedDataError(mockWallet *mocks.Wallet, account accounts.Account, typedDataBz []byte) { + typedData, _ := eip712.GetEIP712TypedDataForMsg(typedDataBz) + mockWallet.On("SignTypedData", account, typedData). + Return([]byte{}, errors.New("error generating signature, please retry")) +} diff --git a/wallets/usbwallet/hub.go b/wallets/usbwallet/hub.go new file mode 100644 index 0000000..1aabe20 --- /dev/null +++ b/wallets/usbwallet/hub.go @@ -0,0 +1,219 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package usbwallet + +import ( + "errors" + "sync" + "sync/atomic" + "time" + + // runtime is listed as a potential source for non-determinism, but we use it only for checking the OS + // #nosec + "runtime" + + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/evmos/os/wallets/accounts" + usb "github.com/zondax/hid" +) + +const ( + // LedgerScheme is the protocol scheme prefixing account and wallet URLs. + LedgerScheme = "ledger" + + // onLinux is a boolean value to check if the operating system is Linux-based. + onLinux = runtime.GOOS == "linux" + + // refreshThrottling is the minimum time between wallet refreshes to avoid USB + // trashing. + refreshThrottling = 500 * time.Millisecond +) + +var _ accounts.Backend = &Hub{} + +// Hub is an accounts.Backend that can find and handle generic USB hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + vendorID uint16 // USB vendor identifier used for device discovery + productIDs []uint16 // USB product identifiers used for device discovery + usageID uint16 // USB usage page identifier used for macOS device discovery + endpointID int // USB endpoint identifier used for non-macOS device discovery + makeDriver func() driver // Factory method to construct a vendor specific driver + + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets []accounts.Wallet // List of USB wallet devices currently tracking + + quit chan chan error + + stateLock sync.RWMutex // Protects the internals of the hub from racey access + + // TODO(karalabe): remove if hotplug lands on Windows + commsPend int // Number of operations blocking enumeration + commsLock sync.Mutex // Lock protecting the pending counter and enumeration + enumFails uint32 // Number of times enumeration has failed +} + +// NewLedgerHub creates a new hardware wallet manager for Ledger devices. +func NewLedgerHub() (*Hub, error) { + return newHub(LedgerScheme, 0x2c97, []uint16{ + // Device definitions taken from + // https://github.com/LedgerHQ/ledger-live/blob/38012bc8899e0f07149ea9cfe7e64b2c146bc92b/libs/ledgerjs/packages/devices/src/index.ts + + // Original product IDs + 0x0000, /* Ledger Blue */ + 0x0001, /* Ledger Nano S */ + 0x0004, /* Ledger Nano X */ + 0x0005, /* Ledger Nano S Plus */ + 0x0006, /* Ledger Nano FTS */ + + 0x0015, /* HID + U2F + WebUSB Ledger Blue */ + 0x1015, /* HID + U2F + WebUSB Ledger Nano S */ + 0x4015, /* HID + U2F + WebUSB Ledger Nano X */ + 0x5015, /* HID + U2F + WebUSB Ledger Nano S Plus */ + 0x6015, /* HID + U2F + WebUSB Ledger Nano FTS */ + + 0x0011, /* HID + WebUSB Ledger Blue */ + 0x1011, /* HID + WebUSB Ledger Nano S */ + 0x4011, /* HID + WebUSB Ledger Nano X */ + 0x5011, /* HID + WebUSB Ledger Nano S Plus */ + 0x6011, /* HID + WebUSB Ledger Nano FTS */ + }, 0xffa0, 0, newLedgerDriver) +} + +// newHub creates a new hardware wallet manager for generic USB devices. +func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func() driver) (*Hub, error) { + if !usb.Supported() { + return nil, errors.New("unsupported platform") + } + hub := &Hub{ + scheme: scheme, + vendorID: vendorID, + productIDs: productIDs, + usageID: usageID, + endpointID: endpointID, + makeDriver: makeDriver, + quit: make(chan chan error), + } + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked USB +// devices that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up-to-date + hub.refreshWallets() + + hub.stateLock.RLock() + defer hub.stateLock.RUnlock() + + cpy := make([]accounts.Wallet, len(hub.wallets)) + copy(cpy, hub.wallets) + return cpy +} + +// refreshWallets scans the USB devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + // Don't scan the USB like crazy it the user fetches wallets in a loop + hub.stateLock.RLock() + elapsed := time.Since(hub.refreshed) + hub.stateLock.RUnlock() + + if elapsed < refreshThrottling { + return + } + + // If USB enumeration is continually failing, don't keep trying indefinitely + if atomic.LoadUint32(&hub.enumFails) > 2 { + return + } + + // Retrieve the current list of USB wallet devices + var devices []usb.DeviceInfo + + if onLinux { + // hidapi on Linux opens the device during enumeration to retrieve some infos, + // breaking the Ledger protocol if that is waiting for user confirmation. This + // is a bug acknowledged at Ledger, but it won't be fixed on old devices, so we + // need to prevent concurrent comms ourselves. The more elegant solution would + // be to ditch enumeration in favor of hotplug events, but that don't work yet + // on Windows so if we need to hack it anyway, this is more elegant for now. + hub.commsLock.Lock() + if hub.commsPend > 0 { // A confirmation is pending, don't refresh + hub.commsLock.Unlock() + return + } + } + infos := usb.Enumerate(hub.vendorID, 0) + if infos == nil { + if onLinux { + // See rationale before the enumeration why this is needed and only on Linux. + hub.commsLock.Unlock() + } + return + } + atomic.StoreUint32(&hub.enumFails, 0) + + for _, info := range infos { + for _, id := range hub.productIDs { + // Windows and macOS use UsageID matching, Linux uses Interface matching + if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) { + devices = append(devices, info) + break + } + } + } + + if onLinux { + // See rationale before the enumeration why this is needed and only on Linux. + hub.commsLock.Unlock() + } + + // Transform the current list of wallets into the new one + hub.stateLock.Lock() + + wallets := make([]accounts.Wallet, 0, len(devices)) + + for _, device := range devices { + url := gethaccounts.URL{ + Scheme: hub.scheme, + Path: device.Path, + } + + // Drop wallets in front of the next device or those that failed for some reason + for len(hub.wallets) > 0 { + // Abort if we're past the current device and found an operational one + _, err := hub.wallets[0].Status() + if hub.wallets[0].URL().Cmp(url) >= 0 || err == nil { + break + } + // Drop the stale and failed devices + hub.wallets = hub.wallets[1:] + } + + // If there are no more wallets or the device is before the next, wrap new wallet + if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { + wallet := &wallet{ + hub: hub, + driver: hub.makeDriver(), + url: &url, + info: device, + } + + wallets = append(wallets, wallet) + continue + } + // If the device is the same as the first wallet, keep it + if hub.wallets[0].URL().Cmp(url) == 0 { + wallets = append(wallets, hub.wallets[0]) + hub.wallets = hub.wallets[1:] + continue + } + } + + hub.refreshed = time.Now().UTC() + hub.wallets = wallets + hub.stateLock.Unlock() +} diff --git a/wallets/usbwallet/ledger.go b/wallets/usbwallet/ledger.go new file mode 100644 index 0000000..aa80751 --- /dev/null +++ b/wallets/usbwallet/ledger.go @@ -0,0 +1,433 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package usbwallet + +import ( + "crypto/ecdsa" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// ledgerOpcode is an enumeration encoding the supported Ledger opcodes. +type ledgerOpcode byte + +// ledgerParam1 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam1 byte + +// ledgerParam2 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam2 byte + +const ( + ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path + ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration + ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification + + ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet + ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data + ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address +) + +// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange +// if the device replies with a mismatching header. This usually means the device +// is in browser mode. +var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header") + +// errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval +// when a response does arrive, but it does not contain the expected data. +var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") + +// ledgerDriver implements the communication with a Ledger hardware wallet. +type ledgerDriver struct { + device io.ReadWriter // USB device connection to communicate through + version [3]byte // Current version of the Ledger firmware (zero if app is offline) + browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch) + failure error // Any failure that would make the device unusable +} + +// newLedgerDriver creates a new instance of a Ledger USB protocol driver. +func newLedgerDriver() driver { + return &ledgerDriver{} +} + +// Status implements usbwallet.driver, returning various states the Ledger can +// currently be in. +func (w *ledgerDriver) Status() (string, error) { + if w.failure != nil { + return fmt.Sprintf("Failed: %v", w.failure), w.failure + } + if w.browser { + return "Ethereum app in browser mode", w.failure + } + if w.offline() { + return "Ethereum app offline", w.failure + } + return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure +} + +// offline returns whether the wallet and the Ethereum app is offline or not. +// +// The method assumes that the state lock is held! +func (w *ledgerDriver) offline() bool { + return w.version == [3]byte{0, 0, 0} +} + +// Open implements usbwallet.driver, attempting to initialize the connection to the +// Ledger hardware wallet. The Ledger does not require a user passphrase, so that +// parameter is silently discarded. +func (w *ledgerDriver) Open(device io.ReadWriter, _ string) error { + w.device, w.failure = device, nil + + _, _, err := w.ledgerDerive(gethaccounts.DefaultBaseDerivationPath) + if err != nil { + // Ethereum app is not running or in browser mode, nothing more to do, return + if err == errLedgerReplyInvalidHeader { + w.browser = true + } + return nil + } + // Try to resolve the Ethereum app's version, will fail prior to v1.0.2 + version, err := w.ledgerVersion() + if err == nil { + w.version = version + } else { + w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 + } + return nil +} + +// Close implements usbwallet.driver, cleaning up and metadata maintained within +// the Ledger driver. +func (w *ledgerDriver) Close() error { + w.browser, w.version = false, [3]byte{} + return nil +} + +// Heartbeat implements usbwallet.driver, performing a sanity check against the +// Ledger to see if it's still online. +func (w *ledgerDriver) Heartbeat() error { + if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { + w.failure = err + return err + } + return nil +} + +// Derive implements usbwallet.driver, sending a derivation request to the Ledger +// and returning the Ethereum address located on that derivation path. +func (w *ledgerDriver) Derive(path gethaccounts.DerivationPath) (common.Address, *ecdsa.PublicKey, error) { + return w.ledgerDerive(path) +} + +// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and +// waiting for the user to sign or deny the transaction. +// +// Note: this was introduced in the ledger 1.5.0 firmware +func (w *ledgerDriver) SignTypedMessage(path gethaccounts.DerivationPath, domainHash, messageHash []byte) ([]byte, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return nil, gethaccounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if w.version[0] < 1 && w.version[1] < 5 { + //nolint:stylecheck // ST1005 requires error strings to be lowercase but Ledger as a brand name should start with a capital letter + return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSignTypedMessage(path, domainHash, messageHash) +} + +// ledgerVersion retrieves the current version of the Ethereum wallet app running +// on the Ledger wallet. +// +// The version retrieval protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+----+--- +// E0 | 06 | 00 | 00 | 00 | 04 +// +// With no input data, and the output data being: +// +// Description | Length +// ---------------------------------------------------+-------- +// Flags 01: arbitrary data signature enabled by user | 1 byte +// Application major version | 1 byte +// Application minor version | 1 byte +// Application patch version | 1 byte +func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) + if err != nil { + return [3]byte{}, err + } + if len(reply) != 4 { + return [3]byte{}, errLedgerInvalidVersionReply + } + // Cache the version for future reference + var version [3]byte + copy(version[:], reply[1:]) + return version, nil +} + +// ledgerDerive retrieves the currently active Ethereum address from a Ledger +// wallet at the specified derivation path. +// +// The address derivation protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+-----+--- +// E0 | 02 | 00 return address +// 01 display address and confirm before returning +// | 00: do not return the chain code +// | 01: return the chain code +// | var | 00 +// +// Where the input data is: +// +// Description | Length +// -------------------------------------------------+-------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// +// And the output data is: +// +// Description | Length +// ------------------------+------------------- +// Public Key length | 1 byte +// Uncompressed Public Key | arbitrary +// Ethereum address length | 1 byte +// Ethereum address | 40 bytes hex ascii +// Chain code if requested | 32 bytes +func (w *ledgerDriver) ledgerDerive(derivationPath gethaccounts.DerivationPath) (common.Address, *ecdsa.PublicKey, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) + if err != nil { + return common.Address{}, nil, err + } + + // Verify public key was returned + // #nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, nil, errors.New("reply lacks public key entry") + } + + // #nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + replyFirstByteAsInt := int(reply[0]) + + pubkeyBz := reply[1 : 1+replyFirstByteAsInt] + + publicKey, err := crypto.UnmarshalPubkey(pubkeyBz) + if err != nil { + return common.Address{}, nil, fmt.Errorf("failed to unmarshal public key: %w", err) + } + + // Discard pubkey after fetching + reply = reply[1+replyFirstByteAsInt:] + + // Extract the Ethereum hex address string + // #nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, nil, errors.New("reply lacks address entry") + } + + // Reset first byte after discarding pubkey from response + // #nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + replyFirstByteAsInt = int(reply[0]) + + hexStr := reply[1 : 1+replyFirstByteAsInt] + + // Decode the hex string into an Ethereum address and return + var address common.Address + if _, err = hex.Decode(address[:], hexStr); err != nil { + return common.Address{}, nil, err + } + + derivedAddr := crypto.PubkeyToAddress(*publicKey) + if derivedAddr != address { + return common.Address{}, nil, fmt.Errorf("address mismatch, expected %s, got %s", derivedAddr, address) + } + + return address, publicKey, nil +} + +// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+-----------------------------+-----+--- +// E0 | 0C | 00 | implementation version : 00 | variable | variable +// +// Where the input is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// domain hash | 32 bytes +// message hash | 32 bytes +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath gethaccounts.DerivationPath, domainHash, messageHash []byte) ([]byte, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the 712 message + var payload []byte + payload = append(payload, path...) + payload = append(payload, domainHash...) + payload = append(payload, messageHash...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTypedMessageData + reply []byte + err error + ) + + // Send the message over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) + if err != nil { + return nil, err + } + + // Extract the Ethereum signature and do a sanity validation + if len(reply) != crypto.SignatureLength { + return nil, errors.New("reply lacks signature") + } + + var signature []byte + signature = append(signature, reply[1:]...) + signature = append(signature, reply[0]) + + return signature, nil +} + +// ledgerExchange performs a data exchange with the Ledger wallet, sending it a +// message and retrieving the response. +// +// The common transport header is defined as follows: +// +// Description | Length +// --------------------------------------+---------- +// Communication channel ID (big endian) | 2 bytes +// Command tag | 1 byte +// Packet sequence index (big endian) | 2 bytes +// Payload | arbitrary +// +// The Communication channel ID allows commands multiplexing over the same +// physical link. It is not used for the time being, and should be set to 0101 +// to avoid compatibility issues with implementations ignoring a leading 00 byte. +// +// The Command tag describes the message content. Use TAG_APDU (0x05) for standard +// APDU payloads, or TAG_PING (0x02) for a simple link test. +// +// The Packet sequence index describes the current sequence for fragmented payloads. +// The first fragment index is 0x00. +// +// APDU Command payloads are encoded as follows: +// +// Description | Length +// ----------------------------------- +// APDU length (big endian) | 2 bytes +// APDU CLA | 1 byte +// APDU INS | 1 byte +// APDU P1 | 1 byte +// APDU P2 | 1 byte +// APDU length | 1 byte +// Optional APDU data | arbitrary +func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { + // Construct the message payload, possibly split into multiple chunks + apdu := make([]byte, 2, 7+len(data)) + + //#nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) + apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) + apdu = append(apdu, data...) + + // Stream all the chunks to the device + header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended + chunk := make([]byte, 64) + space := len(chunk) - len(header) + + for i := 0; len(apdu) > 0; i++ { + // Construct the new message to stream + chunk = append(chunk[:0], header...) + //#nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + binary.BigEndian.PutUint16(chunk[3:], uint16(i)) + + if len(apdu) > space { + chunk = append(chunk, apdu[:space]...) + apdu = apdu[space:] + } else { + chunk = append(chunk, apdu...) + apdu = nil + } + // Send over to the device + if _, err := w.device.Write(chunk); err != nil { + return nil, err + } + } + // Stream the reply back from the wallet in 64 byte chunks + var reply []byte + chunk = chunk[:64] // Yeah, we surely have enough space + for { + // Read the next chunk from the Ledger wallet + if _, err := io.ReadFull(w.device, chunk); err != nil { + return nil, err + } + + // Make sure the transport header matches + if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { + return nil, errLedgerReplyInvalidHeader + } + // If it's the first chunk, retrieve the total message length + var payload []byte + + if chunk[3] == 0x00 && chunk[4] == 0x00 { + //#nosec G701 -- gosec will raise a warning on this integer conversion for potential overflow + reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) + payload = chunk[7:] + } else { + payload = chunk[5:] + } + // Append to the reply and stop when filled up + if left := cap(reply) - len(reply); left > len(payload) { + reply = append(reply, payload...) + } else { + reply = append(reply, payload[:left]...) + break + } + } + return reply[:len(reply)-2], nil +} diff --git a/wallets/usbwallet/wallet.go b/wallets/usbwallet/wallet.go new file mode 100644 index 0000000..0e6f367 --- /dev/null +++ b/wallets/usbwallet/wallet.go @@ -0,0 +1,413 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package usbwallet + +import ( + "bytes" + "crypto/ecdsa" + "errors" + "fmt" + "io" + "sync" + "time" + + gethaccounts "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/evmos/os/wallets/accounts" + usb "github.com/zondax/hid" +) + +// Maximum time between wallet health checks to detect USB unplugs. +const heartbeatCycle = time.Second + +// driver defines the vendor specific functionality hardware wallets instances +// must implement to allow using them with the wallet lifecycle management. +type driver interface { + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. The passphrase parameter may + // or may not be used by the implementation of a particular wallet instance. + Open(device io.ReadWriter, passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Heartbeat performs a sanity check against the hardware wallet to see if it + // is still online and healthy. + Heartbeat() error + + // Derive sends a derivation request to the USB device and returns the Ethereum + // address located on that path. + Derive(path gethaccounts.DerivationPath) (common.Address, *ecdsa.PublicKey, error) + + // SignTypedMessage sends the message to the Ledger and waits for the user to sign + // or deny the transaction. + SignTypedMessage(path gethaccounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error) +} + +// wallet represents the common functionality shared by all USB hardware +// wallets to prevent reimplementing the same complex maintenance mechanisms +// for different vendors. +type wallet struct { + hub *Hub // USB hub scanning + driver driver // Hardware implementation of the low level device operations + url *gethaccounts.URL // Textual URL uniquely identifying this wallet + + info usb.DeviceInfo // Known USB device infos about the wallet + device *usb.Device // USB device advertising itself as a hardware wallet + + accounts []accounts.Account // List of derive accounts pinned on the hardware wallet + paths map[common.Address]gethaccounts.DerivationPath // Known derivation paths for signing operations + + healthQuit chan chan error + + // Locking a hardware wallet is a bit special. Since hardware devices are lower + // performing, any communication with them might take a non-negligible amount of + // time. Worse still, waiting for user confirmation can take arbitrarily long, + // but exclusive communication must be upheld during. Locking the entire wallet + // in the meantime however would stall any parts of the system that don't want + // to communicate, just read some state (e.g. list the accounts). + // + // As such, a hardware wallet needs two locks to function correctly. A state + // lock can be used to protect the wallet's software-side internal state, which + // must not be held exclusively during hardware communication. A communication + // lock can be used to achieve exclusive access to the device itself, this one + // however should allow "skipping" waiting for operations that might want to + // use the device, but can live without too (e.g. account self-derivation). + // + // Since we have two locks, it's important to know how to properly use them: + // - Communication requires the `device` to not change, so obtaining the + // commsLock should be done after having a stateLock. + // - Communication must not disable read access to the wallet state, so it + // must only ever hold a *read* lock to stateLock. + commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked + stateLock sync.RWMutex // Protects read and write access to the wallet struct fields +} + +// URL implements accounts.Wallet, returning the URL of the USB hardware device. +func (w *wallet) URL() gethaccounts.URL { + return *w.url // Immutable, no need for a lock +} + +// Status implements accounts.Wallet, returning a custom status message from the +// underlying vendor-specific hardware wallet implementation. +func (w *wallet) Status() (string, error) { + w.stateLock.RLock() // No device communication, state lock is enough + defer w.stateLock.RUnlock() + + status, failure := w.driver.Status() + if w.device == nil { + return "Closed", failure + } + return status, failure +} + +// Open implements accounts.Wallet, attempting to open a USB connection to the +// hardware wallet. +func (w *wallet) Open(passphrase string) error { + w.stateLock.Lock() // State lock is enough since there's no connection yet at this point + defer w.stateLock.Unlock() + + // If the device was already opened once, refuse to try again + if w.paths != nil { + return gethaccounts.ErrWalletAlreadyOpen + } + // Make sure the actual device connection is done only once + if w.device == nil { + device, err := w.info.Open() + if err != nil { + return err + } + w.device = device + w.commsLock = make(chan struct{}, 1) + w.commsLock <- struct{}{} // Enable lock + } + // Delegate device initialization to the underlying driver + if err := w.driver.Open(w.device, passphrase); err != nil { + return err + } + // Connection successful, start life-cycle management + w.paths = make(map[common.Address]gethaccounts.DerivationPath) + + w.healthQuit = make(chan chan error) + + go w.heartbeat() + + return nil +} + +// heartbeat is a health check loop for the USB wallets to periodically verify +// whether they are still present or if they malfunctioned. +func (w *wallet) heartbeat() { + // Execute heartbeat checks until termination or error + var ( + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until termination is requested or the heartbeat cycle arrives + select { + case errc = <-w.healthQuit: + // Termination requested + continue + case <-time.After(heartbeatCycle): + // Heartbeat time + } + // Execute a tiny data exchange to see responsiveness + w.stateLock.RLock() + if w.device == nil { + // Terminated while waiting for the lock + w.stateLock.RUnlock() + continue + } + <-w.commsLock // Don't lock state while resolving version + err = w.driver.Heartbeat() + w.commsLock <- struct{}{} + w.stateLock.RUnlock() + + if err != nil { + w.stateLock.Lock() // Lock state to tear the wallet down + //#nosec G703 -- ignoring the returned error on purpose here + _ = w.close() + w.stateLock.Unlock() + } + // Ignore non hardware related errors + err = nil + } + // In case of error, wait for termination + if err != nil { + errc = <-w.healthQuit + } + errc <- err +} + +// Close implements accounts.Wallet, closing the USB connection to the device. +func (w *wallet) Close() error { + // Ensure the wallet was opened + w.stateLock.RLock() + hQuit := w.healthQuit + w.stateLock.RUnlock() + + // Terminate the health checks + var herr error + if hQuit != nil { + errc := make(chan error) + hQuit <- errc + herr = <-errc // Save for later, we *must* close the USB + } + + // Terminate the device connection + w.stateLock.Lock() + defer w.stateLock.Unlock() + + w.healthQuit = nil + + if err := w.close(); err != nil { + return err + } + if herr != nil { + return herr + } + return nil +} + +// close is the internal wallet closer that terminates the USB connection and +// resets all the fields to their defaults. +// +// Note, close assumes the state lock is held! +func (w *wallet) close() error { + // Allow duplicate closes, especially for health-check failures + if w.device == nil { + return nil + } + // Close the device, clear everything, then return + //#nosec G703 -- ignoring the returned error on purpose here + _ = w.device.Close() + w.device = nil + + w.accounts, w.paths = nil, nil + return w.driver.Close() +} + +// Accounts implements accounts.Wallet, returning the list of accounts pinned to +// the USB hardware wallet. If self-derivation was enabled, the account list is +// periodically expanded based on current chain state. +func (w *wallet) Accounts() []accounts.Account { + // Return current account list + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + cpy := make([]accounts.Account, len(w.accounts)) + copy(cpy, w.accounts) + return cpy +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not pinned into this wallet instance. Although we could attempt to resolve +// unpinned accounts, that would be a non-negligible hardware operation. +func (w *wallet) Contains(account accounts.Account) bool { + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + _, exists := w.paths[account.Address] + return exists +} + +// Derive implements accounts.Wallet, deriving a new account at the specific +// derivation path. If pin is set to true, the account will be added to the list +// of tracked accounts. +func (w *wallet) Derive(path gethaccounts.DerivationPath, pin bool) (accounts.Account, error) { + formatPathIfNeeded(path) + + // Try to derive the actual account and update its URL if successful + w.stateLock.RLock() // Avoid device disappearing during derivation + + if w.device == nil { + w.stateLock.RUnlock() + return accounts.Account{}, gethaccounts.ErrWalletClosed + } + <-w.commsLock // Avoid concurrent hardware access + address, publicKey, err := w.driver.Derive(path) + w.commsLock <- struct{}{} + + w.stateLock.RUnlock() + + // If an error occurred or no pinning was requested, return + if err != nil { + return accounts.Account{}, err + } + + account := accounts.Account{ + Address: address, + PublicKey: publicKey, + } + if !pin { + return account, nil + } + // Pinning needs to modify the state + w.stateLock.Lock() + defer w.stateLock.Unlock() + + if _, ok := w.paths[address]; !ok { + w.accounts = append(w.accounts, account) + w.paths[address] = make(gethaccounts.DerivationPath, len(path)) + copy(w.paths[address], path) + } + return account, nil +} + +// Format the hd path to harden the first three values (purpose, coinType, account) +// if needed, modifying the array in-place. +func formatPathIfNeeded(path gethaccounts.DerivationPath) { + for i := 0; i < 3; i++ { + if path[i] < 0x80000000 { + path[i] += 0x80000000 + } + } +} + +// signHash implements accounts.Wallet, however signing arbitrary data is not +// supported for hardware wallets, so this method will always return an error. +func (w *wallet) signHash(_ accounts.Account, _ []byte) ([]byte, error) { + return nil, gethaccounts.ErrNotSupported +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *wallet) signData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + // Unless we are doing 712 signing, simply dispatch to signHash + if !(mimeType == gethaccounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) { + return w.signHash(account, crypto.Keccak256(data)) + } + + // dispatch to 712 signing if the mimetype is TypedData and the format matches + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, gethaccounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, gethaccounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction + signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66]) + if err != nil { + return nil, err + } + return signature, nil +} + +func (w *wallet) verifyTypedDataSignature(account accounts.Account, rawData []byte, signature []byte) error { + if len(signature) != crypto.SignatureLength { + return fmt.Errorf("invalid signature length: %d", len(signature)) + } + + // Copy signature as it would otherwise be modified + sigCopy := make([]byte, len(signature)) + copy(sigCopy, signature) + + // Subtract 27 to match ECDSA standard + sigCopy[crypto.RecoveryIDOffset] -= 27 + + hash := crypto.Keccak256(rawData) + + derivedPubkey, err := crypto.Ecrecover(hash, sigCopy) + if err != nil { + return err + } + + accountPK := crypto.FromECDSAPub(account.PublicKey) + + if !bytes.Equal(derivedPubkey, accountPK) { + return errors.New("unauthorized: invalid signature verification") + } + + return nil +} + +// SignTypedData signs a TypedData in EIP-712 format. This method is a wrapper +// to call SignData after hashing and encoding the TypedData input +func (w *wallet) SignTypedData(account accounts.Account, typedData apitypes.TypedData) ([]byte, error) { + _, rawData, err := apitypes.TypedDataAndHash(typedData) + if err != nil { + return nil, err + } + + rawDataBz := []byte(rawData) + + sigBytes, err := w.signData(account, "data/typed", rawDataBz) + if err != nil { + return nil, err + } + + // Verify recovered public key matches expected value + if err = w.verifyTypedDataSignature(account, rawDataBz, sigBytes); err != nil { + return nil, err + } + + return sigBytes, nil +} From b38681a3ee88b6a1f87d7ea29369051e35f7ef8a Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 20:25:58 +0200 Subject: [PATCH 02/14] add changelog entry --- .clconfig.json | 3 ++- CHANGELOG.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.clconfig.json b/.clconfig.json index 2e894b9..6b3dbb9 100644 --- a/.clconfig.json +++ b/.clconfig.json @@ -19,7 +19,8 @@ "proto", "rpc", "staking-precompile", - "tests" + "tests", + "types" ], "change_types": { "API Breaking": "api\\s*breaking", diff --git a/CHANGELOG.md b/CHANGELOG.md index d77ef4a..ad9c428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This changelog was created using the `clu` binary ### Improvements +- (types) [#19](https://github.com/evmos/os/pull/19) Add required wallet types for integration. - (all) [#15](https://github.com/evmos/os/pull/15) Add general types and utils. - (proto) [#14](https://github.com/evmos/os/pull/14) Add Protobufs and adjust scripts. - (eip-712) [#13](https://github.com/evmos/os/pull/13) Add EIP-712 package. From 974b38b785f69647a2dc6d6033b8b4da46b4565b Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:14:30 +0200 Subject: [PATCH 03/14] use example chain ID in wallet tests --- wallets/ledger/ledger_suite_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/wallets/ledger/ledger_suite_test.go b/wallets/ledger/ledger_suite_test.go index 123bf75..6431be7 100644 --- a/wallets/ledger/ledger_suite_test.go +++ b/wallets/ledger/ledger_suite_test.go @@ -5,6 +5,7 @@ package ledger_test import ( "encoding/hex" + "fmt" "regexp" "testing" @@ -21,6 +22,7 @@ import ( auxTx "github.com/cosmos/cosmos-sdk/x/auth/tx" bankTypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/evmos/os/testutil" "github.com/evmos/os/wallets/ledger" "github.com/evmos/os/wallets/ledger/mocks" "github.com/evmos/os/wallets/usbwallet" @@ -64,10 +66,10 @@ func (suite *LedgerTestSuite) newPubKey(pk string) (res cryptoTypes.PubKey) { func (suite *LedgerTestSuite) getMockTxAmino() []byte { whitespaceRegex := regexp.MustCompile(`\s+`) - tmp := whitespaceRegex.ReplaceAllString( + tmp := whitespaceRegex.ReplaceAllString(fmt.Sprintf( `{ "account_number": "0", - "chain_id":"evmos_9000-1", + "chain_id":"%s", "fee":{ "amount":[{"amount":"150","denom":"atom"}], "gas":"20000" @@ -82,7 +84,7 @@ func (suite *LedgerTestSuite) getMockTxAmino() []byte { } }], "sequence":"6" - }`, + }`, testutil.ExampleChainID), "", ) @@ -146,7 +148,7 @@ func (suite *LedgerTestSuite) getMockTxProtobuf() []byte { signBytes, err := auxTx.DirectSignBytes( bodyBytes, authInfoBytes, - "evmos_9000-1", + testutil.ExampleChainID, 0, ) suite.Require().NoError(err) From 38442cc927e47dba8eea529cba3a46ec60953c98 Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:28:28 +0200 Subject: [PATCH 04/14] add crypto package --- crypto/codec/amino.go | 28 ++ crypto/codec/codec.go | 16 + crypto/ethsecp256k1/benchmark_test.go | 34 ++ crypto/ethsecp256k1/ethsecp256k1.go | 249 +++++++++++ crypto/ethsecp256k1/ethsecp256k1_test.go | 124 ++++++ crypto/ethsecp256k1/keys.pb.go | 500 +++++++++++++++++++++++ crypto/hd/algorithm.go | 113 +++++ crypto/hd/algorithm_test.go | 130 ++++++ crypto/hd/benchmark_test.go | 31 ++ crypto/hd/utils_test.go | 181 ++++++++ crypto/keyring/options.go | 47 +++ crypto/secp256r1/verify.go | 52 +++ go.mod | 4 + go.sum | 34 ++ 14 files changed, 1543 insertions(+) create mode 100644 crypto/codec/amino.go create mode 100644 crypto/codec/codec.go create mode 100644 crypto/ethsecp256k1/benchmark_test.go create mode 100644 crypto/ethsecp256k1/ethsecp256k1.go create mode 100644 crypto/ethsecp256k1/ethsecp256k1_test.go create mode 100644 crypto/ethsecp256k1/keys.pb.go create mode 100644 crypto/hd/algorithm.go create mode 100644 crypto/hd/algorithm_test.go create mode 100644 crypto/hd/benchmark_test.go create mode 100644 crypto/hd/utils_test.go create mode 100644 crypto/keyring/options.go create mode 100644 crypto/secp256r1/verify.go diff --git a/crypto/codec/amino.go b/crypto/codec/amino.go new file mode 100644 index 0000000..e65885d --- /dev/null +++ b/crypto/codec/amino.go @@ -0,0 +1,28 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package codec + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + "github.com/evmos/os/crypto/ethsecp256k1" +) + +// RegisterCrypto registers all crypto dependency types with the provided Amino +// codec. +func RegisterCrypto(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(ðsecp256k1.PubKey{}, + ethsecp256k1.PubKeyName, nil) + cdc.RegisterConcrete(ðsecp256k1.PrivKey{}, + ethsecp256k1.PrivKeyName, nil) + + keyring.RegisterLegacyAminoCodec(cdc) + cryptocodec.RegisterCrypto(cdc) + + // 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/crypto/codec/codec.go b/crypto/codec/codec.go new file mode 100644 index 0000000..bba29d7 --- /dev/null +++ b/crypto/codec/codec.go @@ -0,0 +1,16 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package codec + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/evmos/os/crypto/ethsecp256k1" +) + +// RegisterInterfaces register the Evmos 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/crypto/ethsecp256k1/benchmark_test.go b/crypto/ethsecp256k1/benchmark_test.go new file mode 100644 index 0000000..815cc58 --- /dev/null +++ b/crypto/ethsecp256k1/benchmark_test.go @@ -0,0 +1,34 @@ +package ethsecp256k1 + +import ( + "fmt" + "testing" +) + +func BenchmarkGenerateKey(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := GenerateKey(); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkPubKey_VerifySignature(b *testing.B) { + privKey, err := GenerateKey() + if err != nil { + b.Fatal(err) + } + 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/crypto/ethsecp256k1/ethsecp256k1.go b/crypto/ethsecp256k1/ethsecp256k1.go new file mode 100644 index 0000000..f9358c0 --- /dev/null +++ b/crypto/ethsecp256k1/ethsecp256k1.go @@ -0,0 +1,249 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package ethsecp256k1 + +import ( + "bytes" + "crypto/ecdsa" + "crypto/subtle" + "fmt" + + errorsmod "cosmossdk.io/errors" + tmcrypto "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" + "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/os/ethereum/eip712" +) + +const ( + // PrivKeySize defines the size of the PrivKey bytes + PrivKeySize = 32 + // PubKeySize defines the size of the PubKey bytes + PubKeySize = 33 + // 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 = "ethermint/PrivKeyEthSecp256k1" + // PubKeyName defines the amino encoding name for the EthSecp256k1 public key + PubKeyName = "ethermint/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, error) { + priv, err := crypto.GenerateKey() + if err != nil { + return nil, err + } + + return &PrivKey{ + Key: crypto.FromECDSA(priv), + }, nil +} + +// 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 { + ecdsaPrivKey, err := privKey.ToECDSA() + if err != nil { + return nil + } + + return &PubKey{ + Key: crypto.CompressPubkey(&ecdsaPrivKey.PublicKey), + } +} + +// 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) { + // TODO: remove + if len(digestBz) != crypto.DigestLength { + digestBz = crypto.Keccak256Hash(digestBz).Bytes() + } + + key, err := privKey.ToECDSA() + if err != nil { + return nil, err + } + + return crypto.Sign(digestBz, key) +} + +// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type. +func (privKey PrivKey) ToECDSA() (*ecdsa.PrivateKey, error) { + return crypto.ToECDSA(privKey.Bytes()) +} + +// ---------------------------------------------------------------------------- +// secp256k1 Public Key + +var ( + _ cryptotypes.PubKey = &PubKey{} + _ codec.AminoMarshaler = &PubKey{} +) + +// 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() tmcrypto.Address { + pubk, err := crypto.DecompressPubkey(pubKey.Key) + if err != nil { + return nil + } + + return tmcrypto.Address(crypto.PubkeyToAddress(*pubk).Bytes()) +} + +// 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) || pubKey.verifySignatureAsEIP712(msg, sig) +} + +// Verifies the signature as an EIP-712 signature by first converting the message payload +// to EIP-712 object bytes, then performing ECDSA verification on the hash. This is to support +// signing a Cosmos payload using EIP-712. +func (pubKey PubKey) verifySignatureAsEIP712(msg, sig []byte) bool { + eip712Bytes, err := eip712.GetEIP712BytesForMsg(msg) + if err != nil { + return false + } + + if pubKey.verifySignatureECDSA(eip712Bytes, sig) { + return true + } + + // Try verifying the signature using the legacy EIP-712 encoding + legacyEIP712Bytes, err := eip712.LegacyGetEIP712BytesForMsg(msg) + if err != nil { + return false + } + + return pubKey.verifySignatureECDSA(legacyEIP712Bytes, sig) +} + +// Perform standard ECDSA signature verification for the given raw bytes and signature. +func (pubKey PubKey) verifySignatureECDSA(msg, sig []byte) bool { + if len(sig) == crypto.SignatureLength { + // remove recovery ID (V) if contained in the signature + sig = sig[:len(sig)-1] + } + + // the signature needs to be in [R || S] format when provided to VerifySignature + return crypto.VerifySignature(pubKey.Key, crypto.Keccak256Hash(msg).Bytes(), sig) +} diff --git a/crypto/ethsecp256k1/ethsecp256k1_test.go b/crypto/ethsecp256k1/ethsecp256k1_test.go new file mode 100644 index 0000000..044f1c2 --- /dev/null +++ b/crypto/ethsecp256k1/ethsecp256k1_test.go @@ -0,0 +1,124 @@ +package ethsecp256k1 + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +func TestPrivKey(t *testing.T) { + // validate type and equality + privKey, err := GenerateKey() + require.NoError(t, err) + require.Implements(t, (*cryptotypes.PrivKey)(nil), privKey) + + // validate inequality + privKey2, err := GenerateKey() + require.NoError(t, err) + require.False(t, privKey.Equals(privKey2)) + + // validate Ethereum address equality + addr := privKey.PubKey().Address() + key, err := privKey.ToECDSA() + require.NoError(t, err) + expectedAddr := crypto.PubkeyToAddress(key.PublicKey) + require.Equal(t, expectedAddr.Bytes(), addr.Bytes()) + + // validate we can sign some bytes + msg := []byte("hello world") + sigHash := crypto.Keccak256Hash(msg) + expectedSig, err := secp256k1.Sign(sigHash.Bytes(), privKey.Bytes()) + require.NoError(t, err) + + sig, err := privKey.Sign(sigHash.Bytes()) + require.NoError(t, err) + require.Equal(t, expectedSig, sig) +} + +func TestPrivKey_PubKey(t *testing.T) { + privKey, err := GenerateKey() + require.NoError(t, err) + + // validate type and equality + pubKey := &PubKey{ + Key: privKey.PubKey().Bytes(), + } + require.Implements(t, (*cryptotypes.PubKey)(nil), pubKey) + + // validate inequality + privKey2, err := GenerateKey() + require.NoError(t, err) + require.False(t, pubKey.Equals(privKey2.PubKey())) + + // validate signature + msg := []byte("hello world") + sigHash := crypto.Keccak256Hash(msg) + sig, err := privKey.Sign(sigHash.Bytes()) + require.NoError(t, err) + + res := pubKey.VerifySignature(msg, sig) + require.True(t, res) +} + +func TestMarshalAmino(t *testing.T) { + aminoCdc := codec.NewLegacyAmino() + privKey, err := GenerateKey() + require.NoError(t, err) + + 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/crypto/ethsecp256k1/keys.pb.go b/crypto/ethsecp256k1/keys.pb.go new file mode 100644 index 0000000..55901b7 --- /dev/null +++ b/crypto/ethsecp256k1/keys.pb.go @@ -0,0 +1,500 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ethermint/crypto/v1/ethsecp256k1/keys.proto + +package ethsecp256k1 + +import ( + fmt "fmt" + _ "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 is the public key in byte form + 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_0c10cadcf35beb64, []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 is the private key in byte form + 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_0c10cadcf35beb64, []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), "ethermint.crypto.v1.ethsecp256k1.PubKey") + proto.RegisterType((*PrivKey)(nil), "ethermint.crypto.v1.ethsecp256k1.PrivKey") +} + +func init() { + proto.RegisterFile("ethermint/crypto/v1/ethsecp256k1/keys.proto", fileDescriptor_0c10cadcf35beb64) +} + +var fileDescriptor_0c10cadcf35beb64 = []byte{ + // 197 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0x2d, 0xc9, 0x48, + 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x33, 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, 0x80, 0x2b, 0xd6, 0x83, 0x28, 0xd6, 0x2b, + 0x33, 0xd4, 0x43, 0x56, 0x2c, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xac, 0x0f, 0x62, 0x41, + 0xf4, 0x29, 0x29, 0x70, 0xb1, 0x05, 0x94, 0x26, 0x79, 0xa7, 0x56, 0x0a, 0x09, 0x70, 0x31, 0x67, + 0xa7, 0x56, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, 0x81, 0x98, 0x56, 0x2c, 0x33, 0x16, 0xc8, + 0x33, 0x28, 0x49, 0x73, 0xb1, 0x07, 0x14, 0x65, 0x96, 0x61, 0x55, 0xe2, 0xe4, 0x71, 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, 0x7a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, + 0xc9, 0xf9, 0xb9, 0xfa, 0xa9, 0x65, 0xb9, 0xf9, 0xc5, 0x50, 0xb2, 0xcc, 0xd0, 0x02, 0xe6, 0x1d, + 0x64, 0xe7, 0x25, 0xb1, 0x81, 0xdd, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x38, 0x7c, 0x56, + 0x84, 0xf6, 0x00, 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/crypto/hd/algorithm.go b/crypto/hd/algorithm.go new file mode 100644 index 0000000..f995f2b --- /dev/null +++ b/crypto/hd/algorithm.go @@ -0,0 +1,113 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package hd + +import ( + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + bip39 "github.com/tyler-smith/go-bip39" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/evmos/os/crypto/ethsecp256k1" +) + +const ( + // EthSecp256k1Type defines the ECDSA secp256k1 used on Ethereum + EthSecp256k1Type = hd.PubKeyType(ethsecp256k1.KeyType) +) + +var ( + // SupportedAlgorithms defines the list of signing algorithms used on evmOS: + // - eth_secp256k1 (Ethereum) + // - secp256k1 (Tendermint) + SupportedAlgorithms = keyring.SigningAlgoList{EthSecp256k1, hd.Secp256k1} + // SupportedAlgorithmsLedger defines the list of signing algorithms used on evmOS for the Ledger device: + // - eth_secp256k1 (Ethereum) + // - secp256k1 (Tendermint) + SupportedAlgorithmsLedger = keyring.SigningAlgoList{EthSecp256k1, hd.Secp256k1} +) + +// EthSecp256k1Option defines a function keys options for the ethereum Secp256k1 curve. +// It supports eth_secp256k1 and secp256k1 keys for accounts. +func EthSecp256k1Option() keyring.Option { + return func(options *keyring.Options) { + options.SupportedAlgos = SupportedAlgorithms + options.SupportedAlgosLedger = SupportedAlgorithmsLedger + } +} + +var ( + _ keyring.SignatureAlgo = EthSecp256k1 + + // EthSecp256k1 uses the Bitcoin secp256k1 ECDSA parameters. + EthSecp256k1 = ethSecp256k1Algo{} +) + +type ethSecp256k1Algo struct{} + +// Name returns eth_secp256k1 +func (s ethSecp256k1Algo) Name() hd.PubKeyType { + return EthSecp256k1Type +} + +// Derive derives and returns the eth_secp256k1 private key for the given mnemonic and HD path. +func (s ethSecp256k1Algo) Derive() hd.DeriveFn { + return func(mnemonic, bip39Passphrase, path string) ([]byte, error) { + hdpath, err := accounts.ParseDerivationPath(path) + if err != nil { + return nil, err + } + + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + if err != nil { + return nil, err + } + + // create a BTC-utils hd-derivation key chain + masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + return nil, err + } + + key := masterKey + for _, n := range hdpath { + key, err = key.Derive(n) + if err != nil { + return nil, err + } + } + + // btc-utils representation of a secp256k1 private key + privateKey, err := key.ECPrivKey() + if err != nil { + return nil, err + } + + // cast private key to a convertible form (single scalar field element of secp256k1) + // and then load into ethcrypto private key format. + // TODO: add links to godocs of the two methods or implementations of them, to compare equivalency + privateKeyECDSA := privateKey.ToECDSA() + derivedKey := crypto.FromECDSA(privateKeyECDSA) + + return derivedKey, nil + } +} + +// Generate generates a eth_secp256k1 private key from the given bytes. +func (s ethSecp256k1Algo) Generate() hd.GenerateFn { + return func(bz []byte) cryptotypes.PrivKey { + bzArr := make([]byte, ethsecp256k1.PrivKeySize) + copy(bzArr, bz) + + // TODO: modulo P + return ðsecp256k1.PrivKey{ + Key: bzArr, + } + } +} diff --git a/crypto/hd/algorithm_test.go b/crypto/hd/algorithm_test.go new file mode 100644 index 0000000..103f186 --- /dev/null +++ b/crypto/hd/algorithm_test.go @@ -0,0 +1,130 @@ +package hd + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + amino "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + + enccodec "github.com/evmos/evmos/v19/encoding/codec" + cryptocodec "github.com/evmos/os/crypto/codec" + evmostypes "github.com/evmos/os/types" +) + +var TestCodec amino.Codec + +func init() { + cdc := amino.NewLegacyAmino() + cryptocodec.RegisterCrypto(cdc) + + interfaceRegistry := types.NewInterfaceRegistry() + TestCodec = amino.NewProtoCodec(interfaceRegistry) + enccodec.RegisterInterfaces(interfaceRegistry) +} + +const ( + mnemonic = "picnic rent average infant boat squirrel federal assault mercy purity very motor fossil wheel verify upset box fresh horse vivid copy predict square regret" + + // hdWalletFixEnv defines whether the standard (correct) bip39 + // derivation path was used, or if derivation was affected by + // https://github.com/btcsuite/btcutil/issues/179 + hdWalletFixEnv = "GO_ETHEREUM_HDWALLET_FIX_ISSUE_179" +) + +func TestKeyring(t *testing.T) { + dir := t.TempDir() + mockIn := strings.NewReader("") + kr, err := keyring.New("evmos", keyring.BackendTest, dir, mockIn, TestCodec, EthSecp256k1Option()) + require.NoError(t, err) + + // fail in retrieving key + info, err := kr.Key("foo") + require.Error(t, err) + require.Nil(t, info) + + mockIn.Reset("password\npassword\n") + info, mnemonic, err := kr.NewMnemonic("foo", keyring.English, evmostypes.BIP44HDPath, keyring.DefaultBIP39Passphrase, EthSecp256k1) + require.NoError(t, err) + require.NotEmpty(t, mnemonic) + require.Equal(t, "foo", info.Name) + require.Equal(t, "local", info.GetType().String()) + pubKey, err := info.GetPubKey() + require.NoError(t, err) + require.Equal(t, string(EthSecp256k1Type), pubKey.Type()) + + hdPath := evmostypes.BIP44HDPath + + bz, err := EthSecp256k1.Derive()(mnemonic, keyring.DefaultBIP39Passphrase, hdPath) + require.NoError(t, err) + require.NotEmpty(t, bz) + + wrongBz, err := EthSecp256k1.Derive()(mnemonic, keyring.DefaultBIP39Passphrase, "/wrong/hdPath") + require.Error(t, err) + require.Empty(t, wrongBz) + + privkey := EthSecp256k1.Generate()(bz) + addr := common.BytesToAddress(privkey.PubKey().Address().Bytes()) + + os.Setenv(hdWalletFixEnv, "true") + wallet, err := NewFromMnemonic(mnemonic) + os.Setenv(hdWalletFixEnv, "") + require.NoError(t, err) + + path := MustParseDerivationPath(hdPath) + + account, err := wallet.Derive(path, false) + require.NoError(t, err) + require.Equal(t, addr.String(), account.Address.String()) +} + +func TestDerivation(t *testing.T) { + bz, err := EthSecp256k1.Derive()(mnemonic, keyring.DefaultBIP39Passphrase, evmostypes.BIP44HDPath) + require.NoError(t, err) + require.NotEmpty(t, bz) + + badBz, err := EthSecp256k1.Derive()(mnemonic, keyring.DefaultBIP39Passphrase, "44'/60'/0'/0/0") + require.NoError(t, err) + require.NotEmpty(t, badBz) + + require.NotEqual(t, bz, badBz) + + privkey := EthSecp256k1.Generate()(bz) + badPrivKey := EthSecp256k1.Generate()(badBz) + + require.False(t, privkey.Equals(badPrivKey)) + + wallet, err := NewFromMnemonic(mnemonic) + require.NoError(t, err) + + path := MustParseDerivationPath(evmostypes.BIP44HDPath) + account, err := wallet.Derive(path, false) + require.NoError(t, err) + + badPath := MustParseDerivationPath("44'/60'/0'/0/0") + badAccount, err := wallet.Derive(badPath, false) + require.NoError(t, err) + + // Equality of Address BIP44 + require.Equal(t, account.Address.String(), "0xA588C66983a81e800Db4dF74564F09f91c026351") + require.Equal(t, badAccount.Address.String(), "0xF8D6FDf2B8b488ea37e54903750dcd13F67E71cb") + // Inequality of wrong derivation path address + require.NotEqual(t, account.Address.String(), badAccount.Address.String()) + // Equality of evmOS implementation + require.Equal(t, common.BytesToAddress(privkey.PubKey().Address().Bytes()).String(), "0xA588C66983a81e800Db4dF74564F09f91c026351") + require.Equal(t, common.BytesToAddress(badPrivKey.PubKey().Address().Bytes()).String(), "0xF8D6FDf2B8b488ea37e54903750dcd13F67E71cb") + + // Equality of Eth and evmOS implementation + require.Equal(t, common.BytesToAddress(privkey.PubKey().Address()).String(), account.Address.String()) + require.Equal(t, common.BytesToAddress(badPrivKey.PubKey().Address()).String(), badAccount.Address.String()) + + // Inequality of wrong derivation path of Eth and evmOS implementation + require.NotEqual(t, common.BytesToAddress(privkey.PubKey().Address()).String(), badAccount.Address.String()) + require.NotEqual(t, common.BytesToAddress(badPrivKey.PubKey().Address()).String(), account.Address.Hex()) +} diff --git a/crypto/hd/benchmark_test.go b/crypto/hd/benchmark_test.go new file mode 100644 index 0000000..c2f4349 --- /dev/null +++ b/crypto/hd/benchmark_test.go @@ -0,0 +1,31 @@ +package hd + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/evmos/os/types" +) + +func BenchmarkEthSecp256k1Algo_Derive(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + deriveFn := EthSecp256k1.Derive() + if _, err := deriveFn(mnemonic, keyring.DefaultBIP39Passphrase, types.BIP44HDPath); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEthSecp256k1Algo_Generate(b *testing.B) { + bz, err := EthSecp256k1.Derive()(mnemonic, keyring.DefaultBIP39Passphrase, types.BIP44HDPath) + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + (ðSecp256k1Algo{}).Generate()(bz) + } +} diff --git a/crypto/hd/utils_test.go b/crypto/hd/utils_test.go new file mode 100644 index 0000000..2082368 --- /dev/null +++ b/crypto/hd/utils_test.go @@ -0,0 +1,181 @@ +// NOTE: This code is being used as test helper functions. +package hd + +import ( + "crypto/ecdsa" + "errors" + "os" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + bip39 "github.com/tyler-smith/go-bip39" +) + +const issue179FixEnvar = "GO_ETHEREUM_HDWALLET_FIX_ISSUE_179" + +// Wallet is the underlying wallet struct. +type Wallet struct { + mnemonic string + masterKey *hdkeychain.ExtendedKey + seed []byte + paths map[common.Address]accounts.DerivationPath + accounts []accounts.Account + stateLock sync.RWMutex + fixIssue172 bool +} + +// NewFromMnemonic returns a new wallet from a BIP-39 mnemonic. +func NewFromMnemonic(mnemonic string) (*Wallet, error) { + if mnemonic == "" { + return nil, errors.New("mnemonic is required") + } + + if !bip39.IsMnemonicValid(mnemonic) { + return nil, errors.New("mnemonic is invalid") + } + + seed, err := NewSeedFromMnemonic(mnemonic) + if err != nil { + return nil, err + } + + wallet, err := newWallet(seed) + if err != nil { + return nil, err + } + wallet.mnemonic = mnemonic + + return wallet, nil +} + +// NewSeedFromMnemonic returns a BIP-39 seed based on a BIP-39 mnemonic. +func NewSeedFromMnemonic(mnemonic string) ([]byte, error) { + if mnemonic == "" { + return nil, errors.New("mnemonic is required") + } + + return bip39.NewSeedWithErrorChecking(mnemonic, "") +} + +func newWallet(seed []byte) (*Wallet, error) { + masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + return nil, err + } + + return &Wallet{ + masterKey: masterKey, + seed: seed, + accounts: []accounts.Account{}, + paths: map[common.Address]accounts.DerivationPath{}, + fixIssue172: false || len(os.Getenv(issue179FixEnvar)) > 0, + }, nil +} + +// Derive implements accounts.Wallet, deriving a new account at the specific +// derivation path. If pin is set to true, the account will be added to the list +// of tracked accounts. +func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + // Try to derive the actual account and update its URL if successful + w.stateLock.RLock() // Avoid device disappearing during derivation + + address, err := w.deriveAddress(path) + + w.stateLock.RUnlock() + + // If an error occurred or no pinning was requested, return + if err != nil { + return accounts.Account{}, err + } + + account := accounts.Account{ + Address: address, + URL: accounts.URL{ + Scheme: "", + Path: path.String(), + }, + } + + if !pin { + return account, nil + } + + // Pinning needs to modify the state + w.stateLock.Lock() + defer w.stateLock.Unlock() + + if _, ok := w.paths[address]; !ok { + w.accounts = append(w.accounts, account) + w.paths[address] = path + } + + return account, nil +} + +// MustParseDerivationPath parses the derivation path in string format into +// []uint32 but will panic if it can't parse it. +func MustParseDerivationPath(path string) accounts.DerivationPath { + parsed, err := accounts.ParseDerivationPath(path) + if err != nil { + panic(err) + } + + return parsed +} + +// DerivePrivateKey derives the private key of the derivation path. +func (w *Wallet) derivePrivateKey(path accounts.DerivationPath) (*ecdsa.PrivateKey, error) { + var err error + key := w.masterKey + for _, n := range path { + if w.fixIssue172 && key.IsAffectedByIssue172() { + key, err = key.Derive(n) + } else { + //lint:ignore SA1019 this is used for testing only + key, err = key.DeriveNonStandard(n) //nolint:staticcheck + } + if err != nil { + return nil, err + } + } + + privateKey, err := key.ECPrivKey() + privateKeyECDSA := privateKey.ToECDSA() + if err != nil { + return nil, err + } + + return privateKeyECDSA, nil +} + +// derivePublicKey derives the public key of the derivation path. +func (w *Wallet) derivePublicKey(path accounts.DerivationPath) (*ecdsa.PublicKey, error) { + privateKeyECDSA, err := w.derivePrivateKey(path) + if err != nil { + return nil, err + } + + publicKey := privateKeyECDSA.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return nil, errors.New("failed to get public key") + } + + return publicKeyECDSA, nil +} + +// DeriveAddress derives the account address of the derivation path. +func (w *Wallet) deriveAddress(path accounts.DerivationPath) (common.Address, error) { + publicKeyECDSA, err := w.derivePublicKey(path) + if err != nil { + return common.Address{}, err + } + + address := crypto.PubkeyToAddress(*publicKeyECDSA) + return address, nil +} diff --git a/crypto/keyring/options.go b/crypto/keyring/options.go new file mode 100644 index 0000000..27cafea --- /dev/null +++ b/crypto/keyring/options.go @@ -0,0 +1,47 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package keyring + +import ( + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cosmosLedger "github.com/cosmos/cosmos-sdk/crypto/ledger" + "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/evmos/os/crypto/ethsecp256k1" + "github.com/evmos/os/crypto/hd" + "github.com/evmos/os/wallets/ledger" +) + +// AppName defines the Ledger app used for signing. evmOS uses the Ethereum app +const AppName = "Ethereum" + +var ( + // SupportedAlgorithms defines the list of signing algorithms used on evmOS: + // - eth_secp256k1 (Ethereum) + SupportedAlgorithms = keyring.SigningAlgoList{hd.EthSecp256k1} + // SupportedAlgorithmsLedger defines the list of signing algorithms used on Evmos 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} + // LedgerDerivation defines the evmOS Ledger Go derivation (Ethereum app with EIP-712 signing) + LedgerDerivation = ledger.EvmosLedgerDerivation() + // CreatePubkey uses the ethsecp256k1 pubkey with Ethereum address generation and keccak hashing + CreatePubkey = func(key []byte) types.PubKey { return ðsecp256k1.PubKey{Key: key} } + // SkipDERConversion represents whether the signed Ledger output should skip conversion from DER to BER. + // This is set to true for signing performed by the Ledger Ethereum app. + SkipDERConversion = true +) + +// 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 + options.LedgerDerivation = func() (cosmosLedger.SECP256K1, error) { return LedgerDerivation() } + options.LedgerCreateKey = CreatePubkey + options.LedgerAppName = AppName + options.LedgerSigSkipDERConv = SkipDERConversion + } +} diff --git a/crypto/secp256r1/verify.go b/crypto/secp256r1/verify.go new file mode 100644 index 0000000..9b4ba86 --- /dev/null +++ b/crypto/secp256r1/verify.go @@ -0,0 +1,52 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package secp256r1 + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "math/big" +) + +// Verifies the given signature (r, s) for the given hash and public key (x, y). +func Verify(hash []byte, r, s, x, y *big.Int) bool { + // Create the public key format + publicKey := newECDSAPublicKey(x, y) + + // Check if they are invalid public key coordinates + if publicKey == nil { + return false + } + + // Verify the signature with the public key, + // then return true if it's valid, false otherwise + return ecdsa.Verify(publicKey, hash, r, s) +} + +// newECDSAPublicKey creates an ECDSA P256 public key from the given coordinates +func newECDSAPublicKey(x, y *big.Int) *ecdsa.PublicKey { + // Check if the given coordinates are valid and in the reference point (infinity) + if x == nil || y == nil || x.Sign() == 0 && y.Sign() == 0 || !elliptic.P256().IsOnCurve(x, y) { + return nil + } + + return &ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: x, + Y: y, + } +} diff --git a/go.mod b/go.mod index 80abbf6..4288df9 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.3.0 cosmossdk.io/simapp v0.0.0-20230608160436-666c345ad23d + github.com/btcsuite/btcd v0.24.2 + github.com/btcsuite/btcd/btcutil v1.1.5 github.com/cometbft/cometbft v0.37.9 github.com/cosmos/cosmos-sdk v0.47.12 github.com/cosmos/gogoproto v1.4.10 @@ -15,6 +17,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.3 github.com/tidwall/sjson v1.2.5 + github.com/tyler-smith/go-bip39 v1.1.0 github.com/zondax/hid v0.9.2 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/text v0.16.0 @@ -44,6 +47,7 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect diff --git a/go.sum b/go.sum index a4a9fc8..2afa97c 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,7 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -261,13 +262,32 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -378,16 +398,20 @@ github.com/crypto-org-chain/cronos/versiondb v0.0.0-20240129013154-12efd9b7643f github.com/crypto-org-chain/cronos/versiondb v0.0.0-20240129013154-12efd9b7643f/go.mod h1:8L1WprpzpqIz6erpQjd/qOvMNpYDG4qzR5vWgAqv6Jw= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= @@ -650,6 +674,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -727,6 +752,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -738,6 +765,7 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -754,6 +782,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= @@ -861,6 +890,7 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -1052,6 +1082,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -1117,6 +1149,7 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1160,6 +1193,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 3541a4d3217af8c7d67de5025bee77b04f93232c Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:39:48 +0200 Subject: [PATCH 05/14] regenerate protos --- crypto/ethsecp256k1/keys.pb.go | 41 +++++++++++++------------- types/dynamic_fee.pb.go | 50 +++++++++++++++---------------- types/indexer.pb.go | 50 +++++++++++++++---------------- types/web3.pb.go | 54 +++++++++++++++++----------------- 4 files changed, 97 insertions(+), 98 deletions(-) diff --git a/crypto/ethsecp256k1/keys.pb.go b/crypto/ethsecp256k1/keys.pb.go index 55901b7..857dc85 100644 --- a/crypto/ethsecp256k1/keys.pb.go +++ b/crypto/ethsecp256k1/keys.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ethermint/crypto/v1/ethsecp256k1/keys.proto +// source: os/crypto/v1/ethsecp256k1/keys.proto package ethsecp256k1 @@ -34,7 +34,7 @@ type PubKey struct { func (m *PubKey) Reset() { *m = PubKey{} } func (*PubKey) ProtoMessage() {} func (*PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_0c10cadcf35beb64, []int{0} + return fileDescriptor_ed584e6356c1fa1b, []int{0} } func (m *PubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -81,7 +81,7 @@ 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_0c10cadcf35beb64, []int{1} + return fileDescriptor_ed584e6356c1fa1b, []int{1} } func (m *PrivKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -118,29 +118,28 @@ func (m *PrivKey) GetKey() []byte { } func init() { - proto.RegisterType((*PubKey)(nil), "ethermint.crypto.v1.ethsecp256k1.PubKey") - proto.RegisterType((*PrivKey)(nil), "ethermint.crypto.v1.ethsecp256k1.PrivKey") + proto.RegisterType((*PubKey)(nil), "os.crypto.v1.ethsecp256k1.PubKey") + proto.RegisterType((*PrivKey)(nil), "os.crypto.v1.ethsecp256k1.PrivKey") } func init() { - proto.RegisterFile("ethermint/crypto/v1/ethsecp256k1/keys.proto", fileDescriptor_0c10cadcf35beb64) + proto.RegisterFile("os/crypto/v1/ethsecp256k1/keys.proto", fileDescriptor_ed584e6356c1fa1b) } -var fileDescriptor_0c10cadcf35beb64 = []byte{ - // 197 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0x2d, 0xc9, 0x48, - 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x33, 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, 0x80, 0x2b, 0xd6, 0x83, 0x28, 0xd6, 0x2b, - 0x33, 0xd4, 0x43, 0x56, 0x2c, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xac, 0x0f, 0x62, 0x41, - 0xf4, 0x29, 0x29, 0x70, 0xb1, 0x05, 0x94, 0x26, 0x79, 0xa7, 0x56, 0x0a, 0x09, 0x70, 0x31, 0x67, - 0xa7, 0x56, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, 0x81, 0x98, 0x56, 0x2c, 0x33, 0x16, 0xc8, - 0x33, 0x28, 0x49, 0x73, 0xb1, 0x07, 0x14, 0x65, 0x96, 0x61, 0x55, 0xe2, 0xe4, 0x71, 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, 0x7a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, - 0xc9, 0xf9, 0xb9, 0xfa, 0xa9, 0x65, 0xb9, 0xf9, 0xc5, 0x50, 0xb2, 0xcc, 0xd0, 0x02, 0xe6, 0x1d, - 0x64, 0xe7, 0x25, 0xb1, 0x81, 0xdd, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x38, 0x7c, 0x56, - 0x84, 0xf6, 0x00, 0x00, 0x00, +var fileDescriptor_ed584e6356c1fa1b = []byte{ + // 187 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc9, 0x2f, 0xd6, 0x4f, + 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x33, 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, 0x92, 0xcc, 0x2f, 0xd6, 0x83, 0xa8, 0xd2, 0x2b, 0x33, 0xd4, 0x43, 0x56, 0x25, 0x25, 0x92, + 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa5, 0x0f, 0x62, 0x41, 0x34, 0x28, 0x29, 0x70, 0xb1, 0x05, 0x94, + 0x26, 0x79, 0xa7, 0x56, 0x0a, 0x09, 0x70, 0x31, 0x67, 0xa7, 0x56, 0x4a, 0x30, 0x2a, 0x30, 0x6a, + 0xf0, 0x04, 0x81, 0x98, 0x56, 0x2c, 0x33, 0x16, 0xc8, 0x33, 0x28, 0x49, 0x73, 0xb1, 0x07, 0x14, + 0x65, 0x96, 0x61, 0x55, 0xe2, 0xe4, 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, 0xea, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0xa9, 0x65, 0xb9, + 0xf9, 0xc5, 0xfa, 0x08, 0x0f, 0x20, 0xbb, 0x2b, 0x89, 0x0d, 0xec, 0x10, 0x63, 0x40, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xca, 0xcd, 0x5c, 0x75, 0xe1, 0x00, 0x00, 0x00, } func (m *PubKey) Marshal() (dAtA []byte, err error) { diff --git a/types/dynamic_fee.pb.go b/types/dynamic_fee.pb.go index 6b564c7..5e64776 100644 --- a/types/dynamic_fee.pb.go +++ b/types/dynamic_fee.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ethermint/types/v1/dynamic_fee.proto +// source: os/types/v1/dynamic_fee.proto package types @@ -24,9 +24,11 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// ExtensionOptionDynamicFeeTx is an extension option that specifies the maxPrioPrice for cosmos tx +// ExtensionOptionDynamicFeeTx is an extension option that specifies the +// maxPrioPrice for cosmos tx type ExtensionOptionDynamicFeeTx struct { - // max_priority_price is the same as `max_priority_fee_per_gas` in eip-1559 spec + // max_priority_price is the same as `max_priority_fee_per_gas` in eip-1559 + // spec MaxPriorityPrice cosmossdk_io_math.Int `protobuf:"bytes,1,opt,name=max_priority_price,json=maxPriorityPrice,proto3,customtype=cosmossdk.io/math.Int" json:"max_priority_price"` } @@ -34,7 +36,7 @@ func (m *ExtensionOptionDynamicFeeTx) Reset() { *m = ExtensionOptionDyna func (m *ExtensionOptionDynamicFeeTx) String() string { return proto.CompactTextString(m) } func (*ExtensionOptionDynamicFeeTx) ProtoMessage() {} func (*ExtensionOptionDynamicFeeTx) Descriptor() ([]byte, []int) { - return fileDescriptor_9d7cf05c9992c480, []int{0} + return fileDescriptor_a9afa21b8d75b43a, []int{0} } func (m *ExtensionOptionDynamicFeeTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -64,30 +66,28 @@ func (m *ExtensionOptionDynamicFeeTx) XXX_DiscardUnknown() { var xxx_messageInfo_ExtensionOptionDynamicFeeTx proto.InternalMessageInfo func init() { - proto.RegisterType((*ExtensionOptionDynamicFeeTx)(nil), "ethermint.types.v1.ExtensionOptionDynamicFeeTx") + proto.RegisterType((*ExtensionOptionDynamicFeeTx)(nil), "os.types.v1.ExtensionOptionDynamicFeeTx") } -func init() { - proto.RegisterFile("ethermint/types/v1/dynamic_fee.proto", fileDescriptor_9d7cf05c9992c480) -} +func init() { proto.RegisterFile("os/types/v1/dynamic_fee.proto", fileDescriptor_a9afa21b8d75b43a) } -var fileDescriptor_9d7cf05c9992c480 = []byte{ - // 238 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x49, 0x2d, 0xc9, 0x48, - 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0xd4, 0x4f, - 0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x8e, 0x4f, 0x4b, 0x4d, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0x12, 0x82, 0xab, 0xd2, 0x03, 0xab, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, - 0x07, 0x4b, 0xeb, 0x83, 0x58, 0x10, 0x95, 0x4a, 0x59, 0x5c, 0xd2, 0xae, 0x15, 0x25, 0xa9, 0x79, - 0xc5, 0x99, 0xf9, 0x79, 0xfe, 0x05, 0x25, 0x99, 0xf9, 0x79, 0x2e, 0x10, 0xd3, 0xdc, 0x52, 0x53, - 0x43, 0x2a, 0x84, 0xbc, 0xb9, 0x84, 0x72, 0x13, 0x2b, 0xe2, 0x0b, 0x8a, 0x32, 0xf3, 0x8b, 0x32, - 0x4b, 0x2a, 0x41, 0x8c, 0xe4, 0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0xd9, 0x13, 0xf7, - 0xe4, 0x19, 0x6e, 0xdd, 0x93, 0x17, 0x4d, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0x2e, 0x4e, 0xc9, 0xd6, - 0xcb, 0xcc, 0xd7, 0xcf, 0x4d, 0x2c, 0xc9, 0xd0, 0xf3, 0xcc, 0x2b, 0x09, 0x12, 0xc8, 0x4d, 0xac, - 0x08, 0x80, 0xea, 0x0b, 0x00, 0x69, 0x73, 0xb2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, - 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, - 0x39, 0x86, 0x28, 0x85, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xd4, - 0xb2, 0xdc, 0xfc, 0x62, 0x28, 0x59, 0x66, 0x68, 0x01, 0xf1, 0x66, 0x12, 0x1b, 0xd8, 0xb9, 0xc6, - 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc3, 0x94, 0x96, 0xc5, 0x00, 0x01, 0x00, 0x00, +var fileDescriptor_a9afa21b8d75b43a = []byte{ + // 227 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcd, 0x2f, 0xd6, 0x2f, + 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0xd4, 0x4f, 0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x8e, + 0x4f, 0x4b, 0x4d, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xce, 0x2f, 0xd6, 0x03, 0x4b, + 0xeb, 0x95, 0x19, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xc5, 0xf5, 0x41, 0x2c, 0x88, 0x12, + 0xa5, 0x2c, 0x2e, 0x69, 0xd7, 0x8a, 0x92, 0xd4, 0xbc, 0xe2, 0xcc, 0xfc, 0x3c, 0xff, 0x82, 0x92, + 0xcc, 0xfc, 0x3c, 0x17, 0x88, 0x31, 0x6e, 0xa9, 0xa9, 0x21, 0x15, 0x42, 0xde, 0x5c, 0x42, 0xb9, + 0x89, 0x15, 0xf1, 0x05, 0x45, 0x99, 0xf9, 0x45, 0x99, 0x25, 0x95, 0x20, 0x46, 0x72, 0xaa, 0x04, + 0xa3, 0x02, 0xa3, 0x06, 0xa7, 0x93, 0xec, 0x89, 0x7b, 0xf2, 0x0c, 0xb7, 0xee, 0xc9, 0x8b, 0x26, + 0xe7, 0x17, 0xe7, 0xe6, 0x17, 0x17, 0xa7, 0x64, 0xeb, 0x65, 0xe6, 0xeb, 0xe7, 0x26, 0x96, 0x64, + 0xe8, 0x79, 0xe6, 0x95, 0x04, 0x09, 0xe4, 0x26, 0x56, 0x04, 0x40, 0xf5, 0x05, 0x80, 0xb4, 0x39, + 0x19, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, + 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x64, 0x7a, 0x66, 0x49, + 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x6a, 0x59, 0x6e, 0x7e, 0xb1, 0x3e, 0xcc, 0x63, + 0x49, 0x6c, 0x60, 0x77, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4d, 0xf3, 0xbb, 0x1c, 0xeb, + 0x00, 0x00, 0x00, } func (m *ExtensionOptionDynamicFeeTx) Marshal() (dAtA []byte, err error) { diff --git a/types/indexer.pb.go b/types/indexer.pb.go index e1d1005..c2e1426 100644 --- a/types/indexer.pb.go +++ b/types/indexer.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ethermint/types/v1/indexer.proto +// source: os/types/v1/indexer.proto package types @@ -48,7 +48,7 @@ func (m *TxResult) Reset() { *m = TxResult{} } func (m *TxResult) String() string { return proto.CompactTextString(m) } func (*TxResult) ProtoMessage() {} func (*TxResult) Descriptor() ([]byte, []int) { - return fileDescriptor_1197e10a8be8ed28, []int{0} + return fileDescriptor_188b47a956fe6721, []int{0} } func (m *TxResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -78,32 +78,32 @@ func (m *TxResult) XXX_DiscardUnknown() { var xxx_messageInfo_TxResult proto.InternalMessageInfo func init() { - proto.RegisterType((*TxResult)(nil), "ethermint.types.v1.TxResult") + proto.RegisterType((*TxResult)(nil), "os.types.v1.TxResult") } -func init() { proto.RegisterFile("ethermint/types/v1/indexer.proto", fileDescriptor_1197e10a8be8ed28) } +func init() { proto.RegisterFile("os/types/v1/indexer.proto", fileDescriptor_188b47a956fe6721) } -var fileDescriptor_1197e10a8be8ed28 = []byte{ - // 298 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0x31, 0x4b, 0xc3, 0x40, - 0x18, 0x86, 0x73, 0xb6, 0x4d, 0xe3, 0xa1, 0x83, 0x51, 0x4a, 0x54, 0x88, 0x87, 0x53, 0xa6, 0x84, - 0xe2, 0x22, 0x1d, 0x5d, 0xc4, 0xf5, 0xa8, 0x8b, 0x4b, 0x48, 0x9b, 0xcf, 0xbb, 0x83, 0x5e, 0xaf, - 0xf4, 0xbe, 0x84, 0xf8, 0x0f, 0x1c, 0xfd, 0x09, 0xfe, 0x1c, 0xc7, 0x8e, 0x8e, 0xd2, 0xe2, 0xff, - 0x90, 0x5e, 0x42, 0x5d, 0x8e, 0x7b, 0x79, 0x9e, 0x8f, 0x17, 0x5e, 0xca, 0x00, 0x25, 0xac, 0xb5, - 0x5a, 0x62, 0x86, 0x6f, 0x2b, 0xb0, 0x59, 0x3d, 0xce, 0xd4, 0xb2, 0x84, 0x06, 0xd6, 0xe9, 0x6a, - 0x6d, 0xd0, 0x84, 0xe1, 0xc1, 0x48, 0x9d, 0x91, 0xd6, 0xe3, 0xab, 0x0b, 0x61, 0x84, 0x71, 0x38, - 0xdb, 0xff, 0x5a, 0xf3, 0xf6, 0x97, 0xd0, 0x60, 0xda, 0x70, 0xb0, 0xd5, 0x02, 0xc3, 0x11, 0xf5, - 0x25, 0x28, 0x21, 0x31, 0x22, 0x8c, 0x24, 0x3d, 0xde, 0xa5, 0xf0, 0x92, 0x06, 0xd8, 0xe4, 0xae, - 0x22, 0x3a, 0x62, 0x24, 0x39, 0xe5, 0x43, 0x6c, 0x9e, 0xf6, 0x31, 0xbc, 0xa6, 0xc7, 0xda, 0x8a, - 0x8e, 0xf5, 0x1c, 0x0b, 0xb4, 0x15, 0x2d, 0x64, 0xf4, 0x04, 0x50, 0xe6, 0x87, 0xdb, 0x3e, 0x23, - 0xc9, 0x80, 0x53, 0x40, 0x39, 0xed, 0xce, 0x47, 0xd4, 0x7f, 0x2d, 0xd4, 0x02, 0xca, 0x68, 0xc0, - 0x48, 0x12, 0xf0, 0x2e, 0xed, 0x1b, 0x45, 0x61, 0xf3, 0xca, 0x42, 0x19, 0xf9, 0x8c, 0x24, 0x7d, - 0x3e, 0x14, 0x85, 0x7d, 0xb6, 0x50, 0x86, 0x29, 0x3d, 0x9f, 0x57, 0xba, 0x5a, 0x14, 0xa8, 0x6a, - 0xc8, 0x0f, 0xd6, 0xd0, 0x59, 0x67, 0xff, 0xe8, 0xb1, 0xf5, 0x27, 0xfd, 0xf7, 0xcf, 0x1b, 0xef, - 0x61, 0xf2, 0xb5, 0x8d, 0xc9, 0x66, 0x1b, 0x93, 0x9f, 0x6d, 0x4c, 0x3e, 0x76, 0xb1, 0xb7, 0xd9, - 0xc5, 0xde, 0xf7, 0x2e, 0xf6, 0x5e, 0x98, 0x50, 0x28, 0xab, 0x59, 0x3a, 0x37, 0x3a, 0x83, 0x5a, - 0x1b, 0xdb, 0xbd, 0xf5, 0xf8, 0xbe, 0x9d, 0x77, 0xe6, 0xbb, 0xa9, 0xee, 0xfe, 0x02, 0x00, 0x00, - 0xff, 0xff, 0x8e, 0x96, 0x1b, 0x8f, 0x78, 0x01, 0x00, 0x00, +var fileDescriptor_188b47a956fe6721 = []byte{ + // 290 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xb1, 0x4e, 0xeb, 0x30, + 0x18, 0x85, 0xe3, 0xdb, 0x36, 0xc9, 0x35, 0x30, 0x10, 0x50, 0x95, 0x80, 0x64, 0x2c, 0xa6, 0x4c, + 0xb1, 0xaa, 0x6e, 0x8c, 0x2c, 0x88, 0xd5, 0x2a, 0x0b, 0x4b, 0x94, 0x36, 0x3f, 0x4e, 0xa4, 0x04, + 0x57, 0xb5, 0x13, 0x85, 0x37, 0x60, 0xe4, 0x11, 0x78, 0x1c, 0xc6, 0x8e, 0x8c, 0x28, 0x11, 0xef, + 0x81, 0xe2, 0x84, 0xb2, 0xf9, 0xf8, 0xfb, 0x8e, 0x2d, 0x1d, 0x1c, 0x48, 0xc5, 0xf4, 0xcb, 0x16, + 0x14, 0xab, 0x17, 0x2c, 0x7f, 0x4e, 0xa1, 0x81, 0x5d, 0xb4, 0xdd, 0x49, 0x2d, 0xbd, 0x23, 0xa9, + 0x22, 0x83, 0xa2, 0x7a, 0x71, 0x71, 0x2e, 0xa4, 0x90, 0xe6, 0x9e, 0xf5, 0xa7, 0x41, 0xb9, 0xfe, + 0x46, 0xd8, 0x5d, 0x35, 0x1c, 0x54, 0x55, 0x68, 0x6f, 0x8e, 0xed, 0x0c, 0x72, 0x91, 0x69, 0x1f, + 0x51, 0x14, 0x4e, 0xf8, 0x98, 0xbc, 0x00, 0xbb, 0xba, 0x89, 0xcd, 0xdb, 0xfe, 0x3f, 0x8a, 0xc2, + 0x13, 0xee, 0xe8, 0xe6, 0xbe, 0x8f, 0xde, 0x25, 0xfe, 0x5f, 0x2a, 0x31, 0xb2, 0x89, 0x61, 0x6e, + 0xa9, 0xc4, 0x00, 0x29, 0x3e, 0x06, 0x9d, 0xc5, 0x87, 0xee, 0x94, 0xa2, 0x70, 0xc6, 0x31, 0xe8, + 0x6c, 0x35, 0xd6, 0xe7, 0xd8, 0x7e, 0x4a, 0xf2, 0x02, 0x52, 0x7f, 0x46, 0x51, 0xe8, 0xf2, 0x31, + 0xf5, 0x3f, 0x8a, 0x44, 0xc5, 0x95, 0x82, 0xd4, 0xb7, 0x29, 0x0a, 0xa7, 0xdc, 0x11, 0x89, 0x7a, + 0x50, 0x90, 0x7a, 0x11, 0x3e, 0xdb, 0x54, 0x65, 0x55, 0x24, 0x3a, 0xaf, 0x21, 0x3e, 0x58, 0x8e, + 0xb1, 0x4e, 0xff, 0xd0, 0xdd, 0xe0, 0xdf, 0x4c, 0x5f, 0xdf, 0xaf, 0xac, 0xdb, 0xe5, 0x47, 0x4b, + 0xd0, 0xbe, 0x25, 0xe8, 0xab, 0x25, 0xe8, 0xad, 0x23, 0xd6, 0xbe, 0x23, 0xd6, 0x67, 0x47, 0xac, + 0xc7, 0x40, 0xe4, 0x3a, 0xab, 0xd6, 0xd1, 0x46, 0x96, 0x0c, 0xea, 0x52, 0x2a, 0xf6, 0x3b, 0xe8, + 0xda, 0x36, 0x1b, 0x2d, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x34, 0xaa, 0x8e, 0x4b, 0x63, 0x01, + 0x00, 0x00, } func (m *TxResult) Marshal() (dAtA []byte, err error) { diff --git a/types/web3.pb.go b/types/web3.pb.go index f780bfa..7455ecc 100644 --- a/types/web3.pb.go +++ b/types/web3.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: ethermint/types/v1/web3.proto +// source: os/types/v1/web3.proto package types @@ -23,8 +23,8 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// ExtensionOptionsWeb3Tx is an extension option that specifies the typed chain id, -// the fee payer as well as its signature data. +// ExtensionOptionsWeb3Tx is an extension option that specifies the typed chain +// id, the fee payer as well as its signature data. type ExtensionOptionsWeb3Tx struct { // typed_data_chain_id is used only in EIP712 Domain and should match // Ethereum network ID in a Web3 provider (e.g. Metamask). @@ -41,7 +41,7 @@ func (m *ExtensionOptionsWeb3Tx) Reset() { *m = ExtensionOptionsWeb3Tx{} func (m *ExtensionOptionsWeb3Tx) String() string { return proto.CompactTextString(m) } func (*ExtensionOptionsWeb3Tx) ProtoMessage() {} func (*ExtensionOptionsWeb3Tx) Descriptor() ([]byte, []int) { - return fileDescriptor_9eb7cd56e3c92bc3, []int{0} + return fileDescriptor_36d81232614baf9d, []int{0} } func (m *ExtensionOptionsWeb3Tx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -71,32 +71,32 @@ func (m *ExtensionOptionsWeb3Tx) XXX_DiscardUnknown() { var xxx_messageInfo_ExtensionOptionsWeb3Tx proto.InternalMessageInfo func init() { - proto.RegisterType((*ExtensionOptionsWeb3Tx)(nil), "ethermint.types.v1.ExtensionOptionsWeb3Tx") + proto.RegisterType((*ExtensionOptionsWeb3Tx)(nil), "os.types.v1.ExtensionOptionsWeb3Tx") } -func init() { proto.RegisterFile("ethermint/types/v1/web3.proto", fileDescriptor_9eb7cd56e3c92bc3) } +func init() { proto.RegisterFile("os/types/v1/web3.proto", fileDescriptor_36d81232614baf9d) } -var fileDescriptor_9eb7cd56e3c92bc3 = []byte{ - // 304 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0x2d, 0xc9, 0x48, - 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0xd4, 0x2f, - 0x4f, 0x4d, 0x32, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x82, 0x4b, 0xeb, 0x81, 0xa5, - 0xf5, 0xca, 0x0c, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0xd2, 0xfa, 0x20, 0x16, 0x44, 0xa5, - 0xd2, 0x57, 0x46, 0x2e, 0x31, 0xd7, 0x8a, 0x92, 0xd4, 0xbc, 0xe2, 0xcc, 0xfc, 0x3c, 0xff, 0x82, - 0x92, 0xcc, 0xfc, 0xbc, 0xe2, 0xf0, 0xd4, 0x24, 0xe3, 0x90, 0x0a, 0xa1, 0x44, 0x2e, 0x61, 0x90, - 0xe6, 0x94, 0xf8, 0x94, 0xc4, 0x92, 0xc4, 0xf8, 0xe4, 0x8c, 0xc4, 0xcc, 0xbc, 0xf8, 0xcc, 0x14, - 0x09, 0x46, 0x05, 0x46, 0x0d, 0x16, 0x27, 0xa3, 0x47, 0xf7, 0xe4, 0x05, 0x42, 0x40, 0xd2, 0x2e, - 0x89, 0x25, 0x89, 0xce, 0x20, 0x49, 0x4f, 0x97, 0x57, 0xf7, 0xe4, 0xa5, 0x4a, 0xd0, 0xc4, 0x74, - 0xf2, 0x73, 0x33, 0x4b, 0x52, 0x73, 0x0b, 0x4a, 0x2a, 0x83, 0x04, 0xd0, 0xe4, 0x52, 0x84, 0x8c, - 0xb9, 0x38, 0xd3, 0x52, 0x53, 0xe3, 0x0b, 0x12, 0x2b, 0x53, 0x8b, 0x24, 0x98, 0x14, 0x18, 0x35, - 0x38, 0x9d, 0xc4, 0x5e, 0xdd, 0x93, 0x17, 0x4a, 0x4b, 0x4d, 0x0d, 0x00, 0x89, 0x21, 0x69, 0xe6, - 0x80, 0x89, 0x09, 0xd9, 0x72, 0xf1, 0xc2, 0x35, 0xc5, 0x17, 0x67, 0xa6, 0x4b, 0x30, 0x2b, 0x30, - 0x6a, 0xf0, 0x38, 0x49, 0xbe, 0xba, 0x27, 0x2f, 0x0a, 0x53, 0x14, 0x9c, 0x99, 0x8e, 0xa4, 0x97, - 0x1b, 0x49, 0xd8, 0x8a, 0xa5, 0x63, 0x81, 0x3c, 0x83, 0x93, 0xd5, 0x89, 0x47, 0x72, 0x8c, 0x17, - 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, - 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x29, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, - 0xea, 0xa7, 0x96, 0xe5, 0xe6, 0x17, 0x43, 0xc9, 0x32, 0x43, 0x0b, 0x48, 0x58, 0x27, 0xb1, 0x81, - 0x83, 0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x1d, 0xbb, 0x93, 0xf7, 0x85, 0x01, 0x00, 0x00, +var fileDescriptor_36d81232614baf9d = []byte{ + // 294 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcb, 0x2f, 0xd6, 0x2f, + 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0xd4, 0x2f, 0x4f, 0x4d, 0x32, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0xe2, 0xce, 0x2f, 0xd6, 0x03, 0x8b, 0xeb, 0x95, 0x19, 0x4a, 0x89, 0xa4, 0xe7, + 0xa7, 0xe7, 0x83, 0xc5, 0xf5, 0x41, 0x2c, 0x88, 0x12, 0xa5, 0xaf, 0x8c, 0x5c, 0x62, 0xae, 0x15, + 0x25, 0xa9, 0x79, 0xc5, 0x99, 0xf9, 0x79, 0xfe, 0x05, 0x25, 0x99, 0xf9, 0x79, 0xc5, 0xe1, 0xa9, + 0x49, 0xc6, 0x21, 0x15, 0x42, 0x89, 0x5c, 0xc2, 0x20, 0xcd, 0x29, 0xf1, 0x29, 0x89, 0x25, 0x89, + 0xf1, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xf1, 0x99, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x2c, 0x4e, + 0x46, 0x8f, 0xee, 0xc9, 0x0b, 0x84, 0x80, 0xa4, 0x5d, 0x12, 0x4b, 0x12, 0x9d, 0x41, 0x92, 0x9e, + 0x2e, 0xaf, 0xee, 0xc9, 0x4b, 0x95, 0xa0, 0x89, 0xe9, 0xe4, 0xe7, 0x66, 0x96, 0xa4, 0xe6, 0x16, + 0x94, 0x54, 0x06, 0x09, 0xa0, 0xc9, 0xa5, 0x08, 0x19, 0x73, 0x71, 0xa6, 0xa5, 0xa6, 0xc6, 0x17, + 0x24, 0x56, 0xa6, 0x16, 0x49, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x3a, 0x89, 0xbd, 0xba, 0x27, 0x2f, + 0x94, 0x96, 0x9a, 0x1a, 0x00, 0x12, 0x43, 0xd2, 0xcc, 0x01, 0x13, 0x13, 0xb2, 0xe5, 0xe2, 0x85, + 0x6b, 0x8a, 0x2f, 0xce, 0x4c, 0x97, 0x60, 0x56, 0x60, 0xd4, 0xe0, 0x71, 0x92, 0x7c, 0x75, 0x4f, + 0x5e, 0x14, 0xa6, 0x28, 0x38, 0x33, 0x1d, 0x49, 0x2f, 0x37, 0x92, 0xb0, 0x15, 0x4b, 0xc7, 0x02, + 0x79, 0x06, 0x27, 0xe3, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, + 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x92, 0x4c, + 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0x2d, 0xcb, 0xcd, 0x2f, 0xd6, + 0x87, 0x85, 0x6e, 0x12, 0x1b, 0x38, 0xcc, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x36, + 0x49, 0x21, 0x70, 0x01, 0x00, 0x00, } func (m *ExtensionOptionsWeb3Tx) Marshal() (dAtA []byte, err error) { From 2ac53cacccf8bf83229556aa41e8c71606368d2a Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:40:09 +0200 Subject: [PATCH 06/14] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad9c428..5deeff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This changelog was created using the `clu` binary ### Improvements +- (types) [#20](https://github.com/evmos/os/pull/20) Add crypto package. - (types) [#19](https://github.com/evmos/os/pull/19) Add required wallet types for integration. - (all) [#15](https://github.com/evmos/os/pull/15) Add general types and utils. - (proto) [#14](https://github.com/evmos/os/pull/14) Add Protobufs and adjust scripts. From d12c2a3e7b2b2d77cd2b8f93c2f4992ff30ba96c Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:42:54 +0200 Subject: [PATCH 07/14] change mention of Ethermint --- ethereum/eip712/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/eip712/preprocess.go b/ethereum/eip712/preprocess.go index 5ed4e7e..7daa33b 100644 --- a/ethereum/eip712/preprocess.go +++ b/ethereum/eip712/preprocess.go @@ -13,7 +13,7 @@ import ( "github.com/evmos/os/types" ) -// PreprocessLedgerTx reformats Ledger-signed Cosmos transactions to match the fork expected by Ethermint +// PreprocessLedgerTx reformats Ledger-signed Cosmos transactions to match the fork expected by evmOS // by including the signature in a Web3Tx extension and sending a blank signature in the body. func PreprocessLedgerTx(chainID string, keyType cosmoskr.KeyType, txBuilder client.TxBuilder) error { // Only process Ledger transactions From 5018c0b5d734de87f29c4995fb0a8ec69534836c Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:45:11 +0200 Subject: [PATCH 08/14] adjust package imports --- ethereum/eip712/eip712_test.go | 2 +- utils/utils.go | 2 +- utils/utils_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereum/eip712/eip712_test.go b/ethereum/eip712/eip712_test.go index 8d83f13..b9817d7 100644 --- a/ethereum/eip712/eip712_test.go +++ b/ethereum/eip712/eip712_test.go @@ -25,8 +25,8 @@ import ( "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/evmos/evmos/v19/app" "github.com/evmos/evmos/v19/cmd/config" - "github.com/evmos/evmos/v19/crypto/ethsecp256k1" "github.com/evmos/evmos/v19/encoding" + "github.com/evmos/os/crypto/ethsecp256k1" "github.com/evmos/os/ethereum/eip712" "github.com/evmos/os/testutil" "github.com/stretchr/testify/suite" diff --git a/utils/utils.go b/utils/utils.go index 07ac672..0af3ddb 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,7 +16,7 @@ import ( errortypes "github.com/cosmos/cosmos-sdk/types/errors" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/ethereum/go-ethereum/common" - "github.com/evmos/evmos/v19/crypto/ethsecp256k1" + "github.com/evmos/os/crypto/ethsecp256k1" "golang.org/x/exp/constraints" ) diff --git a/utils/utils_test.go b/utils/utils_test.go index 3ccbbbc..adc29bd 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -10,7 +10,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/evmos/evmos/v19/crypto/ethsecp256k1" + "github.com/evmos/os/crypto/ethsecp256k1" "github.com/stretchr/testify/require" ) From b770d55c5cb1d661feca066fb986915b2d21a92a Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:55:18 +0200 Subject: [PATCH 09/14] add encoding package --- encoding/codec/codec.go | 27 +++++++++++++++++++++++ encoding/config.go | 33 ++++++++++++++++++++++++++++ encoding/config_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 encoding/codec/codec.go create mode 100644 encoding/config.go create mode 100644 encoding/config_test.go diff --git a/encoding/codec/codec.go b/encoding/codec/codec.go new file mode 100644 index 0000000..c40946b --- /dev/null +++ b/encoding/codec/codec.go @@ -0,0 +1,27 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package codec + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/std" + sdk "github.com/cosmos/cosmos-sdk/types" + + cryptocodec "github.com/evmos/os/crypto/codec" + "github.com/evmos/os/types" +) + +// RegisterLegacyAminoCodec registers Interfaces from types, crypto, and SDK std. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + sdk.RegisterLegacyAminoCodec(cdc) + cryptocodec.RegisterCrypto(cdc) + codec.RegisterEvidences(cdc) +} + +// RegisterInterfaces registers Interfaces from types, crypto, and SDK std. +func RegisterInterfaces(interfaceRegistry codectypes.InterfaceRegistry) { + std.RegisterInterfaces(interfaceRegistry) + cryptocodec.RegisterInterfaces(interfaceRegistry) + types.RegisterInterfaces(interfaceRegistry) +} diff --git a/encoding/config.go b/encoding/config.go new file mode 100644 index 0000000..89aa095 --- /dev/null +++ b/encoding/config.go @@ -0,0 +1,33 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package encoding + +import ( + "cosmossdk.io/simapp/params" + amino "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + + enccodec "github.com/evmos/os/encoding/codec" +) + +// MakeConfig creates an EncodingConfig for testing +func MakeConfig(mb module.BasicManager) params.EncodingConfig { + cdc := amino.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + codec := amino.NewProtoCodec(interfaceRegistry) + + encodingConfig := params.EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: codec, + TxConfig: tx.NewTxConfig(codec, tx.DefaultSignModes), + Amino: cdc, + } + + enccodec.RegisterLegacyAminoCodec(encodingConfig.Amino) + mb.RegisterLegacyAminoCodec(encodingConfig.Amino) + enccodec.RegisterInterfaces(encodingConfig.InterfaceRegistry) + mb.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig +} diff --git a/encoding/config_test.go b/encoding/config_test.go new file mode 100644 index 0000000..855a65d --- /dev/null +++ b/encoding/config_test.go @@ -0,0 +1,48 @@ +package encoding_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/evmos/evmos/v19/app" + utiltx "github.com/evmos/evmos/v19/testutil/tx" + evmtypes "github.com/evmos/evmos/v19/x/evm/types" + "github.com/evmos/os/encoding" +) + +func TestTxEncoding(t *testing.T) { + addr, key := utiltx.NewAddrKey() + signer := utiltx.NewSigner(key) + + ethTxParams := evmtypes.EvmTxArgs{ + ChainID: big.NewInt(1), + Nonce: 1, + Amount: big.NewInt(10), + GasLimit: 100000, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Input: []byte{}, + } + msg := evmtypes.NewTx(ðTxParams) + msg.From = addr.Hex() + + ethSigner := ethtypes.LatestSignerForChainID(big.NewInt(1)) + err := msg.Sign(ethSigner, signer) + require.NoError(t, err) + + cfg := encoding.MakeConfig(app.ModuleBasics) + + _, err = cfg.TxConfig.TxEncoder()(msg) + require.Error(t, err, "encoding failed") + + // FIXME: transaction hashing is hardcoded on Tendermint: + // See https://github.com/cometbft/cometbft/issues/6539 for reference + // txHash := msg.AsTransaction().Hash() + // tmTx := tmtypes.Tx(bz) + + // require.Equal(t, txHash.Bytes(), tmTx.Hash()) +} From 2459447e29f4237908cabe663d972bdc18d836f6 Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:55:52 +0200 Subject: [PATCH 10/14] replace import paths for encoding --- crypto/hd/algorithm_test.go | 2 +- ethereum/eip712/eip712_test.go | 2 +- ethereum/eip712/preprocess_test.go | 2 +- wallets/ledger/ledger_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/hd/algorithm_test.go b/crypto/hd/algorithm_test.go index 103f186..3c88c95 100644 --- a/crypto/hd/algorithm_test.go +++ b/crypto/hd/algorithm_test.go @@ -13,8 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" - enccodec "github.com/evmos/evmos/v19/encoding/codec" cryptocodec "github.com/evmos/os/crypto/codec" + enccodec "github.com/evmos/os/encoding/codec" evmostypes "github.com/evmos/os/types" ) diff --git a/ethereum/eip712/eip712_test.go b/ethereum/eip712/eip712_test.go index b9817d7..cbedc7c 100644 --- a/ethereum/eip712/eip712_test.go +++ b/ethereum/eip712/eip712_test.go @@ -25,8 +25,8 @@ import ( "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/evmos/evmos/v19/app" "github.com/evmos/evmos/v19/cmd/config" - "github.com/evmos/evmos/v19/encoding" "github.com/evmos/os/crypto/ethsecp256k1" + "github.com/evmos/os/encoding" "github.com/evmos/os/ethereum/eip712" "github.com/evmos/os/testutil" "github.com/stretchr/testify/suite" diff --git a/ethereum/eip712/preprocess_test.go b/ethereum/eip712/preprocess_test.go index 42a1aef..b8d3f56 100644 --- a/ethereum/eip712/preprocess_test.go +++ b/ethereum/eip712/preprocess_test.go @@ -16,8 +16,8 @@ import ( "github.com/evmos/evmos/v19/app" "github.com/evmos/evmos/v19/cmd/config" - "github.com/evmos/evmos/v19/encoding" utiltx "github.com/evmos/evmos/v19/testutil/tx" + "github.com/evmos/os/encoding" "github.com/evmos/os/ethereum/eip712" "github.com/evmos/os/testutil" "github.com/evmos/os/types" diff --git a/wallets/ledger/ledger_test.go b/wallets/ledger/ledger_test.go index 957214a..d1eb54e 100644 --- a/wallets/ledger/ledger_test.go +++ b/wallets/ledger/ledger_test.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/evmos/evmos/v19/app" - "github.com/evmos/evmos/v19/encoding" + "github.com/evmos/os/encoding" "github.com/evmos/os/ethereum/eip712" "github.com/evmos/os/wallets/accounts" "github.com/evmos/os/wallets/ledger" From 464216d766b3735dd3d5521952eef5b6bdcf30bb Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Fri, 2 Aug 2024 22:56:33 +0200 Subject: [PATCH 11/14] adjust changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5deeff0..4d5730a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ This changelog was created using the `clu` binary ### Improvements -- (types) [#20](https://github.com/evmos/os/pull/20) Add crypto package. +- (types) [#20](https://github.com/evmos/os/pull/20) Add crypto and encoding packages. - (types) [#19](https://github.com/evmos/os/pull/19) Add required wallet types for integration. - (all) [#15](https://github.com/evmos/os/pull/15) Add general types and utils. - (proto) [#14](https://github.com/evmos/os/pull/14) Add Protobufs and adjust scripts. From 3280821c0dbb746b1f5f30a5544ead488a225d77 Mon Sep 17 00:00:00 2001 From: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:04:40 +0200 Subject: [PATCH 12/14] Update crypto/ethsecp256k1/ethsecp256k1.go --- crypto/ethsecp256k1/ethsecp256k1.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/ethsecp256k1/ethsecp256k1.go b/crypto/ethsecp256k1/ethsecp256k1.go index f9358c0..74a0b8d 100644 --- a/crypto/ethsecp256k1/ethsecp256k1.go +++ b/crypto/ethsecp256k1/ethsecp256k1.go @@ -30,9 +30,9 @@ const ( // Amino encoding names const ( // PrivKeyName defines the amino encoding name for the EthSecp256k1 private key - PrivKeyName = "ethermint/PrivKeyEthSecp256k1" + PrivKeyName = "os/PrivKeyEthSecp256k1" // PubKeyName defines the amino encoding name for the EthSecp256k1 public key - PubKeyName = "ethermint/PubKeyEthSecp256k1" + PubKeyName = "os/PubKeyEthSecp256k1" ) // ---------------------------------------------------------------------------- From e15ca8996c2822de02ae3fd1109b9fbc176cb379 Mon Sep 17 00:00:00 2001 From: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:09:19 +0200 Subject: [PATCH 13/14] Update encoding/config.go --- encoding/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoding/config.go b/encoding/config.go index 89aa095..4bf8ea4 100644 --- a/encoding/config.go +++ b/encoding/config.go @@ -12,7 +12,7 @@ import ( enccodec "github.com/evmos/os/encoding/codec" ) -// MakeConfig creates an EncodingConfig for testing +// MakeConfig creates an EncodingConfig for the given basic module manager. func MakeConfig(mb module.BasicManager) params.EncodingConfig { cdc := amino.NewLegacyAmino() interfaceRegistry := types.NewInterfaceRegistry() From 69019f6626e3dfc8002abca00dad7a1fbe949f35 Mon Sep 17 00:00:00 2001 From: MalteHerrmann Date: Mon, 5 Aug 2024 14:12:55 +0200 Subject: [PATCH 14/14] replace mentions of Evmos with evmOS --- .github/workflows/bsr-push.yml | 2 +- .github/workflows/lint.yml | 2 +- Makefile | 4 ++-- crypto/codec/codec.go | 2 +- crypto/keyring/options.go | 2 +- ethereum/eip712/preprocess.go | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/bsr-push.yml b/.github/workflows/bsr-push.yml index 2606a4b..a76381a 100644 --- a/.github/workflows/bsr-push.yml +++ b/.github/workflows/bsr-push.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: bufbuild/buf-setup-action@v1.35.1 - # Push Evmos protos to the Buf Schema Registry + # Push evmOS protos to the Buf Schema Registry - uses: bufbuild/buf-push-action@v1.2.0 with: input: ./proto diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d9a7e9f..8d1b8de 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,5 @@ name: Lint -# Lint runs golangci-lint over the entire Evmos repository This workflow is +# Lint runs golangci-lint over the entire evmOS repository This workflow is # run on every pull request and push to main The `golangci` will pass without # running if no *.{go, mod, sum} files have been changed. on: diff --git a/Makefile b/Makefile index adbcc4b..0cc9d06 100644 --- a/Makefile +++ b/Makefile @@ -186,7 +186,7 @@ release: ############################################################################### # Install the necessary dependencies, compile the solidity contracts found in the -# Evmos repository and then clean up the contracts data. +# evmOS repository and then clean up the contracts data. contracts-all: contracts-compile contracts-clean # Clean smart contract compilation artifacts, dependencies and cache files @@ -194,7 +194,7 @@ contracts-clean: @echo "Cleaning up the contracts directory..." @python3 ./scripts/compile_smart_contracts/compile_smart_contracts.py --clean -# Compile the solidity contracts found in the Evmos repository. +# Compile the solidity contracts found in the evmOS repository. contracts-compile: @echo "Compiling smart contracts..." @python3 ./scripts/compile_smart_contracts/compile_smart_contracts.py --compile diff --git a/crypto/codec/codec.go b/crypto/codec/codec.go index bba29d7..015d2e7 100644 --- a/crypto/codec/codec.go +++ b/crypto/codec/codec.go @@ -9,7 +9,7 @@ import ( "github.com/evmos/os/crypto/ethsecp256k1" ) -// RegisterInterfaces register the Evmos key concrete types. +// RegisterInterfaces register the evmOS 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/crypto/keyring/options.go b/crypto/keyring/options.go index 27cafea..3b78dee 100644 --- a/crypto/keyring/options.go +++ b/crypto/keyring/options.go @@ -20,7 +20,7 @@ var ( // SupportedAlgorithms defines the list of signing algorithms used on evmOS: // - eth_secp256k1 (Ethereum) SupportedAlgorithms = keyring.SigningAlgoList{hd.EthSecp256k1} - // SupportedAlgorithmsLedger defines the list of signing algorithms used on Evmos for the Ledger device: + // SupportedAlgorithmsLedger defines the list of signing algorithms used by evmOS 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} diff --git a/ethereum/eip712/preprocess.go b/ethereum/eip712/preprocess.go index 7daa33b..b2bf6d6 100644 --- a/ethereum/eip712/preprocess.go +++ b/ethereum/eip712/preprocess.go @@ -65,7 +65,7 @@ func PreprocessLedgerTx(chainID string, keyType cosmoskr.KeyType, txBuilder clie extensionBuilder.SetExtensionOptions(option) // Set blank signature with Amino Sign Type - // (Regardless of input signMode, Evmos requires Amino signature type for Ledger) + // (Regardless of input signMode, evmOS requires Amino signature type for Ledger) blankSig := signing.SingleSignatureData{ SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, Signature: nil,