aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/accounts.go
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/accounts.go')
-rw-r--r--accounts/accounts.go256
1 files changed, 111 insertions, 145 deletions
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
}