diff --git a/waddrmgr/address.go b/waddrmgr/address.go index d5ddc41..b8008ae 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -108,7 +108,7 @@ type ManagedPubKeyAddress interface { // that backs the address via traditional methods from the HD root. For // imported keys, the first value will be set to false to indicate that // we don't know exactly how the key was derived. - DerivationInfo() (bool, KeyScope, DerivationPath) + DerivationInfo() (KeyScope, DerivationPath, bool) } // ManagedScriptAddress extends ManagedAddress and represents a pay-to-script-hash @@ -125,7 +125,7 @@ type ManagedScriptAddress interface { // the private key associated with the public key. type managedAddress struct { manager *ScopedKeyManager - account uint32 + derivationPath DerivationPath address btcutil.Address imported bool internal bool @@ -181,7 +181,7 @@ func (a *managedAddress) lock() { // // This is part of the ManagedAddress interface implementation. func (a *managedAddress) Account() uint32 { - return a.account + return a.derivationPath.Account } // AddrType returns the address type of the managed address. This can be used @@ -316,11 +316,33 @@ func (a *managedAddress) ExportPrivKey() (*btcutil.WIF, error) { return btcutil.NewWIF(pk, a.manager.rootManager.chainParams, a.compressed) } +// Derivationinfo contains the information required to derive the key that +// backs the address via traditional methods from the HD root. For imported +// keys, the first value will be set to false to indicate that we don't know +// exactly how the key was derived. +// +// This is part of the ManagedPubKeyAddress interface implementation. +func (a *managedAddress) DerivationInfo() (KeyScope, DerivationPath, bool) { + var ( + scope KeyScope + path DerivationPath + ) + + // If this key is imported, then we can't return any information as we + // don't know precisely how the key was derived. + if a.imported { + return scope, path, false + } + + return a.manager.Scope(), a.derivationPath, true +} + // newManagedAddressWithoutPrivKey returns a new managed address based on the // passed account, public key, and whether or not the public key should be // compressed. -func newManagedAddressWithoutPrivKey(m *ScopedKeyManager, account uint32, pubKey *btcec.PublicKey, - compressed bool, addrType AddressType) (*managedAddress, error) { +func newManagedAddressWithoutPrivKey(m *ScopedKeyManager, + derivationPath DerivationPath, pubKey *btcec.PublicKey, compressed bool, + addrType AddressType) (*managedAddress, error) { // Create a pay-to-pubkey-hash address from the public key. var pubKeyHash []byte @@ -387,7 +409,7 @@ func newManagedAddressWithoutPrivKey(m *ScopedKeyManager, account uint32, pubKey return &managedAddress{ manager: m, address: address, - account: account, + derivationPath: derivationPath, imported: false, internal: false, addrType: addrType, @@ -401,8 +423,9 @@ func newManagedAddressWithoutPrivKey(m *ScopedKeyManager, account uint32, pubKey // newManagedAddress returns a new managed address based on the passed account, // private key, and whether or not the public key is compressed. The managed // address will have access to the private and public keys. -func newManagedAddress(s *ScopedKeyManager, account uint32, privKey *btcec.PrivateKey, - compressed bool, addrType AddressType) (*managedAddress, error) { +func newManagedAddress(s *ScopedKeyManager, derivationPath DerivationPath, + privKey *btcec.PrivateKey, compressed bool, + addrType AddressType) (*managedAddress, error) { // Encrypt the private key. // @@ -419,7 +442,7 @@ func newManagedAddress(s *ScopedKeyManager, account uint32, privKey *btcec.Priva // and then add the private key to it. ecPubKey := (*btcec.PublicKey)(&privKey.PublicKey) managedAddr, err := newManagedAddressWithoutPrivKey( - s, account, ecPubKey, compressed, addrType, + s, derivationPath, ecPubKey, compressed, addrType, ) if err != nil { return nil, err @@ -434,8 +457,9 @@ func newManagedAddress(s *ScopedKeyManager, account uint32, privKey *btcec.Priva // account and extended key. The managed address will have access to the // private and public keys if the provided extended key is private, otherwise it // will only have access to the public key. -func newManagedAddressFromExtKey(s *ScopedKeyManager, account uint32, - key *hdkeychain.ExtendedKey, addrType AddressType) (*managedAddress, error) { +func newManagedAddressFromExtKey(s *ScopedKeyManager, + derivationPath DerivationPath, key *hdkeychain.ExtendedKey, + addrType AddressType) (*managedAddress, error) { // Create a new managed address based on the public or private key // depending on whether the generated key is private. @@ -446,9 +470,10 @@ func newManagedAddressFromExtKey(s *ScopedKeyManager, account uint32, return nil, err } - // Ensure the temp private key big integer is cleared after use. + // Ensure the temp private key big integer is cleared after + // use. managedAddr, err = newManagedAddress( - s, account, privKey, true, addrType, + s, derivationPath, privKey, true, addrType, ) if err != nil { return nil, err @@ -460,7 +485,8 @@ func newManagedAddressFromExtKey(s *ScopedKeyManager, account uint32, } managedAddr, err = newManagedAddressWithoutPrivKey( - s, account, pubKey, true, addrType, + s, derivationPath, pubKey, true, + addrType, ) if err != nil { return nil, err diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index ab5c8e0..dfa7a8c 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -221,11 +221,17 @@ func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, addrType = s.addrSchema.ExternalAddrType } + derivationPath := DerivationPath{ + Account: account, + Branch: branch, + Index: index, + } + // 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( - s, account, derivedKey, addrType, + s, derivationPath, derivedKey, addrType, ) defer derivedKey.Zero() if err != nil { @@ -515,9 +521,15 @@ func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow return nil, managerError(ErrCrypto, str, err) } + // Since this is an imported address, we won't populate the full + // derivation path, as we don't have enough information to do so. + derivationPath := DerivationPath{ + Account: row.account, + } + compressed := len(pubBytes) == btcec.PubKeyBytesLenCompressed ma, err := newManagedAddressWithoutPrivKey( - s, row.account, pubKey, compressed, + s, derivationPath, pubKey, compressed, s.addrSchema.ExternalAddrType, ) if err != nil { @@ -740,12 +752,21 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket, break } + // Now that we know this key can be used, we'll create the + // proper derivation path so this information can be available + // to callers. + derivationPath := DerivationPath{ + Account: account, + Branch: branchNum, + Index: nextIndex - 1, + } + // 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( - s, account, nextKey, addrType, + s, derivationPath, nextKey, addrType, ) if err != nil { return nil, err @@ -920,12 +941,21 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket, break } + // Now that we know this key can be used, we'll create the + // proper derivation path so this information can be available + // to callers. + derivationPath := DerivationPath{ + Account: account, + Branch: branchNum, + Index: nextIndex - 1, + } + // 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( - s, account, nextKey, addrType, + s, derivationPath, nextKey, addrType, ) if err != nil { return err @@ -1452,17 +1482,23 @@ func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket, s.rootManager.mtx.Unlock() } + // The full derivation path for an imported key is incomplete as we + // don't know exactly how it was derived. + importedDerivationPath := DerivationPath{ + Account: ImportedAddrAccount, + } + // Create a new managed address based on the imported address. var managedAddr *managedAddress if !s.rootManager.WatchOnly() { managedAddr, err = newManagedAddress( - s, ImportedAddrAccount, wif.PrivKey, + s, importedDerivationPath, wif.PrivKey, wif.CompressPubKey, s.addrSchema.ExternalAddrType, ) } else { pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey) managedAddr, err = newManagedAddressWithoutPrivKey( - s, ImportedAddrAccount, pubKey, wif.CompressPubKey, + s, importedDerivationPath, pubKey, wif.CompressPubKey, s.addrSchema.ExternalAddrType, ) }