From fad5eb0a87abfc12812647344a26de8a43830182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 7 Feb 2017 12:47:34 +0200 Subject: accounts, cmd, eth, internal, miner, node: wallets and HD APIs --- accounts/accounts.go | 256 ++++++++++++++++++++++----------------------------- 1 file changed, 111 insertions(+), 145 deletions(-) (limited to 'accounts/accounts.go') diff --git a/accounts/accounts.go b/accounts/accounts.go index 234b6e456..b367ee2f4 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -18,162 +18,128 @@ package accounts import ( - "encoding/json" - "errors" "math/big" - "reflect" - "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" ) -// ErrUnknownAccount is returned for any requested operation for which no backend -// provides the specified account. -var ErrUnknownAccount = errors.New("unknown account") - -// ErrNotSupported is returned when an operation is requested from an account -// backend that it does not support. -var ErrNotSupported = errors.New("not supported") - -// Account represents a stored key. -// When used as an argument, it selects a unique key to act on. +// Account represents an Ethereum account located at a specific location defined +// by the optional URL field. type Account struct { - Address common.Address // Ethereum account address derived from the key - URL string // Optional resource locator within a backend - backend Backend // Backend where this account originates from -} - -func (acc *Account) MarshalJSON() ([]byte, error) { - return []byte(`"` + acc.Address.Hex() + `"`), nil -} - -func (acc *Account) UnmarshalJSON(raw []byte) error { - return json.Unmarshal(raw, &acc.Address) -} - -// Manager is an overarching account manager that can communicate with various -// backends for signing transactions. -type Manager struct { - backends []Backend // List of currently registered backends (ordered by registration) - index map[reflect.Type]Backend // Set of currently registered backends - lock sync.RWMutex -} - -// NewManager creates a generic account manager to sign transaction via various -// supported backends. -func NewManager(backends ...Backend) *Manager { - am := &Manager{ - backends: backends, - index: make(map[reflect.Type]Backend), - } - for _, backend := range backends { - am.index[reflect.TypeOf(backend)] = backend - } - return am -} - -// Backend retrieves the backend with the given type from the account manager. -func (am *Manager) Backend(backend reflect.Type) Backend { - return am.index[backend] + Address common.Address `json:"address"` // Ethereum account address derived from the key + URL string `json:"url"` // Optional resource locator within a backend } -// Accounts returns all signer accounts registered under this account manager. -func (am *Manager) Accounts() []Account { - am.lock.RLock() - defer am.lock.RUnlock() - - var all []Account - for _, backend := range am.backends { // TODO(karalabe): cache these after subscriptions are in - accounts := backend.Accounts() - for i := 0; i < len(accounts); i++ { - accounts[i].backend = backend - } - all = append(all, accounts...) - } - return all +// Wallet represents a software or hardware wallet that might contain one or more +// accounts (derived from the same seed). +type Wallet interface { + // Type retrieves a textual representation of the type of the wallet. + Type() string + + // URL retrieves the canonical path under which this wallet is reachable. It is + // user by upper layers to define a sorting order over all wallets from multiple + // backends. + URL() string + + // Status returns a textual status to aid the user in the current state of the + // wallet. + Status() string + + // 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 passwordless 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 string, pin bool) (Account, error) + + // SignHash requests the wallet to sign the given hash. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignHash(account Account, hash []byte) ([]byte, error) + + // SignTx requests the wallet to sign the given transaction. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + + // SignHashWithPassphrase requests the wallet to sign the given hash with the + // given passphrase as extra authentication information. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) + + // SignTxWithPassphrase requests the wallet to sign the given transaction, with the + // given passphrase as extra authentication information. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) } -// HasAddress reports whether a key with the given address is present. -func (am *Manager) HasAddress(addr common.Address) bool { - am.lock.RLock() - defer am.lock.RUnlock() - - for _, backend := range am.backends { - if backend.HasAddress(addr) { - return true - } - } - return false +// 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 + + // Subscribe creates an async subscription to receive notifications when the + // backend detects the arrival or departure of a wallet. + Subscribe(sink chan<- WalletEvent) event.Subscription } -// SignHash requests the account manager to get the hash signed with an arbitrary -// signing backend holding the authorization for the specified account. -func (am *Manager) SignHash(acc Account, hash []byte) ([]byte, error) { - am.lock.RLock() - defer am.lock.RUnlock() - - if err := am.ensureBackend(&acc); err != nil { - return nil, err - } - return acc.backend.SignHash(acc, hash) -} - -// SignTx requests the account manager to get the transaction signed with an -// arbitrary signing backend holding the authorization for the specified account. -func (am *Manager) SignTx(acc Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - am.lock.RLock() - defer am.lock.RUnlock() - - if err := am.ensureBackend(&acc); err != nil { - return nil, err - } - return acc.backend.SignTx(acc, tx, chainID) -} - -// SignHashWithPassphrase requests the account manager to get the hash signed with -// an arbitrary signing backend holding the authorization for the specified account. -func (am *Manager) SignHashWithPassphrase(acc Account, passphrase string, hash []byte) ([]byte, error) { - am.lock.RLock() - defer am.lock.RUnlock() - - if err := am.ensureBackend(&acc); err != nil { - return nil, err - } - return acc.backend.SignHashWithPassphrase(acc, passphrase, hash) -} - -// SignTxWithPassphrase requests the account manager to get the transaction signed -// with an arbitrary signing backend holding the authorization for the specified -// account. -func (am *Manager) SignTxWithPassphrase(acc Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - am.lock.RLock() - defer am.lock.RUnlock() - - if err := am.ensureBackend(&acc); err != nil { - return nil, err - } - return acc.backend.SignTxWithPassphrase(acc, passphrase, tx, chainID) -} - -// ensureBackend ensures that the account has a correctly set backend and that -// it is still alive. -// -// Please note, this method assumes the manager lock is held! -func (am *Manager) ensureBackend(acc *Account) error { - // If we have a backend, make sure it's still live - if acc.backend != nil { - if _, exists := am.index[reflect.TypeOf(acc.backend)]; !exists { - return ErrUnknownAccount - } - return nil - } - // If we don't have a known backend, look up one that can service it - for _, backend := range am.backends { - if backend.HasAddress(acc.Address) { // TODO(karalabe): this assumes unique addresses per backend - acc.backend = backend - return nil - } - } - return ErrUnknownAccount +// WalletEvent is an event fired by an account backend when a wallet arrival or +// departure is detected. +type WalletEvent struct { + Wallet Wallet // Wallet instance arrived or departed + Arrive bool // Whether the wallet was added or removed } -- cgit v1.2.3