Merge pull request #525 from wpaulino/addr-derivation-info
waddrmgr: add new DerivationInfo method to ManagedPubKeyAddress
This commit is contained in:
commit
5fb94231d0
5 changed files with 175 additions and 30 deletions
|
@ -103,6 +103,12 @@ type ManagedPubKeyAddress interface {
|
|||
// ExportPrivKey returns the private key associated with the address
|
||||
// serialized as Wallet Import Format (WIF).
|
||||
ExportPrivKey() (*btcutil.WIF, error)
|
||||
|
||||
// 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.
|
||||
DerivationInfo() (KeyScope, DerivationPath, bool)
|
||||
}
|
||||
|
||||
// ManagedScriptAddress extends ManagedAddress and represents a pay-to-script-hash
|
||||
|
@ -119,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
|
||||
|
@ -175,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
|
||||
|
@ -310,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
|
||||
|
@ -381,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,
|
||||
|
@ -395,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.
|
||||
//
|
||||
|
@ -413,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
|
||||
|
@ -428,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.
|
||||
|
@ -440,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
|
||||
|
@ -454,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
|
||||
|
|
|
@ -55,6 +55,11 @@ var (
|
|||
pubKey: hexToBytes("02d8f88468c5a2e8e1815faf555f59cbd1979e3dbdf823f80c271b6fb70d2d519b"),
|
||||
privKey: hexToBytes("c27d6581b92785834b381fa697c4b0ffc4574b495743722e0acb7601b1b68b99"),
|
||||
privKeyWIF: "L3jmpy54Pc7MLXTN2mL8Xas7BJziwKaUGmgnXXzgGbVRdiAniXZk",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 0,
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1N3D8jy2aQuUsKBsDgZ6ZPTVR9VhHgJYpE",
|
||||
|
@ -65,6 +70,11 @@ var (
|
|||
pubKey: hexToBytes("02b9c175b908624f8a8eaac227d0e8c77c0eec327b8c512ad1b8b7a4b5b676971f"),
|
||||
privKey: hexToBytes("18f3b191019e83878a81557abebb2afda199e31d22e150d8bf4df4561671be6c"),
|
||||
privKeyWIF: "Kx4DNid19W8sjNFN3uPqQE7UYnCqyEp7unCvdkf2LrVUFpnDtwpB",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 0,
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1VTfwD4iHre2bMrR9qGiJMwoiZGQZ8e6s",
|
||||
|
@ -75,6 +85,11 @@ var (
|
|||
pubKey: hexToBytes("0329faddf1254d490d6add49e2b08cf52b561038c72baec0edb3cfacff71ff1021"),
|
||||
privKey: hexToBytes("ccb8f6305b73136b363644b647f6efc0fd27b6b7d9c11c7e560662ed38db7b34"),
|
||||
privKeyWIF: "L45fWF6Yd736fDohuB97vwRRLdQQJr3ZGvbokk9ubiT7aNrg7tTn",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 0,
|
||||
Index: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "13TdEj4ehUuYFiSaB47eLVBwM2XhAhrK2J",
|
||||
|
@ -85,6 +100,11 @@ var (
|
|||
pubKey: hexToBytes("03d738324e2f0ce42e46975d7f8c7117c1670e3d7912b0291aea452add99674774"),
|
||||
privKey: hexToBytes("d6bc8ff768814fede2adcdb74826bd846924341b3862e3b6e31cdc084e992940"),
|
||||
privKeyWIF: "L4R8XyxYQyPSpTwj8w96tM86a6j3QA9jbRPj3RA7DVTVWk71ndeP",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 0,
|
||||
Index: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1LTjSghkBecT59VjEKke331HxVdqcFwUDa",
|
||||
|
@ -95,6 +115,11 @@ var (
|
|||
pubKey: hexToBytes("03a917acd5cd5b6f544b43f1921a35677e4d5320e5d2add2056039b4b44fdf905e"),
|
||||
privKey: hexToBytes("8563ade061110e03aee50695ffc5cb1c06c8310bde0a3674257c853c966968c0"),
|
||||
privKeyWIF: "L1h16Hunxomww4FrpyQP2iFmWNgG7U1u3awp6Vd3s2uGf7v5VU8c",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 0,
|
||||
Index: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "15HNivzKhsLaMs1qRdQN1ifoJYUnJ2xW9z",
|
||||
|
@ -105,6 +130,11 @@ var (
|
|||
pubKey: hexToBytes("020a1290b997c0a234a95213962e7edcb761c7360f0230f698a1a3e71c37047bb0"),
|
||||
privKey: hexToBytes("fe4f855fcf059ec6ddf7b25f63b19aa49c771d1fcb9850b68ae3d65e20657a60"),
|
||||
privKeyWIF: "L5k4HivqXvohxBMpuwD38iUgi6uewffwZny91ZNYfM39RXH2x3QR",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 1,
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1LJpGrAP1vWHuvfHqmUutQqFVYca2qwxhy",
|
||||
|
@ -115,6 +145,11 @@ var (
|
|||
pubKey: hexToBytes("03f79bbde32af42dde98195f011d95982602fcd0dab657fe4a1f49f9d5ada1e02d"),
|
||||
privKey: hexToBytes("bfef521317c65b018ae7e6d7ecc3aa700d5d0f7ea84d567be9270382d0b5e3e6"),
|
||||
privKeyWIF: "L3eomUajnTDM3Pc8GU47qqXUFuCjvpqY7NYN9mH3x1ZFjDgiY4BU",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 1,
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "13NhXy2nCLMwNug1TZ6uwaWnxp3uTqdDQq",
|
||||
|
@ -125,6 +160,11 @@ var (
|
|||
pubKey: hexToBytes("023ded84afe4fe91b52b45c3deb26fd263f749cbc27747dc964dae9e0739cbc579"),
|
||||
privKey: hexToBytes("f506dffd4494c24006df7a35f3291f7ca0297a1a431557a1339bfed6f48738ca"),
|
||||
privKeyWIF: "L5S1bVQUPqQb1Su82fLoSpnGCjcPfdAQE1pJxWRopJSBdYNDHESv",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 1,
|
||||
Index: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1AY6yAHvojvpFcevAichLMnJfxgE8eSe4N",
|
||||
|
@ -135,6 +175,11 @@ var (
|
|||
pubKey: hexToBytes("030f1e79f06824e10a259914ec310528bb2d5b8d6356341fe9dff55498591af6af"),
|
||||
privKey: hexToBytes("b3629de8ef6a275b4ffae41aa2bbbc2952eb92282ea6402435abbb010ecc1fb8"),
|
||||
privKeyWIF: "L3EQsGeEnyXmKaux54cG4DQeCSQDvGuvEuy3W2ss4geum7AtWaHw",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 1,
|
||||
Index: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
address: "1Jc7An3JqjzRQULVr6Wh3iYR7miB6WPJCD",
|
||||
|
@ -145,6 +190,11 @@ var (
|
|||
pubKey: hexToBytes("0317d7182e26b6ca3e0f3db531c474b9cab7a763a75eabff2e14ac92f62a793238"),
|
||||
privKey: hexToBytes("ca747a7ef815ea0dbe68655272cecbfbd65f2a109019a9ed28e0d3dcaffe05c3"),
|
||||
privKeyWIF: "L41Frac75RPbTELKzw1EGC2qCkdveiVumpmsyX4daAvyyCMxit1W",
|
||||
derivationInfo: waddrmgr.DerivationPath{
|
||||
Account: 0,
|
||||
Branch: 1,
|
||||
Index: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// newHash converts the passed big-endian hex string into a chainhash.Hash.
|
||||
|
@ -62,16 +63,17 @@ const (
|
|||
// expectedAddr is used to house the expected return values from a managed
|
||||
// address. Not all fields for used for all managed address types.
|
||||
type expectedAddr struct {
|
||||
address string
|
||||
addressHash []byte
|
||||
internal bool
|
||||
compressed bool
|
||||
used bool
|
||||
imported bool
|
||||
pubKey []byte
|
||||
privKey []byte
|
||||
privKeyWIF string
|
||||
script []byte
|
||||
address string
|
||||
addressHash []byte
|
||||
internal bool
|
||||
compressed bool
|
||||
used bool
|
||||
imported bool
|
||||
pubKey []byte
|
||||
privKey []byte
|
||||
privKeyWIF string
|
||||
script []byte
|
||||
derivationInfo waddrmgr.DerivationPath
|
||||
}
|
||||
|
||||
// testNamePrefix is a helper to return a prefix to show for test errors based
|
||||
|
@ -118,6 +120,22 @@ func testManagedPubKeyAddress(tc *testContext, prefix string,
|
|||
return false
|
||||
}
|
||||
|
||||
// Ensure that the derivation path has been properly re-set after the
|
||||
// address was read from disk.
|
||||
_, gotAddrPath, ok := gotAddr.DerivationInfo()
|
||||
if !ok && !gotAddr.Imported() {
|
||||
tc.t.Errorf("%s PubKey: non-imported address has empty "+
|
||||
"derivation info", prefix)
|
||||
return false
|
||||
}
|
||||
expectedDerivationInfo := wantAddr.derivationInfo
|
||||
if gotAddrPath != expectedDerivationInfo {
|
||||
tc.t.Errorf("%s PubKey: wrong derivation info: expected %v, "+
|
||||
"got %v", prefix, spew.Sdump(gotAddrPath),
|
||||
spew.Sdump(expectedDerivationInfo))
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure private key is the expected value for the managed address.
|
||||
// Since this is only available when the manager is unlocked, also check
|
||||
// for the expected error when the manager is locked.
|
||||
|
@ -181,6 +199,12 @@ func testManagedPubKeyAddress(tc *testContext, prefix string,
|
|||
}
|
||||
}
|
||||
|
||||
// Imported addresses should return a nil derivation info.
|
||||
if _, _, ok := gotAddr.DerivationInfo(); gotAddr.Imported() && ok {
|
||||
tc.t.Errorf("%s Imported: expected nil derivation info", prefix)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -227,12 +227,15 @@ func (w *Wallet) disconnectBlock(dbtx walletdb.ReadWriteTx, b wtxmgr.BlockMeta)
|
|||
}
|
||||
|
||||
bs.Timestamp = header.Timestamp
|
||||
|
||||
err = w.Manager.SetSyncedTo(addrmgrNs, &bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.TxStore.Rollback(txmgrNs, b.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue