waddrmgr: use correct DerivationPath for watch-only accounts

Previously, addresses that belong to a watch-only account would have a
derivation path using the internal account number used to identify
accounts within the databse, rather than the actual account number based
on the account's master public key child index. This wasn't an issue
before as only one account would exist within the wallet, the 0 account,
which is also the default. To ensure users of the DerivationPath struct
can arrive at addresses correctly, we introduce a new field
InternalAccount to denote the internal account number and repurpose the
existing Account field to its actual meaning.
This commit is contained in:
Wilmer Paulino 2021-02-16 17:01:12 -08:00
parent dead1a89d9
commit 0492cb4507
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
8 changed files with 221 additions and 159 deletions

View file

@ -1774,7 +1774,9 @@ func validateAddress(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
// The address lookup was successful which means there is further
// information about it available and it is "mine".
result.IsMine = true
acctName, err := w.AccountName(waddrmgr.KeyScopeBIP0044, ainfo.Account())
acctName, err := w.AccountName(
waddrmgr.KeyScopeBIP0044, ainfo.InternalAccount(),
)
if err != nil {
return nil, &ErrAccountNameNotFound
}

View file

@ -54,8 +54,8 @@ const (
// type may provide further fields to provide information specific to that type
// of address.
type ManagedAddress interface {
// Account returns the account the address is associated with.
Account() uint32
// Account returns the internal account the address is associated with.
InternalAccount() uint32
// Address returns a btcutil.Address for the backing address.
Address() btcutil.Address
@ -133,7 +133,7 @@ type managedAddress struct {
used bool
addrType AddressType
pubKey *btcec.PublicKey
privKeyEncrypted []byte
privKeyEncrypted []byte // nil if part of watch-only account
privKeyCT []byte // non-nil if unlocked
privKeyMutex sync.Mutex
}
@ -177,11 +177,12 @@ func (a *managedAddress) lock() {
a.privKeyMutex.Unlock()
}
// Account returns the account number the address is associated with.
// InternalAccount returns the internal account number the address is associated
// with.
//
// This is part of the ManagedAddress interface implementation.
func (a *managedAddress) Account() uint32 {
return a.derivationPath.Account
func (a *managedAddress) InternalAccount() uint32 {
return a.derivationPath.InternalAccount
}
// AddrType returns the address type of the managed address. This can be used
@ -544,11 +545,11 @@ func (a *scriptAddress) lock() {
a.scriptMutex.Unlock()
}
// Account returns the account the address is associated with. This will always
// be the ImportedAddrAccount constant for script addresses.
// InternalAccount returns the account the address is associated with. This will
// always be the ImportedAddrAccount constant for script addresses.
//
// This is part of the ManagedAddress interface implementation.
func (a *scriptAddress) Account() uint32 {
func (a *scriptAddress) InternalAccount() uint32 {
return a.account
}

View file

@ -13,6 +13,7 @@ import (
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
)
@ -51,9 +52,10 @@ var (
privKey: hexToBytes("c27d6581b92785834b381fa697c4b0ffc4574b495743722e0acb7601b1b68b99"),
privKeyWIF: "L3jmpy54Pc7MLXTN2mL8Xas7BJziwKaUGmgnXXzgGbVRdiAniXZk",
derivationInfo: DerivationPath{
Account: 0,
Branch: 0,
Index: 0,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 0,
},
},
{
@ -66,9 +68,10 @@ var (
privKey: hexToBytes("18f3b191019e83878a81557abebb2afda199e31d22e150d8bf4df4561671be6c"),
privKeyWIF: "Kx4DNid19W8sjNFN3uPqQE7UYnCqyEp7unCvdkf2LrVUFpnDtwpB",
derivationInfo: DerivationPath{
Account: 0,
Branch: 0,
Index: 1,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 1,
},
},
{
@ -81,9 +84,10 @@ var (
privKey: hexToBytes("ccb8f6305b73136b363644b647f6efc0fd27b6b7d9c11c7e560662ed38db7b34"),
privKeyWIF: "L45fWF6Yd736fDohuB97vwRRLdQQJr3ZGvbokk9ubiT7aNrg7tTn",
derivationInfo: DerivationPath{
Account: 0,
Branch: 0,
Index: 2,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 2,
},
},
{
@ -96,9 +100,10 @@ var (
privKey: hexToBytes("d6bc8ff768814fede2adcdb74826bd846924341b3862e3b6e31cdc084e992940"),
privKeyWIF: "L4R8XyxYQyPSpTwj8w96tM86a6j3QA9jbRPj3RA7DVTVWk71ndeP",
derivationInfo: DerivationPath{
Account: 0,
Branch: 0,
Index: 3,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 3,
},
},
{
@ -111,9 +116,10 @@ var (
privKey: hexToBytes("8563ade061110e03aee50695ffc5cb1c06c8310bde0a3674257c853c966968c0"),
privKeyWIF: "L1h16Hunxomww4FrpyQP2iFmWNgG7U1u3awp6Vd3s2uGf7v5VU8c",
derivationInfo: DerivationPath{
Account: 0,
Branch: 0,
Index: 4,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 4,
},
},
{
@ -126,9 +132,10 @@ var (
privKey: hexToBytes("fe4f855fcf059ec6ddf7b25f63b19aa49c771d1fcb9850b68ae3d65e20657a60"),
privKeyWIF: "L5k4HivqXvohxBMpuwD38iUgi6uewffwZny91ZNYfM39RXH2x3QR",
derivationInfo: DerivationPath{
Account: 0,
Branch: 1,
Index: 0,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 1,
Index: 0,
},
},
{
@ -141,9 +148,10 @@ var (
privKey: hexToBytes("bfef521317c65b018ae7e6d7ecc3aa700d5d0f7ea84d567be9270382d0b5e3e6"),
privKeyWIF: "L3eomUajnTDM3Pc8GU47qqXUFuCjvpqY7NYN9mH3x1ZFjDgiY4BU",
derivationInfo: DerivationPath{
Account: 0,
Branch: 1,
Index: 1,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 1,
Index: 1,
},
},
{
@ -156,9 +164,10 @@ var (
privKey: hexToBytes("f506dffd4494c24006df7a35f3291f7ca0297a1a431557a1339bfed6f48738ca"),
privKeyWIF: "L5S1bVQUPqQb1Su82fLoSpnGCjcPfdAQE1pJxWRopJSBdYNDHESv",
derivationInfo: DerivationPath{
Account: 0,
Branch: 1,
Index: 2,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 1,
Index: 2,
},
},
{
@ -171,9 +180,10 @@ var (
privKey: hexToBytes("b3629de8ef6a275b4ffae41aa2bbbc2952eb92282ea6402435abbb010ecc1fb8"),
privKeyWIF: "L3EQsGeEnyXmKaux54cG4DQeCSQDvGuvEuy3W2ss4geum7AtWaHw",
derivationInfo: DerivationPath{
Account: 0,
Branch: 1,
Index: 3,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 1,
Index: 3,
},
},
{
@ -186,9 +196,10 @@ var (
privKey: hexToBytes("ca747a7ef815ea0dbe68655272cecbfbd65f2a109019a9ed28e0d3dcaffe05c3"),
privKeyWIF: "L41Frac75RPbTELKzw1EGC2qCkdveiVumpmsyX4daAvyyCMxit1W",
derivationInfo: DerivationPath{
Account: 0,
Branch: 1,
Index: 4,
InternalAccount: 0,
Account: hdkeychain.HardenedKeyStart,
Branch: 1,
Index: 4,
},
},
}

View file

@ -1178,9 +1178,9 @@ func (m *Manager) Unlock(ns walletdb.ReadBucket, passphrase []byte) error {
// We'll also derive any private keys that are pending due to
// them being created while the address manager was locked.
for _, info := range manager.deriveOnUnlock {
addressKey, err := manager.deriveKeyFromPath(
ns, info.managedAddr.Account(), info.branch,
info.index, true,
addressKey, _, err := manager.deriveKeyFromPath(
ns, info.managedAddr.InternalAccount(),
info.branch, info.index, true,
)
if err != nil {
m.lock()

View file

@ -71,15 +71,15 @@ func failingSecretKeyGen(passphrase *[]byte,
// blocks have been inserted and therefore some of the transaction outputs are
// spent.
type testContext struct {
t *testing.T
caseName string
db walletdb.DB
rootManager *Manager
manager *ScopedKeyManager
account uint32
create bool
unlocked bool
watchingOnly bool
t *testing.T
caseName string
db walletdb.DB
rootManager *Manager
manager *ScopedKeyManager
internalAccount uint32
create bool
unlocked bool
watchingOnly bool
}
// addrType is the type of address being tested
@ -114,7 +114,7 @@ func testNamePrefix(tc *testContext) string {
prefix = "Create "
}
return fmt.Sprintf("(%s) %s account #%d", tc.caseName, prefix, tc.account)
return fmt.Sprintf("(%s) %s account #%d", tc.caseName, prefix, tc.internalAccount)
}
// testManagedPubKeyAddress ensures the data returned by all exported functions
@ -293,9 +293,9 @@ func testManagedScriptAddress(tc *testContext, prefix string,
func testAddress(tc *testContext, prefix string, gotAddr ManagedAddress,
wantAddr *expectedAddr) bool {
if gotAddr.Account() != tc.account {
if gotAddr.InternalAccount() != tc.internalAccount {
tc.t.Errorf("ManagedAddress.Account: unexpected account - got "+
"%d, want %d", gotAddr.Account(), tc.account)
"%d, want %d", gotAddr.InternalAccount(), tc.internalAccount)
return false
}
@ -360,7 +360,9 @@ func testExternalAddresses(tc *testContext) bool {
err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
addrs, err = tc.manager.NextExternalAddresses(ns, tc.account, 5)
addrs, err = tc.manager.NextExternalAddresses(
ns, tc.internalAccount, 5,
)
return err
})
if err != nil {
@ -395,7 +397,9 @@ func testExternalAddresses(tc *testContext) bool {
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
lastAddr, err = tc.manager.LastExternalAddress(ns, tc.account)
lastAddr, err = tc.manager.LastExternalAddress(
ns, tc.internalAccount,
)
return err
})
if err != nil {
@ -512,7 +516,9 @@ func testInternalAddresses(tc *testContext) bool {
err := walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
var err error
addrs, err = tc.manager.NextInternalAddresses(ns, tc.account, 5)
addrs, err = tc.manager.NextInternalAddresses(
ns, tc.internalAccount, 5,
)
return err
})
if err != nil {
@ -547,7 +553,9 @@ func testInternalAddresses(tc *testContext) bool {
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
lastAddr, err = tc.manager.LastInternalAddress(ns, tc.account)
lastAddr, err = tc.manager.LastInternalAddress(
ns, tc.internalAccount,
)
return err
})
if err != nil {
@ -776,7 +784,7 @@ func testImportPrivateKey(tc *testContext) bool {
}
// Only import the private keys when in the create phase of testing.
tc.account = ImportedAddrAccount
tc.internalAccount = ImportedAddrAccount
prefix := testNamePrefix(tc) + " testImportPrivateKey"
if tc.create {
for i, test := range tests {
@ -949,7 +957,7 @@ func testImportScript(tc *testContext) bool {
}
// Only import the scripts when in the create phase of testing.
tc.account = ImportedAddrAccount
tc.internalAccount = ImportedAddrAccount
prefix := testNamePrefix(tc)
if tc.create {
for i, test := range tests {
@ -1320,7 +1328,7 @@ func testNewAccount(tc *testContext) bool {
tc.unlocked = true
testName := "acct-create"
expectedAccount := tc.account + 1
expectedAccount := tc.internalAccount + 1
if !tc.create {
// Create a new account in open mode
testName = "acct-open"
@ -1480,7 +1488,7 @@ func testRenameAccount(tc *testContext) bool {
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
acctName, err = tc.manager.AccountName(ns, tc.account)
acctName, err = tc.manager.AccountName(ns, tc.internalAccount)
return err
})
if err != nil {
@ -1490,7 +1498,7 @@ func testRenameAccount(tc *testContext) bool {
testName := acctName + "-renamed"
err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.RenameAccount(ns, tc.account, testName)
return tc.manager.RenameAccount(ns, tc.internalAccount, testName)
})
if err != nil {
tc.t.Errorf("RenameAccount: unexpected error: %v", err)
@ -1500,7 +1508,7 @@ func testRenameAccount(tc *testContext) bool {
err = walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
var err error
newName, err = tc.manager.AccountName(ns, tc.account)
newName, err = tc.manager.AccountName(ns, tc.internalAccount)
return err
})
if err != nil {
@ -1516,7 +1524,7 @@ func testRenameAccount(tc *testContext) bool {
// Test duplicate account name error
err = walletdb.Update(tc.db, func(tx walletdb.ReadWriteTx) error {
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
return tc.manager.RenameAccount(ns, tc.account, testName)
return tc.manager.RenameAccount(ns, tc.internalAccount, testName)
})
wantErrCode := ErrDuplicateAccount
if !checkManagerError(tc.t, testName, err, wantErrCode) {
@ -1587,7 +1595,7 @@ func testForEachAccountAddress(tc *testContext) bool {
var addrs []ManagedAddress
err := walletdb.View(tc.db, func(tx walletdb.ReadTx) error {
ns := tx.ReadBucket(waddrmgrNamespaceKey)
return tc.manager.ForEachAccountAddress(ns, tc.account,
return tc.manager.ForEachAccountAddress(ns, tc.internalAccount,
func(maddr ManagedAddress) error {
addrs = append(addrs, maddr)
return nil
@ -1632,14 +1640,14 @@ func testManagerAPI(tc *testContext, caseCreatedWatchingOnly bool) {
testChangePassphrase(tc)
// Reset default account
tc.account = 0
tc.internalAccount = 0
testNewAccount(tc)
testLookupAccount(tc)
testForEachAccount(tc)
testForEachAccountAddress(tc)
// Rename account 1 "acct-create"
tc.account = 1
tc.internalAccount = 1
testRenameAccount(tc)
} else {
// Test API for created watch-only case.
@ -1720,14 +1728,14 @@ func testConvertWatchingOnly(tc *testContext) bool {
return false
}
testManagerAPI(&testContext{
t: tc.t,
caseName: tc.caseName,
db: db,
rootManager: mgr,
manager: scopedMgr,
account: 0,
create: false,
watchingOnly: true,
t: tc.t,
caseName: tc.caseName,
db: db,
rootManager: mgr,
manager: scopedMgr,
internalAccount: 0,
create: false,
watchingOnly: true,
}, false)
mgr.Close()
@ -1751,14 +1759,14 @@ func testConvertWatchingOnly(tc *testContext) bool {
}
testManagerAPI(&testContext{
t: tc.t,
caseName: tc.caseName,
db: db,
rootManager: mgr,
manager: scopedMgr,
account: 0,
create: false,
watchingOnly: true,
t: tc.t,
caseName: tc.caseName,
db: db,
rootManager: mgr,
manager: scopedMgr,
internalAccount: 0,
create: false,
watchingOnly: true,
}, false)
return true
@ -1951,14 +1959,14 @@ func testManagerCase(t *testing.T, caseName string,
// Run all of the manager API tests in create mode and close the
// manager after they've completed
testManagerAPI(&testContext{
t: t,
caseName: caseName,
db: db,
manager: scopedMgr,
rootManager: mgr,
account: 0,
create: true,
watchingOnly: caseCreatedWatchingOnly,
t: t,
caseName: caseName,
db: db,
manager: scopedMgr,
rootManager: mgr,
internalAccount: 0,
create: true,
watchingOnly: caseCreatedWatchingOnly,
}, caseCreatedWatchingOnly)
mgr.Close()
@ -1981,14 +1989,14 @@ func testManagerCase(t *testing.T, caseName string,
t.Fatalf("(%s) unable to fetch default scope: %v", caseName, err)
}
tc := &testContext{
t: t,
caseName: caseName,
db: db,
manager: scopedMgr,
rootManager: mgr,
account: 0,
create: false,
watchingOnly: caseCreatedWatchingOnly,
t: t,
caseName: caseName,
db: db,
manager: scopedMgr,
rootManager: mgr,
internalAccount: 0,
create: false,
watchingOnly: caseCreatedWatchingOnly,
}
testManagerAPI(tc, caseCreatedWatchingOnly)
@ -2743,9 +2751,10 @@ func testNewRawAccount(t *testing.T, mgr *Manager, db walletdb.DB,
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
keyPath := DerivationPath{
Account: accountNum,
Branch: 0,
Index: 0,
InternalAccount: accountNum,
Account: hdkeychain.HardenedKeyStart,
Branch: 0,
Index: 0,
}
accountTargetAddr, err = scopedMgr.DeriveFromKeyPath(
ns, keyPath,

View file

@ -23,6 +23,10 @@ import (
// m/purpose'/cointype'/account/branch/index, where purpose' and cointype' are
// bound by the scope of a particular manager.
type DerivationPath struct {
// InternalAccount is the internal account number used within the
// wallet's database to identify accounts.
InternalAccount uint32
// Account is the account, or the first immediate child from the scoped
// manager's hardened coin type key.
Account uint32
@ -213,21 +217,15 @@ func (s *ScopedKeyManager) Close() {
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
account, branch, index uint32) (ManagedAddress, error) {
derivationPath DerivationPath) (ManagedAddress, error) {
var addrType AddressType
if branch == InternalBranch {
if derivationPath.Branch == InternalBranch {
addrType = s.addrSchema.InternalAddrType
} else {
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.
@ -245,13 +243,13 @@ func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
// unlocked.
info := unlockDeriveInfo{
managedAddr: ma,
branch: branch,
index: index,
branch: derivationPath.Branch,
index: derivationPath.Index,
}
s.deriveOnUnlock = append(s.deriveOnUnlock, &info)
}
if branch == InternalBranch {
if derivationPath.Branch == InternalBranch {
ma.internal = true
}
@ -343,7 +341,9 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
nextInternalIndex: row.nextInternalIndex,
}
if !s.rootManager.isLocked() {
watchOnly := s.rootManager.watchOnly() || len(acctInfo.acctKeyEncrypted) == 0
private := !s.rootManager.isLocked() && !watchOnly
if private {
// Use the crypto private key to decrypt the account private
// extended keys.
decrypted, err := s.rootManager.cryptoKeyPriv.Decrypt(acctInfo.acctKeyEncrypted)
@ -367,13 +367,17 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
if index > 0 {
index--
}
lastExtKey, err := s.deriveKey(
acctInfo, branch, index, !s.rootManager.isLocked(),
)
lastExtAddrPath := DerivationPath{
InternalAccount: account,
Account: acctKeyPub.ChildIndex(),
Branch: branch,
Index: index,
}
lastExtKey, err := s.deriveKey(acctInfo, branch, index, private)
if err != nil {
return nil, err
}
lastExtAddr, err := s.keyToManaged(lastExtKey, account, branch, index)
lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath)
if err != nil {
return nil, err
}
@ -384,13 +388,17 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
if index > 0 {
index--
}
lastIntKey, err := s.deriveKey(
acctInfo, branch, index, !s.rootManager.isLocked(),
)
lastIntAddrPath := DerivationPath{
InternalAccount: account,
Account: acctKeyPub.ChildIndex(),
Branch: branch,
Index: index,
}
lastIntKey, err := s.deriveKey(acctInfo, branch, index, private)
if err != nil {
return nil, err
}
lastIntAddr, err := s.keyToManaged(lastIntKey, account, branch, index)
lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath)
if err != nil {
return nil, err
}
@ -452,36 +460,55 @@ func (s *ScopedKeyManager) AccountProperties(ns walletdb.ReadBucket,
// DeriveFromKeyPath attempts to derive a maximal child key (under the BIP0044
// scheme) from a given key path. If key derivation isn't possible, then an
// error will be returned.
//
// NOTE: The key will be derived from the account stored in the database under
// the InternalAccount number.
func (s *ScopedKeyManager) DeriveFromKeyPath(ns walletdb.ReadBucket,
kp DerivationPath) (ManagedAddress, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
extKey, err := s.deriveKeyFromPath(
ns, kp.Account, kp.Branch, kp.Index, !s.rootManager.IsLocked(),
watchOnly := s.rootManager.WatchOnly()
private := !s.rootManager.IsLocked() && !watchOnly
addrKey, _, err := s.deriveKeyFromPath(
ns, kp.InternalAccount, kp.Branch, kp.Index, private,
)
if err != nil {
return nil, err
}
return s.keyToManaged(extKey, kp.Account, kp.Branch, kp.Index)
return s.keyToManaged(addrKey, kp)
}
// deriveKeyFromPath returns either a public or private derived extended key
// based on the private flag for the given an account, branch, and index.
// based on the private flag for an address given an account, branch, and index.
// The account master key is also returned.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) deriveKeyFromPath(ns walletdb.ReadBucket, account, branch,
index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
func (s *ScopedKeyManager) deriveKeyFromPath(ns walletdb.ReadBucket,
internalAccount, branch, index uint32, private bool) (
*hdkeychain.ExtendedKey, *hdkeychain.ExtendedKey, error) {
// Look up the account key information.
acctInfo, err := s.loadAccountInfo(ns, account)
acctInfo, err := s.loadAccountInfo(ns, internalAccount)
if err != nil {
return nil, err
return nil, nil, err
}
private = private && acctInfo.acctKeyPriv != nil
addrKey, err := s.deriveKey(acctInfo, branch, index, private)
if err != nil {
return nil, nil, err
}
return s.deriveKey(acctInfo, branch, index, private)
acctKey := acctInfo.acctKeyPub
if private {
acctKey = acctInfo.acctKeyPriv
}
return addrKey, acctKey, nil
}
// chainAddressRowToManaged returns a new managed address based on chained
@ -493,16 +520,22 @@ func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket,
// Since the manger's mutex is assumed to held when invoking this
// function, we use the internal isLocked to avoid a deadlock.
isLocked := s.rootManager.isLocked()
private := !s.rootManager.isLocked() && !s.rootManager.watchOnly()
addressKey, err := s.deriveKeyFromPath(
ns, row.account, row.branch, row.index, !isLocked,
addressKey, acctKey, err := s.deriveKeyFromPath(
ns, row.account, row.branch, row.index, private,
)
if err != nil {
return nil, err
}
return s.keyToManaged(addressKey, row.account, row.branch, row.index)
return s.keyToManaged(
addressKey, DerivationPath{
InternalAccount: row.account,
Account: acctKey.ChildIndex(),
Branch: row.branch,
Index: row.index,
},
)
}
// importedAddressRowToManaged returns a new managed address based on imported
@ -525,7 +558,7 @@ func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow
// 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,
InternalAccount: row.account,
}
compressed := len(pubBytes) == btcec.PubKeyBytesLenCompressed
@ -688,7 +721,8 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
// Choose the account key to used based on whether the address manager
// is locked.
acctKey := acctInfo.acctKeyPub
if !s.rootManager.IsLocked() {
watchOnly := s.rootManager.WatchOnly() || len(acctInfo.acctKeyEncrypted) == 0
if !s.rootManager.IsLocked() && !watchOnly {
acctKey = acctInfo.acctKeyPriv
}
@ -757,9 +791,10 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
// proper derivation path so this information can be available
// to callers.
derivationPath := DerivationPath{
Account: account,
Branch: branchNum,
Index: nextIndex - 1,
InternalAccount: account,
Account: acctKey.ChildIndex(),
Branch: branchNum,
Index: nextIndex - 1,
}
// Create a new managed address based on the public or private
@ -843,7 +878,7 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
// Add the new managed address to the list of addresses
// that need their private keys derived when the
// address manager is next unlocked.
if s.rootManager.isLocked() && !s.rootManager.watchOnly() {
if s.rootManager.isLocked() && !watchOnly {
s.deriveOnUnlock = append(s.deriveOnUnlock, info)
}
}
@ -883,7 +918,8 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
// Choose the account key to used based on whether the address manager
// is locked.
acctKey := acctInfo.acctKeyPub
if !s.rootManager.IsLocked() {
watchOnly := s.rootManager.WatchOnly() || acctInfo.acctKeyPriv != nil
if !s.rootManager.IsLocked() && !watchOnly {
acctKey = acctInfo.acctKeyPriv
}
@ -959,9 +995,10 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
// proper derivation path so this information can be available
// to callers.
derivationPath := DerivationPath{
Account: account,
Branch: branchNum,
Index: nextIndex - 1,
InternalAccount: account,
Account: acctInfo.acctKeyPub.ChildIndex(),
Branch: branchNum,
Index: nextIndex - 1,
}
// Create a new managed address based on the public or private
@ -1031,7 +1068,7 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
// Add the new managed address to the list of addresses that
// need their private keys derived when the address manager is
// next unlocked.
if s.rootManager.IsLocked() && !s.rootManager.WatchOnly() {
if s.rootManager.IsLocked() && !watchOnly {
s.deriveOnUnlock = append(s.deriveOnUnlock, info)
}
}
@ -1539,7 +1576,7 @@ func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket,
// The full derivation path for an imported key is incomplete as we
// don't know exactly how it was derived.
importedDerivationPath := DerivationPath{
Account: ImportedAddrAccount,
InternalAccount: ImportedAddrAccount,
}
// Create a new managed address based on the imported address.
@ -1575,7 +1612,7 @@ func (s *ScopedKeyManager) ImportPublicKey(ns walletdb.ReadWriteBucket,
// The full derivation path for an imported key is incomplete as we
// don't know exactly how it was derived.
importedDerivationPath := DerivationPath{
Account: ImportedAddrAccount,
InternalAccount: ImportedAddrAccount,
}
return s.toPublicManagedAddress(
pubKey, true, true, importedDerivationPath,
@ -1724,7 +1761,7 @@ func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
defer s.mtx.Unlock()
// The manager must be unlocked to encrypt the imported script.
if s.rootManager.IsLocked() && !s.rootManager.WatchOnly() {
if s.rootManager.IsLocked() {
return nil, managerError(ErrLocked, errLocked, nil)
}

View file

@ -90,7 +90,7 @@ func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetail
if err != nil {
log.Errorf("Cannot fetch account for wallet output: %v", err)
} else {
account = ma.Account()
account = ma.InternalAccount()
internal = ma.Internal()
}
return

View file

@ -946,18 +946,20 @@ func expandScopeHorizons(ns walletdb.ReadWriteBucket,
// externalKeyPath returns the relative external derivation path /0/0/index.
func externalKeyPath(index uint32) waddrmgr.DerivationPath {
return waddrmgr.DerivationPath{
Account: waddrmgr.DefaultAccountNum,
Branch: waddrmgr.ExternalBranch,
Index: index,
InternalAccount: waddrmgr.DefaultAccountNum,
Account: waddrmgr.DefaultAccountNum,
Branch: waddrmgr.ExternalBranch,
Index: index,
}
}
// internalKeyPath returns the relative internal derivation path /0/1/index.
func internalKeyPath(index uint32) waddrmgr.DerivationPath {
return waddrmgr.DerivationPath{
Account: waddrmgr.DefaultAccountNum,
Branch: waddrmgr.InternalBranch,
Index: index,
InternalAccount: waddrmgr.DefaultAccountNum,
Account: waddrmgr.DefaultAccountNum,
Branch: waddrmgr.InternalBranch,
Index: index,
}
}