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)) }