-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9c637f2
commit 2d0025e
Showing
11 changed files
with
2,066 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
} |
Oops, something went wrong.