multi: allow watch-only wallet creation

This commit is contained in:
Oliver Gugger
2021-10-14 15:42:55 +02:00
parent 6d339f31c0
commit ceb31f9034
5 changed files with 581 additions and 94 deletions

View File

@@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup"
@@ -103,6 +104,18 @@ type WalletInitMsg struct {
// through an extended key instead of an aezeed.
ExtendedKeyBirthday time.Time
// WatchOnlyAccounts is a map of scoped account extended public keys
// that should be imported to create a watch-only wallet.
WatchOnlyAccounts map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey
// WatchOnlyBirthday is the birthday of the master root key the above
// watch-only account xpubs were derived from.
WatchOnlyBirthday time.Time
// WatchOnlyMasterFingerprint is the fingerprint of the master root key
// the above watch-only account xpubs were derived from.
WatchOnlyMasterFingerprint uint32
// RecoveryWindow is the address look-ahead used when restoring a seed
// with existing funds. A recovery window zero indicates that no
// recovery should be attempted, such as after the wallet's initial
@@ -495,6 +508,66 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
initMsg.WalletExtendedKey = extendedKey
// The third option for creating a wallet is the watch-only mode:
// Instead of providing the master root key directly, each individual
// account is passed as an extended public key only. Because of the
// hardened derivation path up to the account (depth 3), it is not
// possible to create a master root extended _public_ key. Therefore, an
// xpub must be derived and passed into the unlocker for _every_ account
// lnd expects.
case in.WatchOnly != nil && len(in.WatchOnly.Accounts) > 0:
initMsg.WatchOnlyAccounts = make(
map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey,
len(in.WatchOnly.Accounts),
)
for _, acct := range in.WatchOnly.Accounts {
scopedIndex := waddrmgr.ScopedIndex{
Scope: waddrmgr.KeyScope{
Purpose: acct.Purpose,
Coin: acct.CoinType,
},
Index: acct.Account,
}
acctKey, err := hdkeychain.NewKeyFromString(acct.Xpub)
if err != nil {
return nil, fmt.Errorf("error parsing xpub "+
"%v: %v", acct.Xpub, err)
}
// Just to make sure the user is doing the right thing,
// we expect the public key to be at derivation depth
// three (which is the account level) and the key not to
// contain any private key material.
if acctKey.Depth() != 3 {
return nil, fmt.Errorf("xpub must be at " +
"depth 3")
}
if acctKey.IsPrivate() {
return nil, fmt.Errorf("xpub is not really " +
"an xpub, contains private key")
}
initMsg.WatchOnlyAccounts[scopedIndex] = acctKey
}
// When importing a wallet from its extended public keys we
// don't know the birthday as that information is not encoded in
// that format. We therefore must set an arbitrary date to start
// rescanning at if the user doesn't provide an explicit value
// for it. Since lnd only uses SegWit addresses, we pick the
// date of the first block that contained SegWit transactions
// (481824).
initMsg.WatchOnlyBirthday = time.Date(
2017, time.August, 24, 1, 57, 37, 0, time.UTC,
)
if in.WatchOnly.MasterKeyBirthdayTimestamp != 0 {
initMsg.WatchOnlyBirthday = time.Unix(
int64(in.WatchOnly.MasterKeyBirthdayTimestamp),
0,
)
}
// No key material was set, no wallet can be created.
default:
return nil, fmt.Errorf("must either specify cipher seed " +