accounts, cmd, eth, internal, miner, node: wallets and HD APIs
This commit is contained in:
@ -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
|
||||
Address common.Address `json:"address"` // Ethereum account address derived from the key
|
||||
URL string `json:"url"` // Optional resource locator within a backend
|
||||
}
|
||||
|
||||
func (acc *Account) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + acc.Address.Hex() + `"`), nil
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (acc *Account) UnmarshalJSON(raw []byte) error {
|
||||
return json.Unmarshal(raw, &acc.Address)
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
Reference in New Issue
Block a user