lbcwallet/waddrmgr/manager.go
2015-01-16 18:08:45 -06:00

2006 lines
64 KiB
Go

/*
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package waddrmgr
import (
"fmt"
"sync"
"github.com/btcsuite/btcec"
"github.com/btcsuite/btcnet"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwire"
"github.com/conformal/btcwallet/snacl"
"github.com/conformal/btcwallet/walletdb"
)
const (
// MaxAccountNum is the maximum allowed account number. This value was
// chosen because accounts are hardened children and therefore must
// not exceed the hardened child range of extended keys and it provides
// a reserved account at the top of the range for supporting imported
// addresses.
MaxAccountNum = hdkeychain.HardenedKeyStart - 2 // 2^31 - 2
// MaxAddressesPerAccount is the maximum allowed number of addresses
// per account number. This value is based on the limitation of
// the underlying hierarchical deterministic key derivation.
MaxAddressesPerAccount = hdkeychain.HardenedKeyStart - 1
// importedAddrAccount is the account number to use for all imported
// addresses. This is useful since normal accounts are derived from the
// root hierarchical deterministic key and imported addresses do not
// fit into that model.
ImportedAddrAccount = MaxAccountNum + 1 // 2^31 - 1
// defaultAccountNum is the number of the default account.
defaultAccountNum = 0
// The hierarchy described by BIP0043 is:
// m/<purpose>'/*
// This is further extended by BIP0044 to:
// m/44'/<coin type>'/<account>'/<branch>/<address index>
//
// The branch is 0 for external addresses and 1 for internal addresses.
// maxCoinType is the maximum allowed coin type used when structuring
// the BIP0044 multi-account hierarchy. This value is based on the
// limitation of the underlying hierarchical deterministic key
// derivation.
maxCoinType = hdkeychain.HardenedKeyStart - 1
// externalBranch is the child number to use when performing BIP0044
// style hierarchical deterministic key derivation for the external
// branch.
externalBranch uint32 = 0
// internalBranch is the child number to use when performing BIP0044
// style hierarchical deterministic key derivation for the internal
// branch.
internalBranch uint32 = 1
)
// Options is used to hold the optional parameters passed to Create or Load.
type Options struct {
ScryptN int
ScryptR int
ScryptP int
}
// defaultConfig is an instance of the Options struct initialized with default
// configuration options.
var defaultConfig = &Options{
ScryptN: 262144, // 2^18
ScryptR: 8,
ScryptP: 1,
}
// addrKey is used to uniquely identify an address even when those addresses
// would end up being the same bitcoin address (as is the case for pay-to-pubkey
// and pay-to-pubkey-hash style of addresses).
type addrKey string
// accountInfo houses the current state of the internal and external branches
// of an account along with the extended keys needed to derive new keys. It
// also handles locking by keeping an encrypted version of the serialized
// private extended key so the unencrypted versions can be cleared from memory
// when the address manager is locked.
type accountInfo struct {
// The account key is used to derive the branches which in turn derive
// the internal and external addresses.
// The accountKeyPriv will be nil when the address manager is locked.
acctKeyEncrypted []byte
acctKeyPriv *hdkeychain.ExtendedKey
acctKeyPub *hdkeychain.ExtendedKey
// The external branch is used for all addresses which are intended
// for external use.
nextExternalIndex uint32
lastExternalAddr ManagedAddress
// The internal branch is used for all adddresses which are only
// intended for internal wallet use such as change addresses.
nextInternalIndex uint32
lastInternalAddr ManagedAddress
}
// unlockDeriveInfo houses the information needed to derive a private key for a
// managed address when the address manager is unlocked. See the deriveOnUnlock
// field in the Manager struct for more details on how this is used.
type unlockDeriveInfo struct {
managedAddr *managedAddress
branch uint32
index uint32
}
// defaultNewSecretKey returns a new secret key. See newSecretKey.
func defaultNewSecretKey(passphrase *[]byte, config *Options) (*snacl.SecretKey, error) {
return snacl.NewSecretKey(passphrase, config.ScryptN, config.ScryptR,
config.ScryptP)
}
// newSecretKey is used as a way to replace the new secret key generation
// function used so tests can provide a version that fails for testing error
// paths.
var newSecretKey = defaultNewSecretKey
// EncryptorDecryptor provides an abstraction on top of snacl.CryptoKey so that our
// tests can use dependency injection to force the behaviour they need.
type EncryptorDecryptor interface {
Encrypt(in []byte) ([]byte, error)
Decrypt(in []byte) ([]byte, error)
Bytes() []byte
CopyBytes([]byte)
Zero()
}
// cryptoKey extends snacl.CryptoKey to implement EncryptorDecryptor.
type cryptoKey struct {
snacl.CryptoKey
}
// Bytes returns a copy of this crypto key's byte slice.
func (ck *cryptoKey) Bytes() []byte {
return ck.CryptoKey[:]
}
// CopyBytes copies the bytes from the given slice into this CryptoKey.
func (ck *cryptoKey) CopyBytes(from []byte) {
copy(ck.CryptoKey[:], from)
}
// defaultNewCryptoKey returns a new CryptoKey. See newCryptoKey.
func defaultNewCryptoKey() (EncryptorDecryptor, error) {
key, err := snacl.GenerateCryptoKey()
if err != nil {
return nil, err
}
return &cryptoKey{*key}, nil
}
// CryptoKeyType is used to differentiate between different kinds of
// crypto keys.
type CryptoKeyType byte
// Crypto key types.
const (
// CKTPrivate specifies the key that is used for encryption of private
// key material such as derived extended private keys and imported
// private keys.
CKTPrivate CryptoKeyType = iota
// CKTScript specifies the key that is used for encryption of scripts.
CKTScript
// CKTPublic specifies the key that is used for encryption of public
// key material such as dervied extended public keys and imported public
// keys.
CKTPublic
)
// newCryptoKey is used as a way to replace the new crypto key generation
// function used so tests can provide a version that fails for testing error
// paths.
var newCryptoKey = defaultNewCryptoKey
// Manager represents a concurrency safe crypto currency address manager and
// key store.
type Manager struct {
mtx sync.RWMutex
namespace walletdb.Namespace
net *btcnet.Params
addrs map[addrKey]ManagedAddress
syncState syncState
watchingOnly bool
locked bool
closed bool
// acctInfo houses information about accounts including what is needed
// to generate deterministic chained keys for each created account.
acctInfo map[uint32]*accountInfo
// masterKeyPub is the secret key used to secure the cryptoKeyPub key
// and masterKeyPriv is the secret key used to secure the cryptoKeyPriv
// key. This approach is used because it makes changing the passwords
// much simpler as it then becomes just changing these keys. It also
// provides future flexibility.
//
// NOTE: This is not the same thing as BIP0032 master node extended
// key.
//
// The underlying master private key will be zeroed when the address
// manager is locked.
masterKeyPub *snacl.SecretKey
masterKeyPriv *snacl.SecretKey
// cryptoKeyPub is the key used to encrypt public extended keys and
// addresses.
cryptoKeyPub EncryptorDecryptor
// cryptoKeyPriv is the key used to encrypt private data such as the
// master hierarchical deterministic extended key.
//
// This key will be zeroed when the address manager is locked.
cryptoKeyPrivEncrypted []byte
cryptoKeyPriv EncryptorDecryptor
// cryptoKeyScript is the key used to encrypt script data.
//
// This key will be zeroed when the address manager is locked.
cryptoKeyScriptEncrypted []byte
cryptoKeyScript EncryptorDecryptor
// deriveOnUnlock is a list of private keys which needs to be derived
// on the next unlock. This occurs when a public address is derived
// while the address manager is locked since it does not have access to
// the private extended key (hence nor the underlying private key) in
// order to encrypt it.
deriveOnUnlock []*unlockDeriveInfo
// config holds overridable options, such as scrypt parameters.
config *Options
}
// lock performs a best try effort to remove and zero all secret keys associated
// with the address manager.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) lock() {
// Clear all of the account private keys.
for _, acctInfo := range m.acctInfo {
if acctInfo.acctKeyPriv != nil {
acctInfo.acctKeyPriv.Zero()
}
acctInfo.acctKeyPriv = nil
}
// Remove clear text private keys and scripts from all address entries.
for _, ma := range m.addrs {
switch addr := ma.(type) {
case *managedAddress:
addr.lock()
case *scriptAddress:
addr.lock()
}
}
// Remove clear text private master and crypto keys from memory.
m.cryptoKeyScript.Zero()
m.cryptoKeyPriv.Zero()
m.masterKeyPriv.Zero()
// NOTE: m.cryptoKeyPub is intentionally not cleared here as the address
// manager needs to be able to continue to read and decrypt public data
// which uses a separate derived key from the database even when it is
// locked.
m.locked = true
}
// zeroSensitivePublicData performs a best try effort to remove and zero all
// sensitive public data associated with the address manager such as
// hierarchical deterministic extended public keys and the crypto public keys.
func (m *Manager) zeroSensitivePublicData() {
// Clear all of the account private keys.
for _, acctInfo := range m.acctInfo {
acctInfo.acctKeyPub.Zero()
acctInfo.acctKeyPub = nil
}
// Remove clear text public master and crypto keys from memory.
m.cryptoKeyPub.Zero()
m.masterKeyPub.Zero()
}
// Close cleanly shuts down the manager. It makes a best try effort to remove
// and zero all private key and sensitive public key material associated with
// the address manager from memory.
func (m *Manager) Close() error {
m.mtx.Lock()
defer m.mtx.Unlock()
// Attempt to clear private key material from memory.
if !m.watchingOnly && !m.locked {
m.lock()
}
// Attempt to clear sensitive public key material from memory too.
m.zeroSensitivePublicData()
m.closed = true
return nil
}
// keyToManaged returns a new managed address for the provided derived key and
// its derivation path which consists of the account, branch, and index.
//
// The passed derivedKey is zeroed after the new address is created.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, account, branch, index uint32) (ManagedAddress, error) {
// Create a new managed address based on the public or private key
// depending on whether the passed key is private. Also, zero the
// key after creating the managed address from it.
ma, err := newManagedAddressFromExtKey(m, account, derivedKey)
defer derivedKey.Zero()
if err != nil {
return nil, err
}
if !derivedKey.IsPrivate() {
// Add the managed address to the list of addresses that need
// their private keys derived when the address manager is next
// unlocked.
info := unlockDeriveInfo{
managedAddr: ma,
branch: branch,
index: index,
}
m.deriveOnUnlock = append(m.deriveOnUnlock, &info)
}
if branch == internalBranch {
ma.internal = true
}
return ma, nil
}
// deriveKey returns either a public or private derived extended key based on
// the private flag for the given an account info, branch, and index.
func (m *Manager) deriveKey(acctInfo *accountInfo, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
// Choose the public or private extended key based on whether or not
// the private flag was specified. This, in turn, allows for public or
// private child derivation.
acctKey := acctInfo.acctKeyPub
if private {
acctKey = acctInfo.acctKeyPriv
}
// Derive and return the key.
branchKey, err := acctKey.Child(branch)
if err != nil {
str := fmt.Sprintf("failed to derive extended key branch %d",
branch)
return nil, managerError(ErrKeyChain, str, err)
}
addressKey, err := branchKey.Child(index)
branchKey.Zero() // Zero branch key after it's used.
if err != nil {
str := fmt.Sprintf("failed to derive child extended key -- "+
"branch %d, child %d",
branch, index)
return nil, managerError(ErrKeyChain, str, err)
}
return addressKey, nil
}
// loadAccountInfo attempts to load and cache information about the given
// account from the database. This includes what is necessary to derive new
// keys for it and track the state of the internal and external branches.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) {
// Return the account info from cache if it's available.
if acctInfo, ok := m.acctInfo[account]; ok {
return acctInfo, nil
}
// The account is either invalid or just wasn't cached, so attempt to
// load the information from the database.
var rowInterface interface{}
err := m.namespace.View(func(tx walletdb.Tx) error {
var err error
rowInterface, err = fetchAccountInfo(tx, account)
return err
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// Ensure the account type is a BIP0044 account.
row, ok := rowInterface.(*dbBIP0044AccountRow)
if !ok {
str := fmt.Sprintf("unsupported account type %T", row)
err = managerError(ErrDatabase, str, nil)
}
// Use the crypto public key to decrypt the account public extended key.
serializedKeyPub, err := m.cryptoKeyPub.Decrypt(row.pubKeyEncrypted)
if err != nil {
str := fmt.Sprintf("failed to decrypt public key for account %d",
account)
return nil, managerError(ErrCrypto, str, err)
}
acctKeyPub, err := hdkeychain.NewKeyFromString(string(serializedKeyPub))
if err != nil {
str := fmt.Sprintf("failed to create extended public key for "+
"account %d", account)
return nil, managerError(ErrKeyChain, str, err)
}
// Create the new account info with the known information. The rest
// of the fields are filled out below.
acctInfo := &accountInfo{
acctKeyEncrypted: row.privKeyEncrypted,
acctKeyPub: acctKeyPub,
nextExternalIndex: row.nextExternalIndex,
nextInternalIndex: row.nextInternalIndex,
}
if !m.locked {
// Use the crypto private key to decrypt the account private
// extended keys.
decrypted, err := m.cryptoKeyPriv.Decrypt(acctInfo.acctKeyEncrypted)
if err != nil {
str := fmt.Sprintf("failed to decrypt private key for "+
"account %d", account)
return nil, managerError(ErrCrypto, str, err)
}
acctKeyPriv, err := hdkeychain.NewKeyFromString(string(decrypted))
if err != nil {
str := fmt.Sprintf("failed to create extended private "+
"key for account %d", account)
return nil, managerError(ErrKeyChain, str, err)
}
acctInfo.acctKeyPriv = acctKeyPriv
}
// Derive and cache the managed address for the last external address.
branch, index := externalBranch, row.nextExternalIndex
if index > 0 {
index--
}
lastExtKey, err := m.deriveKey(acctInfo, branch, index, !m.locked)
if err != nil {
return nil, err
}
lastExtAddr, err := m.keyToManaged(lastExtKey, account, branch, index)
if err != nil {
return nil, err
}
acctInfo.lastExternalAddr = lastExtAddr
// Derive and cache the managed address for the last internal address.
branch, index = internalBranch, row.nextInternalIndex
if index > 0 {
index--
}
lastIntKey, err := m.deriveKey(acctInfo, branch, index, !m.locked)
if err != nil {
return nil, err
}
lastIntAddr, err := m.keyToManaged(lastIntKey, account, branch, index)
if err != nil {
return nil, err
}
acctInfo.lastInternalAddr = lastIntAddr
// Add it to the cache and return it when everything is successful.
m.acctInfo[account] = acctInfo
return acctInfo, nil
}
// deriveKeyFromPath returns either a public or private derived extended key
// based on the private flag for the given an account, branch, and index.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) deriveKeyFromPath(account, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
// Look up the account key information.
acctInfo, err := m.loadAccountInfo(account)
if err != nil {
return nil, err
}
return m.deriveKey(acctInfo, branch, index, private)
}
// chainAddressRowToManaged returns a new managed address based on chained
// address data loaded from the database.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) chainAddressRowToManaged(row *dbChainAddressRow) (ManagedAddress, error) {
addressKey, err := m.deriveKeyFromPath(row.account, row.branch,
row.index, !m.locked)
if err != nil {
return nil, err
}
return m.keyToManaged(addressKey, row.account, row.branch, row.index)
}
// importedAddressRowToManaged returns a new managed address based on imported
// address data loaded from the database.
func (m *Manager) importedAddressRowToManaged(row *dbImportedAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported public key.
pubBytes, err := m.cryptoKeyPub.Decrypt(row.encryptedPubKey)
if err != nil {
str := "failed to decrypt public key for imported address"
return nil, managerError(ErrCrypto, str, err)
}
pubKey, err := btcec.ParsePubKey(pubBytes, btcec.S256())
if err != nil {
str := "invalid public key for imported address"
return nil, managerError(ErrCrypto, str, err)
}
compressed := len(pubBytes) == btcec.PubKeyBytesLenCompressed
ma, err := newManagedAddressWithoutPrivKey(m, row.account, pubKey,
compressed)
if err != nil {
return nil, err
}
ma.privKeyEncrypted = row.encryptedPrivKey
ma.imported = true
return ma, nil
}
// scriptAddressRowToManaged returns a new managed address based on script
// address data loaded from the database.
func (m *Manager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash.
scriptHash, err := m.cryptoKeyPub.Decrypt(row.encryptedHash)
if err != nil {
str := "failed to decrypt imported script hash"
return nil, managerError(ErrCrypto, str, err)
}
return newScriptAddress(m, row.account, scriptHash, row.encryptedScript)
}
// rowInterfaceToManaged returns a new managed address based on the given
// address data loaded from the database. It will automatically select the
// appropriate type.
func (m *Manager) rowInterfaceToManaged(rowInterface interface{}) (ManagedAddress, error) {
switch row := rowInterface.(type) {
case *dbChainAddressRow:
return m.chainAddressRowToManaged(row)
case *dbImportedAddressRow:
return m.importedAddressRowToManaged(row)
case *dbScriptAddressRow:
return m.scriptAddressRowToManaged(row)
}
str := fmt.Sprintf("unsupported address type %T", rowInterface)
return nil, managerError(ErrDatabase, str, nil)
}
// loadAndCacheAddress attempts to load the passed address from the database and
// caches the associated managed address.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) loadAndCacheAddress(address btcutil.Address) (ManagedAddress, error) {
// Attempt to load the raw address information from the database.
var rowInterface interface{}
err := m.namespace.View(func(tx walletdb.Tx) error {
var err error
rowInterface, err = fetchAddress(tx, address.ScriptAddress())
return err
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// Create a new managed address for the specific type of address based
// on type.
managedAddr, err := m.rowInterfaceToManaged(rowInterface)
if err != nil {
return nil, err
}
// Cache and return the new managed address.
m.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
return managedAddr, nil
}
// Address returns a managed address given the passed address if it is known
// to the address manager. A managed address differs from the passed address
// in that it also potentially contains extra information needed to sign
// transactions such as the associated private key for pay-to-pubkey and
// pay-to-pubkey-hash addresses and the script associated with
// pay-to-script-hash addresses.
func (m *Manager) Address(address btcutil.Address) (ManagedAddress, error) {
// Return the address from cache if it's available.
//
// NOTE: Not using a defer on the lock here since a write lock is
// needed if the lookup fails.
m.mtx.RLock()
if ma, ok := m.addrs[addrKey(address.ScriptAddress())]; ok {
m.mtx.RUnlock()
return ma, nil
}
m.mtx.RUnlock()
m.mtx.Lock()
defer m.mtx.Unlock()
// Attempt to load the address from the database.
return m.loadAndCacheAddress(address)
}
// ChangePassphrase changes either the public or private passphrase to the
// provided value depending on the private flag. In order to change the private
// password, the address manager must not be watching-only.
func (m *Manager) ChangePassphrase(oldPassphrase, newPassphrase []byte, private bool) error {
// No private passphrase to change for a watching-only address manager.
if private && m.watchingOnly {
return managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
m.mtx.Lock()
defer m.mtx.Unlock()
// Ensure the provided old passphrase is correct. This check is done
// using a copy of the appropriate master key depending on the private
// flag to ensure the current state is not altered. The temp key is
// cleared when done to avoid leaving a copy in memory.
var keyName string
secretKey := snacl.SecretKey{Key: &snacl.CryptoKey{}}
if private {
keyName = "private"
secretKey.Parameters = m.masterKeyPriv.Parameters
} else {
keyName = "public"
secretKey.Parameters = m.masterKeyPub.Parameters
}
if err := secretKey.DeriveKey(&oldPassphrase); err != nil {
if err == snacl.ErrInvalidPassword {
str := fmt.Sprintf("invalid passphrase for %s master "+
"key", keyName)
return managerError(ErrWrongPassphrase, str, nil)
}
str := fmt.Sprintf("failed to derive %s master key", keyName)
return managerError(ErrCrypto, str, err)
}
defer secretKey.Zero()
// Generate a new master key from the passphrase which is used to secure
// the actual secret keys.
newMasterKey, err := newSecretKey(&newPassphrase, m.config)
if err != nil {
str := "failed to create new master private key"
return managerError(ErrCrypto, str, err)
}
newKeyParams := newMasterKey.Marshal()
if private {
// Technically, the locked state could be checked here to only
// do the decrypts when the address manager is locked as the
// clear text keys are already available in memory when it is
// unlocked, but this is not a hot path, decryption is quite
// fast, and it's less cyclomatic complexity to simply decrypt
// in either case.
// Re-encrypt the crypto private key using the new master
// private key.
decPriv, err := secretKey.Decrypt(m.cryptoKeyPrivEncrypted)
if err != nil {
str := "failed to decrypt crypto private key"
return managerError(ErrCrypto, str, err)
}
encPriv, err := newMasterKey.Encrypt(decPriv)
zero(decPriv)
if err != nil {
str := "failed to encrypt crypto private key"
return managerError(ErrCrypto, str, err)
}
// Re-encrypt the crypto script key using the new master private
// key.
decScript, err := secretKey.Decrypt(m.cryptoKeyScriptEncrypted)
if err != nil {
str := "failed to decrypt crypto script key"
return managerError(ErrCrypto, str, err)
}
encScript, err := newMasterKey.Encrypt(decScript)
zero(decScript)
if err != nil {
str := "failed to encrypt crypto script key"
return managerError(ErrCrypto, str, err)
}
// When the manager is locked, ensure the new clear text master
// key is cleared from memory now that it is no longer needed.
if m.locked {
newMasterKey.Zero()
}
// Save the new keys and params to the the db in a single
// transaction.
err = m.namespace.Update(func(tx walletdb.Tx) error {
err := putCryptoKeys(tx, nil, encPriv, encScript)
if err != nil {
return err
}
return putMasterKeyParams(tx, nil, newKeyParams)
})
if err != nil {
return maybeConvertDbError(err)
}
// Now that the db has been successfully updated, clear the old
// key and set the new one.
copy(m.cryptoKeyPrivEncrypted[:], encPriv)
copy(m.cryptoKeyScriptEncrypted[:], encScript)
m.masterKeyPriv.Zero() // Clear the old key.
m.masterKeyPriv = newMasterKey
} else {
// Re-encrypt the crypto public key using the new master public
// key.
encryptedPub, err := newMasterKey.Encrypt(m.cryptoKeyPub.Bytes())
if err != nil {
str := "failed to encrypt crypto public key"
return managerError(ErrCrypto, str, err)
}
// Save the new keys and params to the the db in a single
// transaction.
err = m.namespace.Update(func(tx walletdb.Tx) error {
err := putCryptoKeys(tx, encryptedPub, nil, nil)
if err != nil {
return err
}
return putMasterKeyParams(tx, newKeyParams, nil)
})
if err != nil {
return maybeConvertDbError(err)
}
// Now that the db has been successfully updated, clear the old
// key and set the new one.
m.masterKeyPub.Zero()
m.masterKeyPub = newMasterKey
}
return nil
}
// ConvertToWatchingOnly converts the current address manager to a locked
// watching-only address manager.
//
// WARNING: This function removes private keys from the existing address manager
// which means they will no longer be available. Typically the caller will make
// a copy of the existing wallet database and modify the copy since otherwise it
// would mean permanent loss of any imported private keys and scripts.
//
// Executing this function on a manager that is already watching-only will have
// no effect.
func (m *Manager) ConvertToWatchingOnly(pubPassphrase []byte) error {
m.mtx.Lock()
defer m.mtx.Unlock()
// Exit now if the manager is already watching-only.
if m.watchingOnly {
return nil
}
// Remove all private key material and mark the new database as watching
// only.
err := m.namespace.Update(func(tx walletdb.Tx) error {
if err := deletePrivateKeys(tx); err != nil {
return err
}
return putWatchingOnly(tx, true)
})
if err != nil {
return maybeConvertDbError(err)
}
// Lock the manager to remove all clear text private key material from
// memory if needed.
if !m.locked {
m.lock()
}
// This section clears and removes the encrypted private key material
// that is ordinarily used to unlock the manager. Since the the manager
// is being converted to watching-only, the encrypted private key
// material is no longer needed.
// Clear and remove all of the encrypted acount private keys.
for _, acctInfo := range m.acctInfo {
zero(acctInfo.acctKeyEncrypted)
acctInfo.acctKeyEncrypted = nil
}
// Clear and remove encrypted private keys and encrypted scripts from
// all address entries.
for _, ma := range m.addrs {
switch addr := ma.(type) {
case *managedAddress:
zero(addr.privKeyEncrypted)
addr.privKeyEncrypted = nil
case *scriptAddress:
zero(addr.scriptEncrypted)
addr.scriptEncrypted = nil
}
}
// Clear and remove encrypted private and script crypto keys.
zero(m.cryptoKeyScriptEncrypted)
m.cryptoKeyScriptEncrypted = nil
m.cryptoKeyScript = nil
zero(m.cryptoKeyPrivEncrypted)
m.cryptoKeyPrivEncrypted = nil
m.cryptoKeyPriv = nil
// The master private key is derived from a passphrase when the manager
// is unlocked, so there is no encrypted version to zero. However,
// it is no longer needed, so nil it.
m.masterKeyPriv = nil
// Mark the manager watching-only.
m.watchingOnly = true
return nil
}
// existsAddress returns whether or not the passed address is known to the
// address manager.
//
// This function MUST be called with the manager lock held for reads.
func (m *Manager) existsAddress(addressID []byte) (bool, error) {
// Check the in-memory map first since it's faster than a db access.
if _, ok := m.addrs[addrKey(addressID)]; ok {
return true, nil
}
// Check the database if not already found above.
var exists bool
err := m.namespace.View(func(tx walletdb.Tx) error {
exists = existsAddress(tx, addressID)
return nil
})
if err != nil {
return false, maybeConvertDbError(err)
}
return exists, nil
}
// ImportPrivateKey imports a WIF private key into the address manager. The
// imported address is created using either a compressed or uncompressed
// serialized public key, depending on the CompressPubKey bool of the WIF.
//
// All imported addresses will be part of the account defined by the
// ImportedAddrAccount constant.
//
// NOTE: When the address manager is watching-only, the private key itself will
// not be stored or available since it is private data. Instead, only the
// public key will be stored. This means it is paramount the private key is
// kept elsewhere as the watching-only address manager will NOT ever have access
// to it.
//
// This function will return an error if the address manager is locked and not
// watching-only, or not for the same network as the key trying to be imported.
// It will also return an error if the address already exists. Any other errors
// returned are generally unexpected.
func (m *Manager) ImportPrivateKey(wif *btcutil.WIF, bs *BlockStamp) (ManagedPubKeyAddress, error) {
// Ensure the address is intended for network the address manager is
// associated with.
if !wif.IsForNet(m.net) {
str := fmt.Sprintf("private key is not for the same network the "+
"address manager is configured for (%s)", m.net.Name)
return nil, managerError(ErrWrongNet, str, nil)
}
m.mtx.Lock()
defer m.mtx.Unlock()
// The manager must be unlocked to encrypt the imported private key.
if m.locked && !m.watchingOnly {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Prevent duplicates.
serializedPubKey := wif.SerializePubKey()
pubKeyHash := btcutil.Hash160(serializedPubKey)
alreadyExists, err := m.existsAddress(pubKeyHash)
if err != nil {
return nil, err
}
if alreadyExists {
str := fmt.Sprintf("address for public key %x already exists",
serializedPubKey)
return nil, managerError(ErrDuplicate, str, nil)
}
// Encrypt public key.
encryptedPubKey, err := m.cryptoKeyPub.Encrypt(serializedPubKey)
if err != nil {
str := fmt.Sprintf("failed to encrypt public key for %x",
serializedPubKey)
return nil, managerError(ErrCrypto, str, err)
}
// Encrypt the private key when not a watching-only address manager.
var encryptedPrivKey []byte
if !m.watchingOnly {
privKeyBytes := wif.PrivKey.Serialize()
encryptedPrivKey, err = m.cryptoKeyPriv.Encrypt(privKeyBytes)
zero(privKeyBytes)
if err != nil {
str := fmt.Sprintf("failed to encrypt private key for %x",
serializedPubKey)
return nil, managerError(ErrCrypto, str, err)
}
}
// The start block needs to be updated when the newly imported address
// is before the current one.
updateStartBlock := bs.Height < m.syncState.startBlock.Height
// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
err = m.namespace.Update(func(tx walletdb.Tx) error {
err := putImportedAddress(tx, pubKeyHash, ImportedAddrAccount,
ssNone, encryptedPubKey, encryptedPrivKey)
if err != nil {
return err
}
if updateStartBlock {
return putStartBlock(tx, bs)
}
return nil
})
if err != nil {
return nil, err
}
// Now that the database has been updated, update the start block in
// memory too if needed.
if updateStartBlock {
m.syncState.startBlock = *bs
}
// Create a new managed address based on the imported address.
var managedAddr *managedAddress
if !m.watchingOnly {
managedAddr, err = newManagedAddress(m, ImportedAddrAccount,
wif.PrivKey, wif.CompressPubKey)
} else {
pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey)
managedAddr, err = newManagedAddressWithoutPrivKey(m,
ImportedAddrAccount, pubKey, wif.CompressPubKey)
}
if err != nil {
return nil, err
}
managedAddr.imported = true
// Add the new managed address to the cache of recent addresses and
// return it.
m.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
return managedAddr, nil
}
// ImportScript imports a user-provided script into the address manager. The
// imported script will act as a pay-to-script-hash address.
//
// All imported script addresses will be part of the account defined by the
// ImportedAddrAccount constant.
//
// When the address manager is watching-only, the script itself will not be
// stored or available since it is considered private data.
//
// This function will return an error if the address manager is locked and not
// watching-only, or the address already exists. Any other errors returned are
// generally unexpected.
func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddress, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
// The manager must be unlocked to encrypt the imported script.
if m.locked && !m.watchingOnly {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Prevent duplicates.
scriptHash := btcutil.Hash160(script)
alreadyExists, err := m.existsAddress(scriptHash)
if err != nil {
return nil, err
}
if alreadyExists {
str := fmt.Sprintf("address for script hash %x already exists",
scriptHash)
return nil, managerError(ErrDuplicate, str, nil)
}
// Encrypt the script hash using the crypto public key so it is
// accessible when the address manager is locked or watching-only.
encryptedHash, err := m.cryptoKeyPub.Encrypt(scriptHash)
if err != nil {
str := fmt.Sprintf("failed to encrypt script hash %x",
scriptHash)
return nil, managerError(ErrCrypto, str, err)
}
// Encrypt the script for storage in database using the crypto script
// key when not a watching-only address manager.
var encryptedScript []byte
if !m.watchingOnly {
encryptedScript, err = m.cryptoKeyScript.Encrypt(script)
if err != nil {
str := fmt.Sprintf("failed to encrypt script for %x",
scriptHash)
return nil, managerError(ErrCrypto, str, err)
}
}
// The start block needs to be updated when the newly imported address
// is before the current one.
updateStartBlock := false
if bs.Height < m.syncState.startBlock.Height {
updateStartBlock = true
}
// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
err = m.namespace.Update(func(tx walletdb.Tx) error {
err := putScriptAddress(tx, scriptHash, ImportedAddrAccount,
ssNone, encryptedHash, encryptedScript)
if err != nil {
return err
}
if updateStartBlock {
return putStartBlock(tx, bs)
}
return nil
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// Now that the database has been updated, update the start block in
// memory too if needed.
if updateStartBlock {
m.syncState.startBlock = *bs
}
// Create a new managed address based on the imported script. Also,
// when not a watching-only address manager, make a copy of the script
// since it will be cleared on lock and the script the caller passed
// should not be cleared out from under the caller.
scriptAddr, err := newScriptAddress(m, ImportedAddrAccount, scriptHash,
encryptedScript)
if err != nil {
return nil, err
}
if !m.watchingOnly {
scriptAddr.scriptCT = make([]byte, len(script))
copy(scriptAddr.scriptCT, script)
}
// Add the new managed address to the cache of recent addresses and
// return it.
m.addrs[addrKey(scriptHash)] = scriptAddr
return scriptAddr, nil
}
// IsLocked returns whether or not the address managed is locked. When it is
// unlocked, the decryption key needed to decrypt private keys used for signing
// is in memory.
func (m *Manager) IsLocked() bool {
m.mtx.RLock()
defer m.mtx.RUnlock()
return m.locked
}
// Lock performs a best try effort to remove and zero all secret keys associated
// with the address manager.
//
// This function will return an error if invoked on a watching-only address
// manager.
func (m *Manager) Lock() error {
// A watching-only address manager can't be locked.
if m.watchingOnly {
return managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
m.mtx.Lock()
defer m.mtx.Unlock()
// Error on attempt to lock an already locked manager.
if m.locked {
return managerError(ErrLocked, errLocked, nil)
}
m.lock()
return nil
}
// Unlock derives the master private key from the specified passphrase. An
// invalid passphrase will return an error. Otherwise, the derived secret key
// is stored in memory until the address manager is locked. Any failures that
// occur during this function will result in the address manager being locked,
// even if it was already unlocked prior to calling this function.
//
// This function will return an error if invoked on a watching-only address
// manager.
func (m *Manager) Unlock(passphrase []byte) error {
// A watching-only address manager can't be unlocked.
if m.watchingOnly {
return managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
m.mtx.Lock()
defer m.mtx.Unlock()
// Derive the master private key using the provided passphrase.
if err := m.masterKeyPriv.DeriveKey(&passphrase); err != nil {
m.lock()
if err == snacl.ErrInvalidPassword {
str := "invalid passphrase for master private key"
return managerError(ErrWrongPassphrase, str, nil)
}
str := "failed to derive master private key"
return managerError(ErrCrypto, str, err)
}
// Use the master private key to decrypt the crypto private key.
decryptedKey, err := m.masterKeyPriv.Decrypt(m.cryptoKeyPrivEncrypted)
if err != nil {
m.lock()
str := "failed to decrypt crypto private key"
return managerError(ErrCrypto, str, err)
}
m.cryptoKeyPriv.CopyBytes(decryptedKey)
zero(decryptedKey)
// Use the crypto private key to decrypt all of the account private
// extended keys.
for account, acctInfo := range m.acctInfo {
decrypted, err := m.cryptoKeyPriv.Decrypt(acctInfo.acctKeyEncrypted)
if err != nil {
m.lock()
str := fmt.Sprintf("failed to decrypt account %d "+
"private key", account)
return managerError(ErrCrypto, str, err)
}
acctKeyPriv, err := hdkeychain.NewKeyFromString(string(decrypted))
zero(decrypted)
if err != nil {
m.lock()
str := fmt.Sprintf("failed to regenerate account %d "+
"extended key", account)
return managerError(ErrKeyChain, str, err)
}
acctInfo.acctKeyPriv = acctKeyPriv
}
// Derive any private keys that are pending due to them being created
// while the address manager was locked.
for _, info := range m.deriveOnUnlock {
addressKey, err := m.deriveKeyFromPath(info.managedAddr.account,
info.branch, info.index, true)
if err != nil {
m.lock()
return err
}
// It's ok to ignore the error here since it can only fail if
// the extended key is not private, however it was just derived
// as a private key.
privKey, _ := addressKey.ECPrivKey()
addressKey.Zero()
privKeyBytes := privKey.Serialize()
privKeyEncrypted, err := m.cryptoKeyPriv.Encrypt(privKeyBytes)
zeroBigInt(privKey.D)
if err != nil {
m.lock()
str := fmt.Sprintf("failed to encrypt private key for "+
"address %s", info.managedAddr.Address())
return managerError(ErrCrypto, str, err)
}
info.managedAddr.privKeyEncrypted = privKeyEncrypted
info.managedAddr.privKeyCT = privKeyBytes
}
m.locked = false
return nil
}
// Net returns the network parameters for this address manager.
func (m *Manager) Net() *btcnet.Params {
// NOTE: No need for mutex here since the net field does not change
// after the manager instance is created.
return m.net
}
// nextAddresses returns the specified number of next chained address from the
// branch indicated by the internal flag.
//
// This function MUST be called with the manager lock held for writes.
func (m *Manager) nextAddresses(account uint32, numAddresses uint32, internal bool) ([]ManagedAddress, error) {
// The next address can only be generated for accounts that have already
// been created.
acctInfo, err := m.loadAccountInfo(account)
if err != nil {
return nil, err
}
// Choose the account key to used based on whether the address manager
// is locked.
acctKey := acctInfo.acctKeyPub
if !m.locked {
acctKey = acctInfo.acctKeyPriv
}
// Choose the branch key and index depending on whether or not this
// is an internal address.
branchNum, nextIndex := externalBranch, acctInfo.nextExternalIndex
if internal {
branchNum = internalBranch
nextIndex = acctInfo.nextInternalIndex
}
// Ensure the requested number of addresses doesn't exceed the maximum
// allowed for this account.
if numAddresses > MaxAddressesPerAccount || nextIndex+numAddresses >
MaxAddressesPerAccount {
str := fmt.Sprintf("%d new addresses would exceed the maximum "+
"allowed number of addresses per account of %d",
numAddresses, MaxAddressesPerAccount)
return nil, managerError(ErrTooManyAddresses, str, nil)
}
// Derive the appropriate branch key and ensure it is zeroed when done.
branchKey, err := acctKey.Child(branchNum)
if err != nil {
str := fmt.Sprintf("failed to derive extended key branch %d",
branchNum)
return nil, managerError(ErrKeyChain, str, err)
}
defer branchKey.Zero() // Ensure branch key is zeroed when done.
// Create the requested number of addresses and keep track of the index
// with each one.
addressInfo := make([]*unlockDeriveInfo, 0, numAddresses)
for i := uint32(0); i < numAddresses; i++ {
// There is an extremely small chance that a particular child is
// invalid, so use a loop to derive the next valid child.
var nextKey *hdkeychain.ExtendedKey
for {
// Derive the next child in the external chain branch.
key, err := branchKey.Child(nextIndex)
if err != nil {
// When this particular child is invalid, skip to the
// next index.
if err == hdkeychain.ErrInvalidChild {
nextIndex++
continue
}
str := fmt.Sprintf("failed to generate child %d",
nextIndex)
return nil, managerError(ErrKeyChain, str, err)
}
key.SetNet(m.net)
nextIndex++
nextKey = key
break
}
// Create a new managed address based on the public or private
// key depending on whether the generated key is private. Also,
// zero the next key after creating the managed address from it.
managedAddr, err := newManagedAddressFromExtKey(m, account, nextKey)
nextKey.Zero()
if err != nil {
return nil, err
}
if internal {
managedAddr.internal = true
}
info := unlockDeriveInfo{
managedAddr: managedAddr,
branch: branchNum,
index: nextIndex - 1,
}
addressInfo = append(addressInfo, &info)
}
// Now that all addresses have been successfully generated, update the
// database in a single transaction.
err = m.namespace.Update(func(tx walletdb.Tx) error {
for _, info := range addressInfo {
ma := info.managedAddr
addressID := ma.Address().ScriptAddress()
err := putChainedAddress(tx, addressID, account, ssFull,
info.branch, info.index)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// Finally update the next address tracking and add the addresses to the
// cache after the newly generated addresses have been successfully
// added to the db.
managedAddresses := make([]ManagedAddress, 0, len(addressInfo))
for _, info := range addressInfo {
ma := info.managedAddr
m.addrs[addrKey(ma.Address().ScriptAddress())] = ma
// Add the new managed address to the list of addresses that
// need their private keys derived when the address manager is
// next unlocked.
if m.locked && !m.watchingOnly {
m.deriveOnUnlock = append(m.deriveOnUnlock, info)
}
managedAddresses = append(managedAddresses, ma)
}
// Set the last address and next address for tracking.
ma := addressInfo[len(addressInfo)-1].managedAddr
if internal {
acctInfo.nextInternalIndex = nextIndex
acctInfo.lastInternalAddr = ma
} else {
acctInfo.nextExternalIndex = nextIndex
acctInfo.lastExternalAddr = ma
}
return managedAddresses, nil
}
// NextExternalAddresses returns the specified number of next chained addresses
// that are intended for external use from the address manager.
func (m *Manager) NextExternalAddresses(account uint32, numAddresses uint32) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
m.mtx.Lock()
defer m.mtx.Unlock()
return m.nextAddresses(account, numAddresses, false)
}
// NextInternalAddresses returns the specified number of next chained addresses
// that are intended for internal use such as change from the address manager.
func (m *Manager) NextInternalAddresses(account uint32, numAddresses uint32) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
m.mtx.Lock()
defer m.mtx.Unlock()
return m.nextAddresses(account, numAddresses, true)
}
// LastExternalAddress returns the most recently requested chained external
// address from calling NextExternalAddress for the given account. The first
// external address for the account will be returned if none have been
// previously requested.
//
// This function will return an error if the provided account number is greater
// than the MaxAccountNum constant or there is no account information for the
// passed account. Any other errors returned are generally unexpected.
func (m *Manager) LastExternalAddress(account uint32) (ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
m.mtx.Lock()
defer m.mtx.Unlock()
// Load account information for the passed account. It is typically
// cached, but if not it will be loaded from the database.
acctInfo, err := m.loadAccountInfo(account)
if err != nil {
return nil, err
}
return acctInfo.lastExternalAddr, nil
}
// LastInternalAddress returns the most recently requested chained internal
// address from calling NextInternalAddress for the given account. The first
// internal address for the account will be returned if none have been
// previously requested.
//
// This function will return an error if the provided account number is greater
// than the MaxAccountNum constant or there is no account information for the
// passed account. Any other errors returned are generally unexpected.
func (m *Manager) LastInternalAddress(account uint32) (ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
m.mtx.Lock()
defer m.mtx.Unlock()
// Load account information for the passed account. It is typically
// cached, but if not it will be loaded from the database.
acctInfo, err := m.loadAccountInfo(account)
if err != nil {
return nil, err
}
return acctInfo.lastInternalAddr, nil
}
// AllActiveAddresses returns a slice of all addresses stored in the manager.
func (m *Manager) AllActiveAddresses() ([]btcutil.Address, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
// Load the raw address information from the database.
var rowInterfaces []interface{}
err := m.namespace.View(func(tx walletdb.Tx) error {
var err error
rowInterfaces, err = fetchAllAddresses(tx)
return err
})
if err != nil {
return nil, maybeConvertDbError(err)
}
addrs := make([]btcutil.Address, 0, len(rowInterfaces))
for _, rowInterface := range rowInterfaces {
// Create a new managed address for the specific type of address
// based on type.
managedAddr, err := m.rowInterfaceToManaged(rowInterface)
if err != nil {
return nil, err
}
addrs = append(addrs, managedAddr.Address())
}
return addrs, nil
}
// selectCryptoKey selects the appropriate crypto key based on the key type. An
// error is returned when an invalid key type is specified or the requested key
// requires the manager to be unlocked when it isn't.
//
// This function MUST be called with the manager lock held for reads.
func (m *Manager) selectCryptoKey(keyType CryptoKeyType) (EncryptorDecryptor, error) {
if keyType == CKTPrivate || keyType == CKTScript {
// The manager must be unlocked to work with the private keys.
if m.locked || m.watchingOnly {
return nil, managerError(ErrLocked, errLocked, nil)
}
}
var cryptoKey EncryptorDecryptor
switch keyType {
case CKTPrivate:
cryptoKey = m.cryptoKeyPriv
case CKTScript:
cryptoKey = m.cryptoKeyScript
case CKTPublic:
cryptoKey = m.cryptoKeyPub
default:
return nil, managerError(ErrInvalidKeyType, "invalid key type",
nil)
}
return cryptoKey, nil
}
// Encrypt in using the crypto key type specified by keyType.
func (m *Manager) Encrypt(keyType CryptoKeyType, in []byte) ([]byte, error) {
// Encryption must be performed under the manager mutex since the
// keys are cleared when the manager is locked.
m.mtx.Lock()
defer m.mtx.Unlock()
cryptoKey, err := m.selectCryptoKey(keyType)
if err != nil {
return nil, err
}
encrypted, err := cryptoKey.Encrypt(in)
if err != nil {
return nil, managerError(ErrCrypto, "failed to encrypt", err)
}
return encrypted, nil
}
// Decrypt in using the crypto key type specified by keyType.
func (m *Manager) Decrypt(keyType CryptoKeyType, in []byte) ([]byte, error) {
// Decryption must be performed under the manager mutex since the
// keys are cleared when the manager is locked.
m.mtx.Lock()
defer m.mtx.Unlock()
cryptoKey, err := m.selectCryptoKey(keyType)
if err != nil {
return nil, err
}
decrypted, err := cryptoKey.Decrypt(in)
if err != nil {
return nil, managerError(ErrCrypto, "failed to decrypt", err)
}
return decrypted, nil
}
// newManager returns a new locked address manager with the given parameters.
func newManager(namespace walletdb.Namespace, net *btcnet.Params,
masterKeyPub *snacl.SecretKey, masterKeyPriv *snacl.SecretKey,
cryptoKeyPub EncryptorDecryptor, cryptoKeyPrivEncrypted,
cryptoKeyScriptEncrypted []byte, syncInfo *syncState,
config *Options) *Manager {
return &Manager{
namespace: namespace,
net: net,
addrs: make(map[addrKey]ManagedAddress),
syncState: *syncInfo,
locked: true,
acctInfo: make(map[uint32]*accountInfo),
masterKeyPub: masterKeyPub,
masterKeyPriv: masterKeyPriv,
cryptoKeyPub: cryptoKeyPub,
cryptoKeyPrivEncrypted: cryptoKeyPrivEncrypted,
cryptoKeyPriv: &cryptoKey{},
cryptoKeyScriptEncrypted: cryptoKeyScriptEncrypted,
cryptoKeyScript: &cryptoKey{},
config: config,
}
}
// deriveAccountKey derives the extended key for an account according to the
// hierarchy described by BIP0044 given the master node.
//
// In particular this is the hierarchical deterministic extended key path:
// m/44'/<coin type>'/<account>'
func deriveAccountKey(masterNode *hdkeychain.ExtendedKey, coinType uint32,
account uint32) (*hdkeychain.ExtendedKey, error) {
// Enforce maximum coin type.
if coinType > maxCoinType {
err := managerError(ErrCoinTypeTooHigh, errCoinTypeTooHigh, nil)
return nil, err
}
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
// The hierarchy described by BIP0043 is:
// m/<purpose>'/*
// This is further extended by BIP0044 to:
// m/44'/<coin type>'/<account>'/<branch>/<address index>
//
// The branch is 0 for external addresses and 1 for internal addresses.
// Derive the purpose key as a child of the master node.
purpose, err := masterNode.Child(44 + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
// Derive the coin type key as a child of the purpose key.
coinTypeKey, err := purpose.Child(coinType + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
// Derive the account key as a child of the coin type key.
return coinTypeKey.Child(account + hdkeychain.HardenedKeyStart)
}
// checkBranchKeys ensures deriving the extended keys for the internal and
// external branches given an account key does not result in an invalid child
// error which means the chosen seed is not usable. This conforms to the
// hierarchy described by BIP0044 so long as the account key is already derived
// accordingly.
//
// In particular this is the hierarchical deterministic extended key path:
// m/44'/<coin type>'/<account>'/<branch>
//
// The branch is 0 for external addresses and 1 for internal addresses.
func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error {
// Derive the external branch as the first child of the account key.
if _, err := acctKey.Child(externalBranch); err != nil {
return err
}
// Derive the external branch as the second child of the account key.
_, err := acctKey.Child(internalBranch)
return err
}
// loadManager returns a new address manager that results from loading it from
// the passed opened database. The public passphrase is required to decrypt the
// public keys.
func loadManager(namespace walletdb.Namespace, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Perform all database lookups in a read-only view.
var watchingOnly bool
var masterKeyPubParams, masterKeyPrivParams []byte
var cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc []byte
var syncedTo, startBlock *BlockStamp
var recentHeight int32
var recentHashes []btcwire.ShaHash
err := namespace.View(func(tx walletdb.Tx) error {
// Load whether or not the manager is watching-only from the db.
var err error
watchingOnly, err = fetchWatchingOnly(tx)
if err != nil {
return err
}
// Load the master key params from the db.
masterKeyPubParams, masterKeyPrivParams, err =
fetchMasterKeyParams(tx)
if err != nil {
return err
}
// Load the crypto keys from the db.
cryptoKeyPubEnc, cryptoKeyPrivEnc, cryptoKeyScriptEnc, err =
fetchCryptoKeys(tx)
if err != nil {
return err
}
// Load the sync state from the db.
syncedTo, err = fetchSyncedTo(tx)
if err != nil {
return err
}
startBlock, err = fetchStartBlock(tx)
if err != nil {
return err
}
recentHeight, recentHashes, err = fetchRecentBlocks(tx)
return err
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// When not a watching-only manager, set the master private key params,
// but don't derive it now since the manager starts off locked.
var masterKeyPriv snacl.SecretKey
if !watchingOnly {
err := masterKeyPriv.Unmarshal(masterKeyPrivParams)
if err != nil {
str := "failed to unmarshal master private key"
return nil, managerError(ErrCrypto, str, err)
}
}
// Derive the master public key using the serialized params and provided
// passphrase.
var masterKeyPub snacl.SecretKey
if err := masterKeyPub.Unmarshal(masterKeyPubParams); err != nil {
str := "failed to unmarshal master public key"
return nil, managerError(ErrCrypto, str, err)
}
if err := masterKeyPub.DeriveKey(&pubPassphrase); err != nil {
str := "invalid passphrase for master public key"
return nil, managerError(ErrWrongPassphrase, str, nil)
}
// Use the master public key to decrypt the crypto public key.
cryptoKeyPub := &cryptoKey{snacl.CryptoKey{}}
cryptoKeyPubCT, err := masterKeyPub.Decrypt(cryptoKeyPubEnc)
if err != nil {
str := "failed to decrypt crypto public key"
return nil, managerError(ErrCrypto, str, err)
}
cryptoKeyPub.CopyBytes(cryptoKeyPubCT)
zero(cryptoKeyPubCT)
// Create the sync state struct.
syncInfo := newSyncState(startBlock, syncedTo, recentHeight, recentHashes)
// Create new address manager with the given parameters. Also, override
// the defaults for the additional fields which are not specified in the
// call to new with the values loaded from the database.
mgr := newManager(namespace, net, &masterKeyPub, &masterKeyPriv,
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
config)
mgr.watchingOnly = watchingOnly
return mgr, nil
}
// Open loads an existing address manager from the given namespace. The public
// passphrase is required to decrypt the public keys used to protect the public
// information such as addresses. This is important since access to BIP0032
// extended keys means it is possible to generate all future addresses.
//
// If a config structure is passed to the function, that configuration
// will override the defaults.
//
// A ManagerError with an error code of ErrNoExist will be returned if the
// passed manager does not exist in the specified namespace.
func Open(namespace walletdb.Namespace, pubPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Return an error if the manager has NOT already been created in the
// given database namespace.
exists, err := managerExists(namespace)
if err != nil {
return nil, err
}
if !exists {
str := "the specified address manager does not exist"
return nil, managerError(ErrNoExist, str, nil)
}
// Upgrade the manager to the latest version as needed. This includes
// the initial creation.
if err := upgradeManager(namespace); err != nil {
return nil, err
}
if config == nil {
config = defaultConfig
}
return loadManager(namespace, pubPassphrase, net, config)
}
// Create returns a new locked address manager in the given namespace. The
// seed must conform to the standards described in hdkeychain.NewMaster and will
// be used to create the master root node from which all hierarchical
// deterministic addresses are derived. This allows all chained addresses in
// the address manager to be recovered by using the same seed.
//
// All private and public keys and information are protected by secret keys
// derived from the provided private and public passphrases. The public
// passphrase is required on subsequent opens of the address manager, and the
// private passphrase is required to unlock the address manager in order to gain
// access to any private keys and information.
//
// If a config structure is passed to the function, that configuration
// will override the defaults.
//
// A ManagerError with an error code of ErrAlreadyExists will be returned the
// address manager already exists in the specified namespace.
func Create(namespace walletdb.Namespace, seed, pubPassphrase, privPassphrase []byte, net *btcnet.Params, config *Options) (*Manager, error) {
// Return an error if the manager has already been created in the given
// database namespace.
exists, err := managerExists(namespace)
if err != nil {
return nil, err
}
if exists {
return nil, managerError(ErrAlreadyExists, errAlreadyExists, nil)
}
// Upgrade the manager to the latest version as needed. This includes
// the initial creation.
if err := upgradeManager(namespace); err != nil {
return nil, err
}
if config == nil {
config = defaultConfig
}
// Generate the BIP0044 HD key structure to ensure the provided seed
// can generate the required structure with no issues.
// Derive the master extended key from the seed.
root, err := hdkeychain.NewMaster(seed)
if err != nil {
str := "failed to derive master extended key"
return nil, managerError(ErrKeyChain, str, err)
}
// Derive the account key for the first account according to BIP0044.
acctKeyPriv, err := deriveAccountKey(root, net.HDCoinType, 0)
if err != nil {
// The seed is unusable if the any of the children in the
// required hierarchy can't be derived due to invalid child.
if err == hdkeychain.ErrInvalidChild {
str := "the provided seed is unusable"
return nil, managerError(ErrKeyChain, str,
hdkeychain.ErrUnusableSeed)
}
return nil, err
}
// Ensure the branch keys can be derived for the provided seed according
// to BIP0044.
if err := checkBranchKeys(acctKeyPriv); err != nil {
// The seed is unusable if the any of the children in the
// required hierarchy can't be derived due to invalid child.
if err == hdkeychain.ErrInvalidChild {
str := "the provided seed is unusable"
return nil, managerError(ErrKeyChain, str,
hdkeychain.ErrUnusableSeed)
}
return nil, err
}
// The address manager needs the public extended key for the account.
acctKeyPub, err := acctKeyPriv.Neuter()
if err != nil {
str := "failed to convert private key for account 0"
return nil, managerError(ErrKeyChain, str, err)
}
// Generate new master keys. These master keys are used to protect the
// crypto keys that will be generated next.
masterKeyPub, err := newSecretKey(&pubPassphrase, config)
if err != nil {
str := "failed to master public key"
return nil, managerError(ErrCrypto, str, err)
}
masterKeyPriv, err := newSecretKey(&privPassphrase, config)
if err != nil {
str := "failed to master private key"
return nil, managerError(ErrCrypto, str, err)
}
// Generate new crypto public, private, and script keys. These keys are
// used to protect the actual public and private data such as addresses,
// extended keys, and scripts.
cryptoKeyPub, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto public key"
return nil, managerError(ErrCrypto, str, err)
}
cryptoKeyPriv, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto private key"
return nil, managerError(ErrCrypto, str, err)
}
cryptoKeyScript, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto script key"
return nil, managerError(ErrCrypto, str, err)
}
// Encrypt the crypto keys with the associated master keys.
cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes())
if err != nil {
str := "failed to encrypt crypto public key"
return nil, managerError(ErrCrypto, str, err)
}
cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
if err != nil {
str := "failed to encrypt crypto private key"
return nil, managerError(ErrCrypto, str, err)
}
cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
if err != nil {
str := "failed to encrypt crypto script key"
return nil, managerError(ErrCrypto, str, err)
}
// Encrypt the default account keys with the associated crypto keys.
acctPubEnc, err := cryptoKeyPub.Encrypt([]byte(acctKeyPub.String()))
if err != nil {
str := "failed to encrypt public key for account 0"
return nil, managerError(ErrCrypto, str, err)
}
acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String()))
if err != nil {
str := "failed to encrypt private key for account 0"
return nil, managerError(ErrCrypto, str, err)
}
// Use the genesis block for the passed network as the created at block
// for the defaut.
createdAt := &BlockStamp{Hash: *net.GenesisHash, Height: 0}
// Create the initial sync state.
recentHashes := []btcwire.ShaHash{createdAt.Hash}
recentHeight := createdAt.Height
syncInfo := newSyncState(createdAt, createdAt, recentHeight, recentHashes)
// Perform all database updates in a single transaction.
err = namespace.Update(func(tx walletdb.Tx) error {
// Save the master key params to the database.
pubParams := masterKeyPub.Marshal()
privParams := masterKeyPriv.Marshal()
err = putMasterKeyParams(tx, pubParams, privParams)
if err != nil {
return err
}
// Save the encrypted crypto keys to the database.
err = putCryptoKeys(tx, cryptoKeyPubEnc, cryptoKeyPrivEnc,
cryptoKeyScriptEnc)
if err != nil {
return err
}
// Save the fact this is not a watching-only address manager to
// the database.
err = putWatchingOnly(tx, false)
if err != nil {
return err
}
// Save the initial synced to state.
err = putSyncedTo(tx, &syncInfo.syncedTo)
if err != nil {
return err
}
err = putStartBlock(tx, &syncInfo.startBlock)
if err != nil {
return err
}
// Save the initial recent blocks state.
err = putRecentBlocks(tx, recentHeight, recentHashes)
if err != nil {
return err
}
// Save the information for the default account to the database.
err = putAccountInfo(tx, defaultAccountNum, acctPubEnc,
acctPrivEnc, 0, 0, "")
if err != nil {
return err
}
return putNumAccounts(tx, 1)
})
if err != nil {
return nil, maybeConvertDbError(err)
}
// The new address manager is locked by default, so clear the master,
// crypto private, and crypto script keys from memory.
masterKeyPriv.Zero()
cryptoKeyPriv.Zero()
cryptoKeyScript.Zero()
return newManager(namespace, net, masterKeyPub, masterKeyPriv,
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
config), nil
}