From a75170c65065518c53cd42978218be65adad21b1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 13 Feb 2018 21:27:05 -0800 Subject: [PATCH] waddrmgr: update the Manager struct to remove functionality covered by ScopedManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we remove many of the methods in the Manager struct as they’ve now be been replicated within a scoped format for each of the ScopedKeyManagers. A major change is that we’ll now actually store the master HD private and public keys. This required as in order to create new scopes, we need access to the master HD private key as hardened derivation is required in accordance with BIP43. The initial creation of the manager namespaces has also been extended to create the namespaces and keys for the set of default key scopes. Finally, a series of utility method has been added to allow callers to create ScopedKeyManagers for arbitrary sets of scopes. --- waddrmgr/manager.go | 2231 +++++++++++++++---------------------------- 1 file changed, 773 insertions(+), 1458 deletions(-) diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 7a27849..a66f62a 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -11,7 +11,6 @@ import ( "sync" "time" - "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil/hdkeychain" @@ -22,21 +21,21 @@ import ( 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 + // 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. + // 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. + // 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 // ImportedAddrAccountName is the name of the imported account. @@ -50,8 +49,8 @@ const ( // so the default account might not be named "default" and non-default // accounts may be named "default". // - // Account numbers never change, so the DefaultAccountNum should be used - // to refer to (and only to) the default account. + // Account numbers never change, so the DefaultAccountNum should be + // used to refer to (and only to) the default account. defaultAccountName = "default" // The hierarchy described by BIP0043 is: @@ -82,9 +81,9 @@ const ( saltSize = 32 ) -// isReservedAccountName returns true if the account name is reserved. Reserved -// accounts may never be renamed, and other accounts may not be renamed to a -// reserved name. +// isReservedAccountName returns true if the account name is reserved. +// Reserved accounts may never be renamed, and other accounts may not be +// renamed to a reserved name. func isReservedAccountName(name string) bool { return name == ImportedAddrAccountName } @@ -109,6 +108,7 @@ type OpenCallbacks struct { // upgrades. It is intended to be used to request the wallet seed // from the user (or any other mechanism the caller deems fit). ObtainSeed ObtainUserInputFunc + // ObtainPrivatePass is a callback function that is potentially invoked // during upgrades. It is intended to be used to request the wallet // private passphrase from the user (or any other mechanism the caller @@ -124,8 +124,8 @@ var DefaultScryptOptions = ScryptOptions{ } // 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). +// 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 @@ -137,14 +137,14 @@ type accountInfo struct { acctName string // 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. + // 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. + // The external branch is used for all addresses which are intended for + // external use. nextExternalIndex uint32 lastExternalAddr ManagedAddress @@ -165,8 +165,9 @@ type AccountProperties struct { } // 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. +// 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 @@ -247,17 +248,19 @@ var newCryptoKey = defaultNewCryptoKey type Manager struct { mtx sync.RWMutex - chainParams *chaincfg.Params - addrs map[addrKey]ManagedAddress + // scopedManager is a mapping of scope of scoped manager, the manager + // itself loaded into memory. + scopedManagers map[KeyScope]*ScopedKeyManager + + externalAddrSchemas map[AddressType][]KeyScope + internalAddrSchemas map[AddressType][]KeyScope + syncState syncState - birthday time.Time watchingOnly bool + birthday time.Time 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 + chainParams *chaincfg.Params // masterKeyPub is the secret key used to secure the cryptoKeyPub key // and masterKeyPriv is the secret key used to secure the cryptoKeyPriv @@ -290,13 +293,6 @@ type Manager struct { 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 - // privPassphraseSalt and hashedPrivPassphrase allow for the secure // detection of a correct passphrase on manager unlock when the // manager is already unlocked. The hash is zeroed each lock. @@ -304,26 +300,47 @@ type Manager struct { hashedPrivPassphrase [sha512.Size]byte } +// Locked returns true if the root manager is locked, and false otherwise. +func (m *Manager) Locked() bool { + m.mtx.RLock() + defer m.mtx.RUnlock() + + return m.locked +} + +// WatchOnly returns true if the root manager is in watch only mode, and false +// otherwise. +func (m *Manager) WatchOnly() bool { + m.mtx.RLock() + defer m.mtx.RUnlock() + + return m.watchingOnly +} + // 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() + for _, manager := range m.scopedManagers { + // Clear all of the account private keys. + for _, acctInfo := range manager.acctInfo { + if acctInfo.acctKeyPriv != nil { + acctInfo.acctKeyPriv.Zero() + } + acctInfo.acctKeyPriv = nil } - 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() + for _, manager := range m.scopedManagers { + for _, ma := range manager.addrs { + switch addr := ma.(type) { + case *managedAddress: + addr.lock() + case *scriptAddress: + addr.lock() + } } } @@ -343,21 +360,6 @@ func (m *Manager) lock() { 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. @@ -369,410 +371,369 @@ func (m *Manager) Close() { return } + for _, manager := range m.scopedManagers { + // Zero out the account keys (if any) of all sub key managers. + manager.Close() + } + // 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() + // Remove clear text public master and crypto keys from memory. + m.cryptoKeyPub.Zero() + m.masterKeyPub.Zero() m.closed = true return } -// keyToManaged returns a new managed address for the provided derived key and -// its derivation path which consists of the account, branch, and index. +// NewScopedKeyManager creates a new scoped key manager from the root manager. A +// scoped key manager is a sub-manager that only has the coin type key of a +// particular coin type and BIP0043 purpose. This is useful as it enables +// callers to create an arbitrary BIP0043 like schema with a stand alone +// manager. Note that a new scoped manager cannot be created if: the wallet is +// watch only, the manager hasn't been unlocked, or the root key has been. +// neutered from the database. // -// 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, - addrType addressType, 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, addrType) - 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(ns walletdb.ReadBucket, 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. - rowInterface, err := fetchAccountInfo(ns, account) - 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{ - acctName: row.name, - 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 - } - // TODO(roasbeef): default type?? - lastExtAddr, err := m.keyToManaged(lastExtKey, adtChainWitness, - 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, adtChainWitness, - 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 -} - -// AccountProperties returns properties associated with the account, such as the -// account number, name, and the number of derived and imported keys. -// -// TODO: Instead of opening a second read transaction after making a change, and -// then fetching the account properties with a new read tx, this can be made -// more performant by simply returning the new account properties during the -// change. -func (m *Manager) AccountProperties(ns walletdb.ReadBucket, account uint32) (*AccountProperties, error) { - defer m.mtx.RUnlock() - m.mtx.RLock() - - props := &AccountProperties{AccountNumber: account} - - // Until keys can be imported into any account, special handling is - // required for the imported account. - // - // loadAccountInfo errors when using it on the imported account since - // the accountInfo struct is filled with a BIP0044 account's extended - // keys, and the imported accounts has none. - // - // Since only the imported account allows imports currently, the number - // of imported keys for any other account is zero, and since the - // imported account cannot contain non-imported keys, the external and - // internal key counts for it are zero. - if account != ImportedAddrAccount { - acctInfo, err := m.loadAccountInfo(ns, account) - if err != nil { - return nil, err - } - props.AccountName = acctInfo.acctName - props.ExternalKeyCount = acctInfo.nextExternalIndex - props.InternalKeyCount = acctInfo.nextInternalIndex - } else { - props.AccountName = ImportedAddrAccountName // reserved, nonchangable - - // Could be more efficient if this was tracked by the db. - var importedKeyCount uint32 - count := func(interface{}) error { - importedKeyCount++ - return nil - } - err := forEachAccountAddress(ns, ImportedAddrAccount, count) - if err != nil { - return nil, err - } - props.ImportedKeyCount = importedKeyCount - } - - return props, 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(ns walletdb.ReadBucket, account, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) { - // Look up the account key information. - acctInfo, err := m.loadAccountInfo(ns, 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(ns walletdb.ReadBucket, row *dbChainAddressRow) (ManagedAddress, error) { - addressKey, err := m.deriveKeyFromPath(ns, row.account, row.branch, - row.index, !m.locked) - if err != nil { - return nil, err - } - - return m.keyToManaged(addressKey, row.addrType, 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, row.addrType) - 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. -// -// This function MUST be called with the manager lock held for writes. -func (m *Manager) rowInterfaceToManaged(ns walletdb.ReadBucket, rowInterface interface{}) (ManagedAddress, error) { - switch row := rowInterface.(type) { - case *dbChainAddressRow: - return m.chainAddressRowToManaged(ns, 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(ns walletdb.ReadBucket, address btcutil.Address) (ManagedAddress, error) { - // Attempt to load the raw address information from the database. - rowInterface, err := fetchAddress(ns, address.ScriptAddress()) - if err != nil { - if merr, ok := err.(*ManagerError); ok { - desc := fmt.Sprintf("failed to fetch address '%s': %v", - address.ScriptAddress(), merr.Description) - merr.Description = desc - return nil, merr - } - return nil, maybeConvertDbError(err) - } - - // Create a new managed address for the specific type of address based - // on type. - managedAddr, err := m.rowInterfaceToManaged(ns, 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(ns walletdb.ReadBucket, address btcutil.Address) (ManagedAddress, error) { - // ScriptAddress will only return a script hash if we're - // accessing an address that is either PKH or SH. In - // the event we're passed a PK address, convert the - // PK to PKH address so that we can access it from - // the addrs map and database. - if pka, ok := address.(*btcutil.AddressPubKey); ok { - address = pka.AddressPubKeyHash() - } - - // TODO(roasbeef): also need to distinguish p2wkh from p2pkh - - // 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() +// TODO(roasbeef): addrtype of raw key means it'll look in scripts to possibly +// mark as gucci? +func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, scope KeyScope, + addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) { m.mtx.Lock() defer m.mtx.Unlock() - // Attempt to load the address from the database. - return m.loadAndCacheAddress(ns, address) + // If the manager is locked, then we can't create a new scoped manager. + if m.locked { + return nil, managerError(ErrLocked, errLocked, nil) + } + + // Now that we know the manager is unlocked, we'll need to fetch the + // root master HD private key. This is required as we'll be attempting + // the following derivation: m/purpose'/cointype' + // + // Note that the path to the coin type is requires hardened derivation, + // therefore this can only be done if the wallet's root key hasn't been + // neutered. + masterRootPrivEnc, _, err := fetchMasterHDKeys(ns) + if err != nil { + return nil, err + } + + // If the master root private key isn't found within the database, but + // we need to bail here as we can't create the cointype key without the + // master root private key. + if masterRootPrivEnc == nil { + return nil, managerError(ErrWatchingOnly, "", nil) + } + + // Before we can derive any new scoped managers using this key, we'll + // need to fully decrypt it. + serializedMasterRootPriv, err := m.cryptoKeyPriv.Decrypt(masterRootPrivEnc) + if err != nil { + str := fmt.Sprintf("failed to decrypt master root serialized private key") + return nil, managerError(ErrLocked, str, err) + } + + // Now that we know the root priv is within the database, we'll decode + // it into a usable object. + rootPriv, err := hdkeychain.NewKeyFromString( + string(serializedMasterRootPriv), + ) + zero.Bytes(serializedMasterRootPriv) + if err != nil { + str := fmt.Sprintf("failed to create master extended private key") + return nil, managerError(ErrKeyChain, str, err) + } + + // Now that we have the root private key, we'll fetch the scope bucket + // so we can create the proper internal name spaces. + scopeBucket := ns.NestedReadWriteBucket(scopeBucketName) + + // Now that we know it's possible to actually create a new scoped + // manager, we'll carve out its bucket space within the database. + if err := createScopedManagerNS(scopeBucket, &scope); err != nil { + return nil, err + } + + // With the database state created, we'll now write down the address + // schema of this particular scope type. + scopeSchemas := ns.NestedReadWriteBucket(scopeSchemaBucketName) + if scopeSchemas == nil { + str := "scope schema bucket not found" + return nil, managerError(ErrDatabase, str, nil) + } + scopeKey := scopeToBytes(&scope) + schemaBytes := scopeSchemaToBytes(&addrSchema) + err = scopeSchemas.Put(scopeKey[:], schemaBytes) + if err != nil { + return nil, err + } + + // With the database state created, we'll now derive the cointype key + // using the master HD private key, then encrypt it along with the + // first account using our crypto keys. + err = createManagerKeyScope( + ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv, + ) + if err != nil { + return nil, err + } + + // Finally, we'll register this new scoped manager with the root + // manager. + m.scopedManagers[scope] = &ScopedKeyManager{ + scope: scope, + addrSchema: addrSchema, + rootManager: m, + addrs: make(map[addrKey]ManagedAddress), + acctInfo: make(map[uint32]*accountInfo), + } + m.externalAddrSchemas[addrSchema.ExternalAddrType] = append( + m.externalAddrSchemas[addrSchema.ExternalAddrType], scope, + ) + m.internalAddrSchemas[addrSchema.InternalAddrType] = append( + m.internalAddrSchemas[addrSchema.InternalAddrType], scope, + ) + + return m.scopedManagers[scope], nil } -// AddrAccount returns the account to which the given address belongs. -func (m *Manager) AddrAccount(ns walletdb.ReadBucket, address btcutil.Address) (uint32, error) { - account, err := fetchAddrAccount(ns, address.ScriptAddress()) - if err != nil { - return 0, maybeConvertDbError(err) +// FetchScopedKeyManager attempts to fetch an active scoped manager according to +// its registered scope. If the manger is found, then a nil error is returned +// along with the active scoped manager. Otherwise, a nil manager and a non-nil +// error will be returned. +func (m *Manager) FetchScopedKeyManager(scope KeyScope) (*ScopedKeyManager, error) { + m.mtx.RLock() + defer m.mtx.RUnlock() + + sm, ok := m.scopedManagers[scope] + if !ok { + str := fmt.Sprintf("scope %v not found", scope) + return nil, managerError(ErrScopeNotFound, str, nil) } - return account, nil + + return sm, nil +} + +// ActiveScopedKeyManagers returns a slice of all the active scoped key +// managers currently known by the root key manager. +func (m *Manager) ActiveScopedKeyManagers() []*ScopedKeyManager { + m.mtx.RLock() + defer m.mtx.RUnlock() + + var scopedManagers []*ScopedKeyManager + for _, smgr := range m.scopedManagers { + scopedManagers = append(scopedManagers, smgr) + } + + return scopedManagers +} + +// ScopesForExternalAddrTypes returns the set of key scopes that are able to +// produce the target address type as external addresses. +func (m *Manager) ScopesForExternalAddrType(addrType AddressType) []KeyScope { + m.mtx.RLock() + defer m.mtx.RUnlock() + + scopes, _ := m.externalAddrSchemas[addrType] + return scopes +} + +// ScopesForInternalAddrTypes returns the set of key scopes that are able to +// produce the target address type as internal addresses. +func (m *Manager) ScopesForInternalAddrTypes(addrType AddressType) []KeyScope { + m.mtx.RLock() + defer m.mtx.RUnlock() + + scopes, _ := m.internalAddrSchemas[addrType] + return scopes +} + +// NeuterRootKey is a special method that should be used once a caller is +// *certain* that no further scoped managers are to be created. This method +// will *delete* the encrypted master HD root private key from the database. +func (m *Manager) NeuterRootKey(ns walletdb.ReadWriteBucket) error { + m.mtx.Lock() + defer m.mtx.Unlock() + + // First, we'll fetch the current master HD keys from the database. + masterRootPrivEnc, _, err := fetchMasterHDKeys(ns) + if err != nil { + return err + } + + // If the root master private key is already nil, then we'll return a + // nil error here as the root key has already been permanently + // neutered. + if masterRootPrivEnc == nil { + return nil + } + zero.Bytes(masterRootPrivEnc) + + // Otherwise, we'll neuter the root key permanently by deleting the + // encrypted master HD key from the database. + return ns.NestedReadWriteBucket(mainBucketName).Delete(masterHDPrivName) +} + +// 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(ns walletdb.ReadBucket, + address btcutil.Address) (ManagedAddress, error) { + + m.mtx.RLock() + defer m.mtx.RUnlock() + + // We'll iterate through each of the known scoped managers, and see if + // any of them now of the target address. + for _, scopedMgr := range m.scopedManagers { + addr, err := scopedMgr.Address(ns, address) + if err != nil { + continue + } + + return addr, nil + } + + // If the address wasn't known to any of the scoped managers, then + // we'll return an error. + str := fmt.Sprintf("unable to find key for addr %v", address) + return nil, managerError(ErrAddressNotFound, str, nil) +} + +// MarkUsed updates the used flag for the provided address. +func (m *Manager) MarkUsed(ns walletdb.ReadWriteBucket, address btcutil.Address) error { + m.mtx.RLock() + defer m.mtx.RUnlock() + + // Run through all the known scoped managers, and attempt to mark the + // address as used for each one. + + // First, we'll figure out which scoped manager this address belong to. + for _, scopedMgr := range m.scopedManagers { + if _, err := scopedMgr.Address(ns, address); err != nil { + continue + } + + // We've found the manager that this address belongs to, so we + // can mark the address as used and return. + return scopedMgr.MarkUsed(ns, address) + } + + // If we get to this point, then we weren't able to find the address in + // any of the managers, so we'll exit with an error. + str := fmt.Sprintf("unable to find key for addr %v", address) + return managerError(ErrAddressNotFound, str, nil) +} + +// AddrAccount returns the account to which the given address belongs. We also +// return the scoped manager that owns the addr+account combo. +func (m *Manager) AddrAccount(ns walletdb.ReadBucket, + address btcutil.Address) (*ScopedKeyManager, uint32, error) { + + m.mtx.RLock() + defer m.mtx.RUnlock() + + for _, scopedMgr := range m.scopedManagers { + if _, err := scopedMgr.Address(ns, address); err != nil { + continue + } + + // We've found the manager that this address belongs to, so we + // can retrieve the address' account along with the manager + // that the addr belongs to. + accNo, err := scopedMgr.AddrAccount(ns, address) + if err != nil { + return nil, 0, err + } + + return scopedMgr, accNo, err + } + + // If we get to this point, then we weren't able to find the address in + // any of the managers, so we'll exit with an error. + str := fmt.Sprintf("unable to find key for addr %v", address) + return nil, 0, managerError(ErrAddressNotFound, str, nil) +} + +// ForEachActiveAccountAddress calls the given function with each active +// address of the given account stored in the manager, across all active +// scopes, breaking early on error. +// +// TODO(tuxcanfly): actually return only active addresses +func (m *Manager) ForEachActiveAccountAddress(ns walletdb.ReadBucket, + account uint32, fn func(maddr ManagedAddress) error) error { + + m.mtx.RLock() + defer m.mtx.RUnlock() + + for _, scopedMgr := range m.scopedManagers { + err := scopedMgr.ForEachActiveAccountAddress(ns, account, fn) + if err != nil { + return err + } + } + + return nil +} + +// ForEachActiveAddress calls the given function with each active address +// stored in the manager, breaking early on error. +func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcutil.Address) error) error { + m.mtx.Lock() + defer m.mtx.Unlock() + + for _, scopedMgr := range m.scopedManagers { + err := scopedMgr.ForEachActiveAddress(ns, fn) + if err != nil { + return err + } + } + + return nil +} + +// ForEachAccountAddress calls the given function with each address of +// the given account stored in the manager, breaking early on error. +func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32, + fn func(maddr ManagedAddress) error) error { + + m.mtx.Lock() + defer m.mtx.Unlock() + + for _, scopedMgr := range m.scopedManagers { + err := scopedMgr.ForEachAccountAddress(ns, account, fn) + if err != nil { + return err + } + } + + return nil +} + +// ChainParams returns the chain parameters for this address manager. +func (m *Manager) ChainParams() *chaincfg.Params { + // NOTE: No need for mutex here since the net field does not change + // after the manager instance is created. + + return m.chainParams } // 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. The new passphrase -// keys are derived using the scrypt parameters in the options, so changing the -// passphrase may be used to bump the computational difficulty needed to brute -// force the passphrase. -func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, newPassphrase []byte, private bool, config *ScryptOptions) error { +// provided value depending on the private flag. In order to change the +// private password, the address manager must not be watching-only. The new +// passphrase keys are derived using the scrypt parameters in the options, so +// changing the passphrase may be used to bump the computational difficulty +// needed to brute force the passphrase. +func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, + newPassphrase []byte, private bool, config *ScryptOptions) error { + // No private passphrase to change for a watching-only address manager. if private && m.watchingOnly { return managerError(ErrWatchingOnly, errWatchingOnly, nil) @@ -846,8 +807,8 @@ func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, n return managerError(ErrCrypto, str, err) } - // Re-encrypt the crypto script key using the new master private - // key. + // 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" @@ -874,7 +835,7 @@ func (m *Manager) ChangePassphrase(ns walletdb.ReadWriteBucket, oldPassphrase, n zero.Bytes(saltedPassphrase) } - // Save the new keys and params to the the db in a single + // Save the new keys and params to the db in a single // transaction. err = putCryptoKeys(ns, nil, encPriv, encScript) if err != nil { @@ -943,10 +904,11 @@ func (m *Manager) ConvertToWatchingOnly(ns walletdb.ReadWriteBucket) error { return nil } - // Remove all private key material and mark the new database as watching - // only. - err := deletePrivateKeys(ns) - if err != nil { + var err error + + // Remove all private key material and mark the new database as + // watching only. + if err := deletePrivateKeys(ns); err != nil { return maybeConvertDbError(err) } @@ -967,21 +929,25 @@ func (m *Manager) ConvertToWatchingOnly(ns walletdb.ReadWriteBucket) error { // material is no longer needed. // Clear and remove all of the encrypted acount private keys. - for _, acctInfo := range m.acctInfo { - zero.Bytes(acctInfo.acctKeyEncrypted) - acctInfo.acctKeyEncrypted = nil + for _, manager := range m.scopedManagers { + for _, acctInfo := range manager.acctInfo { + zero.Bytes(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.Bytes(addr.privKeyEncrypted) - addr.privKeyEncrypted = nil - case *scriptAddress: - zero.Bytes(addr.scriptEncrypted) - addr.scriptEncrypted = nil + for _, manager := range m.scopedManagers { + for _, ma := range manager.addrs { + switch addr := ma.(type) { + case *managedAddress: + zero.Bytes(addr.privKeyEncrypted) + addr.privKeyEncrypted = nil + case *scriptAddress: + zero.Bytes(addr.scriptEncrypted) + addr.scriptEncrypted = nil + } } } @@ -1004,233 +970,6 @@ func (m *Manager) ConvertToWatchingOnly(ns walletdb.ReadWriteBucket) error { } -// 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(ns walletdb.ReadBucket, addressID []byte) bool { - // Check the in-memory map first since it's faster than a db access. - if _, ok := m.addrs[addrKey(addressID)]; ok { - return true - } - - // Check the database if not already found above. - return existsAddress(ns, addressID) -} - -// 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(ns walletdb.ReadWriteBucket, wif *btcutil.WIF, bs *BlockStamp) (ManagedPubKeyAddress, error) { - // Ensure the address is intended for network the address manager is - // associated with. - if !wif.IsForNet(m.chainParams) { - str := fmt.Sprintf("private key is not for the same network the "+ - "address manager is configured for (%s)", - m.chainParams.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 := m.existsAddress(ns, pubKeyHash) - if alreadyExists { - str := fmt.Sprintf("address for public key %x already exists", - serializedPubKey) - return nil, managerError(ErrDuplicateAddress, 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.Bytes(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 = putImportedAddress(ns, pubKeyHash, ImportedAddrAccount, ssNone, - encryptedPubKey, encryptedPrivKey) - if err != nil { - return nil, err - } - - if updateStartBlock { - err := putStartBlock(ns, bs) - 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 - // TODO(roasbeef): default type? need to watch for all - if !m.watchingOnly { - managedAddr, err = newManagedAddress(m, ImportedAddrAccount, - wif.PrivKey, wif.CompressPubKey, adtChain) - } else { - pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey) - managedAddr, err = newManagedAddressWithoutPrivKey(m, - ImportedAddrAccount, pubKey, wif.CompressPubKey, - adtChain) - } - 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(ns walletdb.ReadWriteBucket, 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 := m.existsAddress(ns, scriptHash) - if alreadyExists { - str := fmt.Sprintf("address for script hash %x already exists", - scriptHash) - return nil, managerError(ErrDuplicateAddress, 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 = putScriptAddress(ns, scriptHash, ImportedAddrAccount, - ssNone, encryptedHash, encryptedScript) - if err != nil { - return nil, maybeConvertDbError(err) - } - - if updateStartBlock { - err := putStartBlock(ns, bs) - 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. @@ -1264,23 +1003,6 @@ func (m *Manager) Lock() error { return nil } -// lookupAccount loads account number stored in the manager for the given -// account name -// -// This function MUST be called with the manager lock held for reads. -func (m *Manager) lookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) { - return fetchAccountByName(ns, name) -} - -// LookupAccount loads account number stored in the manager for the given -// account name -func (m *Manager) LookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) { - m.mtx.RLock() - defer m.mtx.RUnlock() - - return m.lookupAccount(ns, name) -} - // 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 @@ -1403,286 +1125,6 @@ func (m *Manager) Unlock(ns walletdb.ReadBucket, passphrase []byte) error { return nil } -// fetchUsed returns true if the provided address id was flagged used. -func (m *Manager) fetchUsed(ns walletdb.ReadBucket, addressID []byte) bool { - return fetchAddressUsed(ns, addressID) -} - -// MarkUsed updates the used flag for the provided address. -func (m *Manager) MarkUsed(ns walletdb.ReadWriteBucket, address btcutil.Address) error { - addressID := address.ScriptAddress() - err := markAddressUsed(ns, addressID) - if err != nil { - return maybeConvertDbError(err) - } - // Clear caches which might have stale entries for used addresses - m.mtx.Lock() - delete(m.addrs, addrKey(addressID)) - m.mtx.Unlock() - return nil -} - -// ChainParams returns the chain parameters for this address manager. -func (m *Manager) ChainParams() *chaincfg.Params { - // NOTE: No need for mutex here since the net field does not change - // after the manager instance is created. - - return m.chainParams -} - -// 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(ns walletdb.ReadWriteBucket, 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(ns, 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.chainParams) - - 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. - addr, err := newManagedAddressFromExtKey(m, account, nextKey, addrType) - if err != nil { - return nil, err - } - if internal { - addr.internal = true - } - managedAddr := addr - nextKey.Zero() - - 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 - // TODO(roasbeef): need to distinguish between p2pkh and p2wkh - addressID := ma.Address().ScriptAddress() - - switch a := ma.(type) { - case *managedAddress: - err := putChainedAddress(tx, addressID, account, ssFull, - info.branch, info.index, addrType) - if err != nil { - return err - } - case *scriptAddress: // TODO(roasbeef): no longer needed? - encryptedHash, err := m.cryptoKeyPub.Encrypt(a.AddrHash()) - if err != nil { - str := fmt.Sprintf("failed to encrypt script hash %x", - a.AddrHash()) - return managerError(ErrCrypto, str, err) - } - - err = putScriptAddress(tx, a.AddrHash(), ImportedAddrAccount, - ssNone, encryptedHash, a.scriptEncrypted) - if err != nil { - return 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(ns walletdb.ReadWriteBucket, 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(ns, 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(ns walletdb.ReadWriteBucket, 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(ns, 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(ns walletdb.ReadBucket, 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(ns, account) - if err != nil { - return nil, err - } - - if acctInfo.nextExternalIndex > 0 { - return acctInfo.lastExternalAddr, nil - } - - return nil, managerError(ErrAddressNotFound, "no previous external address", 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(ns walletdb.ReadBucket, 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(ns, account) - if err != nil { - return nil, err - } - - if acctInfo.nextInternalIndex > 0 { - return acctInfo.lastInternalAddr, nil - } - - return nil, managerError(ErrAddressNotFound, "no previous internal address", nil) -} - // ValidateAccountName validates the given account name and returns an error, if any. func ValidateAccountName(name string) error { if name == "" { @@ -1696,226 +1138,6 @@ func ValidateAccountName(name string) error { return nil } -// NewAccount creates and returns a new account stored in the manager based -// on the given account name. If an account with the same name already exists, -// ErrDuplicateAccount will be returned. Since creating a new account requires -// access to the cointype keys (from which extended account keys are derived), -// it requires the manager to be unlocked. -func (m *Manager) NewAccount(ns walletdb.ReadWriteBucket, name string) (uint32, error) { - if m.watchingOnly { - return 0, managerError(ErrWatchingOnly, errWatchingOnly, nil) - } - - m.mtx.Lock() - defer m.mtx.Unlock() - - if m.locked { - return 0, managerError(ErrLocked, errLocked, nil) - } - - // Validate account name - if err := ValidateAccountName(name); err != nil { - return 0, err - } - - // Check that account with the same name does not exist - _, err := m.lookupAccount(ns, name) - if err == nil { - str := fmt.Sprintf("account with the same name already exists") - return 0, managerError(ErrDuplicateAccount, str, err) - } - - // Fetch latest account, and create a new account in the same transaction - // Fetch the latest account number to generate the next account number - account, err := fetchLastAccount(ns) - if err != nil { - return 0, err - } - account++ - // Fetch the cointype key which will be used to derive the next account - // extended keys - _, coinTypePrivEnc, err := fetchCoinTypeKeys(ns) - if err != nil { - return 0, err - } - - // Decrypt the cointype key - serializedKeyPriv, err := m.cryptoKeyPriv.Decrypt(coinTypePrivEnc) - if err != nil { - str := fmt.Sprintf("failed to decrypt cointype serialized private key") - return 0, managerError(ErrLocked, str, err) - } - coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv)) - zero.Bytes(serializedKeyPriv) - if err != nil { - str := fmt.Sprintf("failed to create cointype extended private key") - return 0, managerError(ErrKeyChain, str, err) - } - - // Derive the account key using the cointype key - acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, account) - coinTypeKeyPriv.Zero() - if err != nil { - str := "failed to convert private key for account" - return 0, managerError(ErrKeyChain, str, err) - } - acctKeyPub, err := acctKeyPriv.Neuter() - if err != nil { - str := "failed to convert public key for account" - return 0, managerError(ErrKeyChain, str, err) - } - // Encrypt the default account keys with the associated crypto keys. - acctPubEnc, err := m.cryptoKeyPub.Encrypt([]byte(acctKeyPub.String())) - if err != nil { - str := "failed to encrypt public key for account" - return 0, managerError(ErrCrypto, str, err) - } - acctPrivEnc, err := m.cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) - if err != nil { - str := "failed to encrypt private key for account" - return 0, managerError(ErrCrypto, str, err) - } - // We have the encrypted account extended keys, so save them to the - // database - err = putAccountInfo(ns, account, acctPubEnc, acctPrivEnc, 0, 0, name) - if err != nil { - return 0, err - } - - // Save last account metadata - if err := putLastAccount(ns, account); err != nil { - return 0, err - } - - return account, nil -} - -// RenameAccount renames an account stored in the manager based on the -// given account number with the given name. If an account with the same name -// already exists, ErrDuplicateAccount will be returned. -func (m *Manager) RenameAccount(ns walletdb.ReadWriteBucket, account uint32, name string) error { - m.mtx.Lock() - defer m.mtx.Unlock() - - // Ensure that a reserved account is not being renamed. - if isReservedAccountNum(account) { - str := "reserved account cannot be renamed" - return managerError(ErrInvalidAccount, str, nil) - } - - // Check that account with the new name does not exist - _, err := m.lookupAccount(ns, name) - if err == nil { - str := fmt.Sprintf("account with the same name already exists") - return managerError(ErrDuplicateAccount, str, err) - } - // Validate account name - if err := ValidateAccountName(name); err != nil { - return err - } - - rowInterface, err := fetchAccountInfo(ns, account) - if err != nil { - return 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) - } - // Remove the old name key from the accout id index - if err = deleteAccountIDIndex(ns, account); err != nil { - return err - } - // Remove the old name key from the account name index - if err = deleteAccountNameIndex(ns, row.name); err != nil { - return err - } - err = putAccountInfo(ns, account, row.pubKeyEncrypted, - row.privKeyEncrypted, row.nextExternalIndex, row.nextInternalIndex, name) - if err != nil { - return err - } - - // Update in-memory account info with new name if cached and the db - // write was successful. - if err == nil { - if acctInfo, ok := m.acctInfo[account]; ok { - acctInfo.acctName = name - } - } - - return err -} - -// AccountName returns the account name for the given account number -// stored in the manager. -func (m *Manager) AccountName(ns walletdb.ReadBucket, account uint32) (string, error) { - return fetchAccountName(ns, account) -} - -// ForEachAccount calls the given function with each account stored in the -// manager, breaking early on error. -func (m *Manager) ForEachAccount(ns walletdb.ReadBucket, fn func(account uint32) error) error { - return forEachAccount(ns, fn) -} - -// LastAccount returns the last account stored in the manager. -func (m *Manager) LastAccount(ns walletdb.ReadBucket) (uint32, error) { - return fetchLastAccount(ns) -} - -// ForEachAccountAddress calls the given function with each address of -// the given account stored in the manager, breaking early on error. -func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(maddr ManagedAddress) error) error { - m.mtx.Lock() - defer m.mtx.Unlock() - - addrFn := func(rowInterface interface{}) error { - managedAddr, err := m.rowInterfaceToManaged(ns, rowInterface) - if err != nil { - return err - } - return fn(managedAddr) - } - err := forEachAccountAddress(ns, account, addrFn) - if err != nil { - return maybeConvertDbError(err) - } - return nil -} - -// ForEachActiveAccountAddress calls the given function with each active -// address of the given account stored in the manager, breaking early on -// error. -// TODO(tuxcanfly): actually return only active addresses -func (m *Manager) ForEachActiveAccountAddress(ns walletdb.ReadBucket, account uint32, fn func(maddr ManagedAddress) error) error { - return m.ForEachAccountAddress(ns, account, fn) -} - -// ForEachActiveAddress calls the given function with each active address -// stored in the manager, breaking early on error. -func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcutil.Address) error) error { - m.mtx.Lock() - defer m.mtx.Unlock() - - addrFn := func(rowInterface interface{}) error { - managedAddr, err := m.rowInterfaceToManaged(ns, rowInterface) - if err != nil { - return err - } - return fn(managedAddr.Address()) - } - - err := forEachActiveAddress(ns, addrFn) - if err != nil { - return maybeConvertDbError(err) - } - return 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. @@ -1966,8 +1188,8 @@ func (m *Manager) Encrypt(keyType CryptoKeyType, in []byte) ([]byte, error) { // 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. + // 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() @@ -1987,15 +1209,14 @@ func (m *Manager) Decrypt(keyType CryptoKeyType, in []byte) ([]byte, error) { func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor, cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState, - birthday time.Time, privPassphraseSalt [saltSize]byte) *Manager { + birthday time.Time, privPassphraseSalt [saltSize]byte, + scopedManagers map[KeyScope]*ScopedKeyManager) *Manager { - return &Manager{ + m := &Manager{ chainParams: chainParams, - addrs: make(map[addrKey]ManagedAddress), syncState: *syncInfo, - birthday: birthday, locked: true, - acctInfo: make(map[uint32]*accountInfo), + birthday: birthday, masterKeyPub: masterKeyPub, masterKeyPriv: masterKeyPriv, cryptoKeyPub: cryptoKeyPub, @@ -2004,7 +1225,25 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, cryptoKeyScriptEncrypted: cryptoKeyScriptEncrypted, cryptoKeyScript: &cryptoKey{}, privPassphraseSalt: privPassphraseSalt, + scopedManagers: scopedManagers, + externalAddrSchemas: make(map[AddressType][]KeyScope), + internalAddrSchemas: make(map[AddressType][]KeyScope), } + + for _, sMgr := range m.scopedManagers { + externalType := sMgr.AddrSchema().ExternalAddrType + internalType := sMgr.AddrSchema().InternalAddrType + scope := sMgr.Scope() + + m.externalAddrSchemas[externalType] = append( + m.externalAddrSchemas[externalType], scope, + ) + m.internalAddrSchemas[internalType] = append( + m.internalAddrSchemas[internalType], scope, + ) + } + + return m } // deriveCoinTypeKey derives the cointype key which can be used to derive the @@ -2012,30 +1251,35 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey, // given the coin type key. // // In particular this is the hierarchical deterministic extended key path: -// m/44'/' +// m/purpose'/' func deriveCoinTypeKey(masterNode *hdkeychain.ExtendedKey, - coinType uint32) (*hdkeychain.ExtendedKey, error) { + scope KeyScope) (*hdkeychain.ExtendedKey, error) { + // Enforce maximum coin type. - if coinType > maxCoinType { + if scope.Coin > maxCoinType { err := managerError(ErrCoinTypeTooHigh, errCoinTypeTooHigh, nil) return nil, err } // The hierarchy described by BIP0043 is: // m/'/* + // // This is further extended by BIP0044 to: // m/44'/'/'//
// + // However, as this is a generic key store for any family for BIP0044 + // standards, we'll use the custom scope to govern our key derivation. + // // 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) + purpose, err := masterNode.Child(scope.Purpose + 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) + coinTypeKey, err := purpose.Child(scope.Coin + hdkeychain.HardenedKeyStart) if err != nil { return nil, err } @@ -2047,9 +1291,10 @@ func deriveCoinTypeKey(masterNode *hdkeychain.ExtendedKey, // hierarchy described by BIP0044 given the master node. // // In particular this is the hierarchical deterministic extended key path: -// m/44'/'/' +// m/purpose'/'/' func deriveAccountKey(coinTypeKey *hdkeychain.ExtendedKey, account uint32) (*hdkeychain.ExtendedKey, error) { + // Enforce maximum account number. if account > MaxAccountNum { err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil) @@ -2063,11 +1308,11 @@ func deriveAccountKey(coinTypeKey *hdkeychain.ExtendedKey, // 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. +// hierarchy described by the BIP0044 family so long as the account key is +// already derived accordingly. // // In particular this is the hierarchical deterministic extended key path: -// m/44'/'/'/ +// m/purpose'/'/'/ // // The branch is 0 for external addresses and 1 for internal addresses. func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error { @@ -2082,9 +1327,11 @@ func checkBranchKeys(acctKey *hdkeychain.ExtendedKey) error { } // 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(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chaincfg.Params) (*Manager, error) { +// the passed opened database. The public passphrase is required to decrypt +// the public keys. +func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, + chainParams *chaincfg.Params) (*Manager, error) { + // Verify the version is neither too old or too new. version, err := fetchManagerVersion(ns) if err != nil { @@ -2176,13 +1423,43 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chai return nil, managerError(ErrCrypto, str, err) } - // 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(chainParams, &masterKeyPub, &masterKeyPriv, + // Next, we'll need to load all known manager scopes from disk. Each + // scope is on a distinct top-level path within our HD key chain. + scopedManagers := make(map[KeyScope]*ScopedKeyManager) + err = forEachKeyScope(ns, func(scope KeyScope) error { + scopeSchema, err := fetchScopeAddrSchema(ns, &scope) + if err != nil { + return err + } + + scopedManagers[scope] = &ScopedKeyManager{ + scope: scope, + addrSchema: *scopeSchema, + addrs: make(map[addrKey]ManagedAddress), + acctInfo: make(map[uint32]*accountInfo), + } + + return nil + }) + if err != nil { + return nil, err + } + + // 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( + chainParams, &masterKeyPub, &masterKeyPriv, cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo, - birthday, privPassphraseSalt) + birthday, privPassphraseSalt, scopedManagers, + ) mgr.watchingOnly = watchingOnly + + for _, scopedManager := range scopedManagers { + scopedManager.rootManager = mgr + } + return mgr, nil } @@ -2191,12 +1468,14 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chai // 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. +// 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(ns walletdb.ReadBucket, pubPassphrase []byte, chainParams *chaincfg.Params) (*Manager, error) { +func Open(ns walletdb.ReadBucket, pubPassphrase []byte, + chainParams *chaincfg.Params) (*Manager, error) { + // Return an error if the manager has NOT already been created in the // given database namespace. exists := managerExists(ns) @@ -2216,6 +1495,107 @@ func DoUpgrades(db walletdb.DB, namespaceKey []byte, pubPassphrase []byte, return upgradeManager(db, namespaceKey, pubPassphrase, chainParams, cbs) } +// createManagerKeyScope creates a new key scoped for a target manager's scope. +// This partitions key derivation for a particular purpose+coin tuple, allowing +// multiple address derivation schems to be maintained concurrently. +func createManagerKeyScope(ns walletdb.ReadWriteBucket, + scope KeyScope, root *hdkeychain.ExtendedKey, + cryptoKeyPub, cryptoKeyPriv EncryptorDecryptor) error { + + // Derive the cointype key according to the passed scope. + coinTypeKeyPriv, err := deriveCoinTypeKey(root, scope) + if err != nil { + str := "failed to derive cointype extended key" + return managerError(ErrKeyChain, str, err) + } + defer coinTypeKeyPriv.Zero() + + // Derive the account key for the first account according our + // BIP0044-like derivation. + acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 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 managerError(ErrKeyChain, str, + hdkeychain.ErrUnusableSeed) + } + + return err + } + + // Ensure the branch keys can be derived for the provided seed according + // to our BIP0044-like derivation. + 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 managerError(ErrKeyChain, str, + hdkeychain.ErrUnusableSeed) + } + + return 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 managerError(ErrKeyChain, str, err) + } + + // Encrypt the cointype keys with the associated crypto keys. + coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() + if err != nil { + str := "failed to convert cointype private key" + return managerError(ErrKeyChain, str, err) + } + coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) + if err != nil { + str := "failed to encrypt cointype public key" + return managerError(ErrCrypto, str, err) + } + coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) + if err != nil { + str := "failed to encrypt cointype private key" + return 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 managerError(ErrCrypto, str, err) + } + acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) + if err != nil { + str := "failed to encrypt private key for account 0" + return managerError(ErrCrypto, str, err) + } + + // Save the encrypted cointype keys to the database. + err = putCoinTypeKeys(ns, &scope, coinTypePubEnc, coinTypePrivEnc) + if err != nil { + return err + } + + // Save the information for the default account to the database. + err = putAccountInfo( + ns, &scope, DefaultAccountNum, acctPubEnc, acctPrivEnc, 0, 0, + defaultAccountName, + ) + if err != nil { + return err + } + + return putAccountInfo( + ns, &scope, ImportedAddrAccount, nil, nil, 0, 0, + ImportedAddrAccountName, + ) +} + // Create creates a new 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 @@ -2225,247 +1605,182 @@ func DoUpgrades(db walletdb.DB, namespaceKey []byte, pubPassphrase []byte, // 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. +// 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. +// 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(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []byte, chainParams *chaincfg.Params, config *ScryptOptions) error { - err := func() error { - // Return an error if the manager has already been created in the given - // database namespace. - exists := managerExists(ns) - if exists { - return managerError(ErrAlreadyExists, errAlreadyExists, nil) - } +func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []byte, + chainParams *chaincfg.Params, config *ScryptOptions) error { - // Ensure the private passphrase is not empty. - if len(privPassphrase) == 0 { - str := "private passphrase may not be empty" - return managerError(ErrEmptyPassphrase, str, nil) - } + // Return an error if the manager has already been created in + // the given database namespace. + exists := managerExists(ns) + if exists { + return managerError(ErrAlreadyExists, errAlreadyExists, nil) + } - // Perform the initial bucket creation and database namespace setup. - if err := createManagerNS(ns); err != nil { - return err - } + // Ensure the private passphrase is not empty. + if len(privPassphrase) == 0 { + str := "private passphrase may not be empty" + return managerError(ErrEmptyPassphrase, str, nil) + } - if config == nil { - config = &DefaultScryptOptions - } + // Perform the initial bucket creation and database namespace setup. + if err := createManagerNS(ns, ScopeAddrMap); err != nil { + return maybeConvertDbError(err) + } - // Generate the BIP0044 HD key structure to ensure the provided seed - // can generate the required structure with no issues. + if config == nil { + config = &DefaultScryptOptions + } - // Derive the master extended key from the seed. - root, err := hdkeychain.NewMaster(seed, chainParams) - if err != nil { - str := "failed to derive master extended key" - return 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 managerError(ErrCrypto, str, err) + } + masterKeyPriv, err := newSecretKey(&privPassphrase, config) + if err != nil { + str := "failed to master private key" + return managerError(ErrCrypto, str, err) + } + defer masterKeyPriv.Zero() - // Derive the cointype key according to BIP0044. - coinTypeKeyPriv, err := deriveCoinTypeKey(root, chainParams.HDCoinType) - if err != nil { - str := "failed to derive cointype extended key" - return managerError(ErrKeyChain, str, err) - } - defer coinTypeKeyPriv.Zero() + // Generate the private passphrase salt. This is used when hashing + // passwords to detect whether an unlock can be avoided when the manager + // is already unlocked. + var privPassphraseSalt [saltSize]byte + _, err = rand.Read(privPassphraseSalt[:]) + if err != nil { + str := "failed to read random source for passphrase salt" + return managerError(ErrCrypto, str, err) + } - // Derive the account key for the first account according to BIP0044. - acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, 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 managerError(ErrKeyChain, str, - hdkeychain.ErrUnusableSeed) - } + // 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 managerError(ErrCrypto, str, err) + } + cryptoKeyPriv, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto private key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyPriv.Zero() + cryptoKeyScript, err := newCryptoKey() + if err != nil { + str := "failed to generate crypto script key" + return managerError(ErrCrypto, str, err) + } + defer cryptoKeyScript.Zero() - return 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 managerError(ErrCrypto, str, err) + } + cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) + if err != nil { + str := "failed to encrypt crypto private key" + return managerError(ErrCrypto, str, err) + } + cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) + if err != nil { + str := "failed to encrypt crypto script key" + return managerError(ErrCrypto, str, 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 managerError(ErrKeyChain, str, - hdkeychain.ErrUnusableSeed) - } + // Use the genesis block for the passed chain as the created at block + // for the default. + createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0} - return err - } + // Create the initial sync state. + syncInfo := newSyncState(createdAt, createdAt) - // 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 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 managerError(ErrCrypto, str, err) - } - masterKeyPriv, err := newSecretKey(&privPassphrase, config) - if err != nil { - str := "failed to master private key" - return managerError(ErrCrypto, str, err) - } - defer masterKeyPriv.Zero() - - // Generate the private passphrase salt. This is used when hashing - // passwords to detect whether an unlock can be avoided when the manager - // is already unlocked. - var privPassphraseSalt [saltSize]byte - _, err = rand.Read(privPassphraseSalt[:]) - if err != nil { - str := "failed to read random source for passphrase salt" - return 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 managerError(ErrCrypto, str, err) - } - cryptoKeyPriv, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto private key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyPriv.Zero() - cryptoKeyScript, err := newCryptoKey() - if err != nil { - str := "failed to generate crypto script key" - return managerError(ErrCrypto, str, err) - } - defer cryptoKeyScript.Zero() - - // 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 managerError(ErrCrypto, str, err) - } - cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes()) - if err != nil { - str := "failed to encrypt crypto private key" - return managerError(ErrCrypto, str, err) - } - cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes()) - if err != nil { - str := "failed to encrypt crypto script key" - return managerError(ErrCrypto, str, err) - } - - // Encrypt the cointype keys with the associated crypto keys. - coinTypeKeyPub, err := coinTypeKeyPriv.Neuter() - if err != nil { - str := "failed to convert cointype private key" - return managerError(ErrKeyChain, str, err) - } - coinTypePubEnc, err := cryptoKeyPub.Encrypt([]byte(coinTypeKeyPub.String())) - if err != nil { - str := "failed to encrypt cointype public key" - return managerError(ErrCrypto, str, err) - } - coinTypePrivEnc, err := cryptoKeyPriv.Encrypt([]byte(coinTypeKeyPriv.String())) - if err != nil { - str := "failed to encrypt cointype private key" - return 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 managerError(ErrCrypto, str, err) - } - acctPrivEnc, err := cryptoKeyPriv.Encrypt([]byte(acctKeyPriv.String())) - if err != nil { - str := "failed to encrypt private key for account 0" - return managerError(ErrCrypto, str, err) - } - - // Use the genesis block for the passed chain as the created at block - // for the default. - createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0} - - // Create the initial sync state. - syncInfo := newSyncState(createdAt, createdAt) - - // Save the master key params to the database. - pubParams := masterKeyPub.Marshal() - privParams := masterKeyPriv.Marshal() - err = putMasterKeyParams(ns, pubParams, privParams) - if err != nil { - return err - } - - // Save the encrypted crypto keys to the database. - err = putCryptoKeys(ns, cryptoKeyPubEnc, cryptoKeyPrivEnc, - cryptoKeyScriptEnc) - if err != nil { - return err - } - - // Save the encrypted cointype keys to the database. - err = putCoinTypeKeys(ns, coinTypePubEnc, coinTypePrivEnc) - if err != nil { - return err - } - - // Save the fact this is not a watching-only address manager to - // the database. - err = putWatchingOnly(ns, false) - if err != nil { - return err - } - - // Save the initial synced to state. - err = putSyncedTo(ns, &syncInfo.syncedTo) - if err != nil { - return err - } - err = putStartBlock(ns, &syncInfo.startBlock) - if err != nil { - return err - } - // Use 48 hours as margin of safety for wallet birthday. - err = putBirthday(ns, time.Now().Add(-48*time.Hour)) - if err != nil { - return err - } - - // Save the information for the imported account to the database. - err = putAccountInfo(ns, ImportedAddrAccount, nil, - nil, 0, 0, ImportedAddrAccountName) - if err != nil { - return err - } - - // Save the information for the default account to the database. - err = putAccountInfo(ns, DefaultAccountNum, acctPubEnc, - acctPrivEnc, 0, 0, defaultAccountName) - return err - }() + // Save the master key params to the database. + pubParams := masterKeyPub.Marshal() + privParams := masterKeyPriv.Marshal() + err = putMasterKeyParams(ns, pubParams, privParams) if err != nil { return maybeConvertDbError(err) } - return nil + // 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. + rootKey, err := hdkeychain.NewMaster(seed, chainParams) + if err != nil { + str := "failed to derive master extended key" + return managerError(ErrKeyChain, str, err) + } + rootPubKey, err := rootKey.Neuter() + if err != nil { + str := "failed to neuter master extended key" + return managerError(ErrKeyChain, str, err) + } + + // Next, for each registers default manager scope, we'll create the + // hardened cointype key for it, as well as the first default account. + for _, defaultScope := range DefaultKeyScopes { + err := createManagerKeyScope( + ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv, + ) + if err != nil { + return maybeConvertDbError(err) + } + } + + // Before we proceed, we'll also store the root master private key + // within the database in an encrypted format. This is required as in + // the future, we may need to create additional scoped key managers. + masterHDPrivKeyEnc, err := cryptoKeyPriv.Encrypt([]byte(rootKey.String())) + if err != nil { + return maybeConvertDbError(err) + } + masterHDPubKeyEnc, err := cryptoKeyPub.Encrypt([]byte(rootPubKey.String())) + if err != nil { + return maybeConvertDbError(err) + } + err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc) + if err != nil { + return maybeConvertDbError(err) + } + + // Save the encrypted crypto keys to the database. + err = putCryptoKeys(ns, cryptoKeyPubEnc, cryptoKeyPrivEnc, + cryptoKeyScriptEnc) + if err != nil { + return maybeConvertDbError(err) + } + + // Save the fact this is not a watching-only address manager to the + // database. + err = putWatchingOnly(ns, false) + if err != nil { + return maybeConvertDbError(err) + } + + // Save the initial synced to state. + err = putSyncedTo(ns, &syncInfo.syncedTo) + if err != nil { + return maybeConvertDbError(err) + } + err = putStartBlock(ns, &syncInfo.startBlock) + if err != nil { + return maybeConvertDbError(err) + } + + // Use 48 hours as margin of safety for wallet birthday. + return putBirthday(ns, time.Now().Add(-48*time.Hour)) }