Merge pull request #4717 from guggero/xprv-wallet-init

walletunlocker: Allow wallet to be created from extended master root key (xprv)
This commit is contained in:
Oliver Gugger
2021-08-24 13:01:41 +02:00
committed by GitHub
12 changed files with 490 additions and 198 deletions

View File

@@ -214,16 +214,21 @@ func create(ctx *cli.Context) error {
}
// Next, we'll see if the user has 24-word mnemonic they want to use to
// derive a seed within the wallet.
// derive a seed within the wallet or if they want to specify an
// extended master root key (xprv) directly.
var (
hasMnemonic bool
hasXprv bool
)
mnemonicCheck:
for {
fmt.Println()
fmt.Printf("Do you have an existing cipher seed " +
"mnemonic you want to use? (Enter y/n): ")
"mnemonic or extended master root key you want to " +
"use?\nEnter 'y' to use an existing cipher seed " +
"mnemonic, 'x' to use an extended master root key " +
"\nor 'n' to create a new seed (Enter y/x/n): ")
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
@@ -240,20 +245,28 @@ mnemonicCheck:
case "y":
hasMnemonic = true
break mnemonicCheck
case "x":
hasXprv = true
break mnemonicCheck
case "n":
hasMnemonic = false
break mnemonicCheck
}
}
// If the user *does* have an existing seed they want to use, then
// we'll read that in directly from the terminal.
// If the user *does* have an existing seed or root key they want to
// use, then we'll read that in directly from the terminal.
var (
cipherSeedMnemonic []string
aezeedPass []byte
recoveryWindow int32
cipherSeedMnemonic []string
aezeedPass []byte
extendedRootKey string
extendedRootKeyBirthday uint64
recoveryWindow int32
)
if hasMnemonic {
switch {
// Use an existing cipher seed mnemonic in the aezeed format.
case hasMnemonic:
// We'll now prompt the user to enter in their 24-word
// mnemonic.
fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
@@ -288,38 +301,37 @@ mnemonicCheck:
return err
}
for {
fmt.Println()
fmt.Printf("Input an optional address look-ahead "+
"used to scan for used keys (default %d): ",
defaultRecoveryWindow)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return err
}
fmt.Println()
answer = strings.TrimSpace(answer)
if len(answer) == 0 {
recoveryWindow = defaultRecoveryWindow
break
}
lookAhead, err := strconv.Atoi(answer)
if err != nil {
fmt.Printf("Unable to parse recovery "+
"window: %v\n", err)
continue
}
recoveryWindow = int32(lookAhead)
break
recoveryWindow, err = askRecoveryWindow()
if err != nil {
return err
}
} else {
// Use an existing extended master root key to create the wallet.
case hasXprv:
// We'll now prompt the user to enter in their extended master
// root key.
fmt.Printf("Input your extended master root key (usually " +
"starting with xprv... on mainnet): ")
reader := bufio.NewReader(os.Stdin)
extendedRootKey, err = reader.ReadString('\n')
if err != nil {
return err
}
extendedRootKey = strings.TrimSpace(extendedRootKey)
extendedRootKeyBirthday, err = askBirthdayTimestamp()
if err != nil {
return err
}
recoveryWindow, err = askRecoveryWindow()
if err != nil {
return err
}
// Neither a seed nor a master root key was specified, the user wants
// to create a new seed.
default:
// Otherwise, if the user doesn't have a mnemonic that they
// want to use, we'll generate a fresh one with the GenSeed
// command.
@@ -352,36 +364,21 @@ mnemonicCheck:
// Before we initialize the wallet, we'll display the cipher seed to
// the user so they can write it down.
mnemonicWords := cipherSeedMnemonic
fmt.Println("!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
fmt.Println()
fmt.Println("---------------BEGIN LND CIPHER SEED---------------")
numCols := 4
colWords := monowidthColumns(mnemonicWords, numCols)
for i := 0; i < len(colWords); i += numCols {
fmt.Printf("%2d. %3s %2d. %3s %2d. %3s %2d. %3s\n",
i+1, colWords[i], i+2, colWords[i+1], i+3,
colWords[i+2], i+4, colWords[i+3])
if len(cipherSeedMnemonic) > 0 {
printCipherSeedWords(cipherSeedMnemonic)
}
fmt.Println("---------------END LND CIPHER SEED-----------------")
fmt.Println("\n!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
// With either the user's prior cipher seed, or a newly generated one,
// we'll go ahead and initialize the wallet.
req := &lnrpc.InitWalletRequest{
WalletPassword: walletPassword,
CipherSeedMnemonic: cipherSeedMnemonic,
AezeedPassphrase: aezeedPass,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
StatelessInit: statelessInit,
WalletPassword: walletPassword,
CipherSeedMnemonic: cipherSeedMnemonic,
AezeedPassphrase: aezeedPass,
ExtendedMasterKey: extendedRootKey,
ExtendedMasterKeyBirthdayTimestamp: extendedRootKeyBirthday,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
StatelessInit: statelessInit,
}
response, err := client.InitWallet(ctxc, req)
if err != nil {
@@ -663,3 +660,86 @@ func storeOrPrintAdminMac(ctx *cli.Context, adminMac []byte) error {
fmt.Printf("Admin macaroon: %s\n", hex.EncodeToString(adminMac))
return nil
}
func askRecoveryWindow() (int32, error) {
for {
fmt.Println()
fmt.Printf("Input an optional address look-ahead used to scan "+
"for used keys (default %d): ", defaultRecoveryWindow)
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return 0, err
}
fmt.Println()
answer = strings.TrimSpace(answer)
if len(answer) == 0 {
return defaultRecoveryWindow, nil
}
lookAhead, err := strconv.Atoi(answer)
if err != nil {
fmt.Printf("Unable to parse recovery window: %v\n", err)
continue
}
return int32(lookAhead), nil
}
}
func askBirthdayTimestamp() (uint64, error) {
for {
fmt.Println()
fmt.Printf("Input an optional wallet birthday unix timestamp " +
"of first block to start scanning from (default 0): ")
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return 0, err
}
fmt.Println()
answer = strings.TrimSpace(answer)
if len(answer) == 0 {
return 0, nil
}
birthdayTimestamp, err := strconv.ParseUint(answer, 10, 64)
if err != nil {
fmt.Printf("Unable to parse birthday timestamp: %v\n",
err)
continue
}
return birthdayTimestamp, nil
}
}
func printCipherSeedWords(mnemonicWords []string) {
fmt.Println("!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
fmt.Println()
fmt.Println("---------------BEGIN LND CIPHER SEED---------------")
numCols := 4
colWords := monowidthColumns(mnemonicWords, numCols)
for i := 0; i < len(colWords); i += numCols {
fmt.Printf("%2d. %3s %2d. %3s %2d. %3s %2d. %3s\n",
i+1, colWords[i], i+2, colWords[i+1], i+3,
colWords[i+2], i+4, colWords[i+3])
}
fmt.Println("---------------END LND CIPHER SEED-----------------")
fmt.Println("\n!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO " +
"RESTORE THE WALLET!!!")
}

View File

@@ -43,6 +43,12 @@ for more information.
* It is now possible to fund a psbt [without specifying any
outputs](https://github.com/lightningnetwork/lnd/pull/5442). This option is
useful for CPFP bumping of unconfirmed outputs or general utxo consolidation.
* The internal wallet can now also be created or restored by using an [extended
master root key (`xprv`) instead of an
`aezeed`](https://github.com/lightningnetwork/lnd/pull/4717) only. This allows
wallet integrators to use existing seed mechanism that might already be in
place. **It is still not supported to use the same seed/root key on multiple
`lnd` instances simultaneously** though.
## Security

6
go.mod
View File

@@ -5,15 +5,15 @@ require (
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2
github.com/btcsuite/btcd v0.21.0-beta.0.20210513141527-ee5896bad5be
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890
github.com/btcsuite/btcwallet v0.12.1-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet v0.12.1-0.20210822222949-9b5a201c344c
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.4.9 // indirect

13
go.sum
View File

@@ -72,9 +72,8 @@ github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcug
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs=
github.com/btcsuite/btcd v0.21.0-beta.0.20210426180113-7eba688b65e5/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btcd v0.21.0-beta.0.20210513141527-ee5896bad5be h1:vDD/JWWS2v4GJUG/RZE/50wT6Saerbujijd7mFqgsKI=
github.com/btcsuite/btcd v0.21.0-beta.0.20210513141527-ee5896bad5be/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4 h1:EmyLrldY44jDVa3dQ2iscj1S6ExuVJhRzCZBOXo93r0=
github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -85,8 +84,8 @@ github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlH
github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4=
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.12.1-0.20210803004036-eebed51155ec h1:MAAR//aKu+I7bnxmWJZqGTX7fU7abWFBRoSzX6ty8zw=
github.com/btcsuite/btcwallet v0.12.1-0.20210803004036-eebed51155ec/go.mod h1:LNhKxGlbwEGVQFjS4Qa7BgR6NipPhTd1/93Ay049pBw=
github.com/btcsuite/btcwallet v0.12.1-0.20210822222949-9b5a201c344c h1:lOUYaSw0aqCHgLk+hA2QSYXhquRXdAvT6rB3sJMXI8w=
github.com/btcsuite/btcwallet v0.12.1-0.20210822222949-9b5a201c344c/go.mod h1:SdqXKJoEEi5LJq6zU67PcKiyqF97AcUOfBfyQHC7rqQ=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.1-0.20210329233242-e0607006dce6/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.2-0.20210803004036-eebed51155ec h1:nuO8goa4gbgDM4iegCztF7mTq8io9NT1DAMoPrEI6S4=
@@ -101,8 +100,8 @@ github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPT
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI=
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk=
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210803004036-eebed51155ec h1:q2OVY/GUKpdpfaVYztVrWoTRVzyzdDQftRcgHs/6cXI=
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210803004036-eebed51155ec/go.mod h1:UM38ixX8VwJ9qey4umf//0H3ndn5kSImFZ46V54Nd5Q=
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c h1:owWPexGfK4eSK4/Zy+XK2lET5qsnW7FRAc8OCOdD0Fg=
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c/go.mod h1:UM38ixX8VwJ9qey4umf//0H3ndn5kSImFZ46V54Nd5Q=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=

38
lnd.go
View File

@@ -1492,13 +1492,17 @@ func waitForWalletPassword(cfg *Config,
case initMsg := <-pwService.InitMsgs:
password := initMsg.Passphrase
cipherSeed := initMsg.WalletSeed
extendedKey := initMsg.WalletExtendedKey
recoveryWindow := initMsg.RecoveryWindow
// Before we proceed, we'll check the internal version of the
// seed. If it's greater than the current key derivation
// version, then we'll return an error as we don't understand
// this.
if cipherSeed.InternalVersion != keychain.KeyDerivationVersion {
const latestVersion = keychain.KeyDerivationVersion
if cipherSeed != nil &&
cipherSeed.InternalVersion != latestVersion {
return nil, fmt.Errorf("invalid internal "+
"seed version %v, current version is %v",
cipherSeed.InternalVersion,
@@ -1515,10 +1519,36 @@ func waitForWalletPassword(cfg *Config,
// With the seed, we can now use the wallet loader to create
// the wallet, then pass it back to avoid unlocking it again.
birthday := cipherSeed.BirthdayTime()
newWallet, err := loader.CreateNewWallet(
password, password, cipherSeed.Entropy[:], birthday,
var (
birthday time.Time
newWallet *wallet.Wallet
)
switch {
// A normal cipher seed was given, use the birthday encoded in
// it and create the wallet from that.
case cipherSeed != nil:
birthday = cipherSeed.BirthdayTime()
newWallet, err = loader.CreateNewWallet(
password, password, cipherSeed.Entropy[:],
birthday,
)
// No seed was given, we're importing a wallet from its extended
// private key.
case extendedKey != nil:
birthday = initMsg.ExtendedKeyBirthday
newWallet, err = loader.CreateNewWalletExtendedKey(
password, password, extendedKey, birthday,
)
default:
// The unlocker service made sure either the cipher seed
// or the extended key is set so, we shouldn't get here.
// The default case is just here for readability and
// completeness.
err = fmt.Errorf("cannot create wallet, neither seed " +
"nor extended key was given")
}
if err != nil {
// Don't leave the file open in case the new wallet
// could not be created for whatever reason.

View File

@@ -189,6 +189,29 @@ type InitWalletRequest struct {
//admin macaroon returned in the response MUST be stored by the caller of the
//RPC as otherwise all access to the daemon will be lost!
StatelessInit bool `protobuf:"varint,6,opt,name=stateless_init,json=statelessInit,proto3" json:"stateless_init,omitempty"`
//
//extended_master_key is an alternative to specifying cipher_seed_mnemonic and
//aezeed_passphrase. Instead of deriving the master root key from the entropy
//of an aezeed cipher seed, the given extended master root key is used
//directly as the wallet's master key. This allows users to import/use a
//master key from another wallet. When doing so, lnd still uses its default
//SegWit only (BIP49/84) derivation paths and funds from custom/non-default
//derivation paths will not automatically appear in the on-chain wallet. Using
//an 'xprv' instead of an aezeed also has the disadvantage that the wallet's
//birthday is not known as that is an information that's only encoded in the
//aezeed, not the xprv. Therefore a birthday needs to be specified in
//extended_master_key_birthday_timestamp or a "safe" default value will be
//used.
ExtendedMasterKey string `protobuf:"bytes,7,opt,name=extended_master_key,json=extendedMasterKey,proto3" json:"extended_master_key,omitempty"`
//
//extended_master_key_birthday_timestamp is the optional unix timestamp in
//seconds to use as the wallet's birthday when using an extended master key
//to restore the wallet. lnd will only start scanning for funds in blocks that
//are after the birthday which can speed up the process significantly. If the
//birthday is not known, this should be left at its default value of 0 in
//which case lnd will start scanning from the first SegWit block (481824 on
//mainnet).
ExtendedMasterKeyBirthdayTimestamp uint64 `protobuf:"varint,8,opt,name=extended_master_key_birthday_timestamp,json=extendedMasterKeyBirthdayTimestamp,proto3" json:"extended_master_key_birthday_timestamp,omitempty"`
}
func (x *InitWalletRequest) Reset() {
@@ -265,6 +288,20 @@ func (x *InitWalletRequest) GetStatelessInit() bool {
return false
}
func (x *InitWalletRequest) GetExtendedMasterKey() string {
if x != nil {
return x.ExtendedMasterKey
}
return ""
}
func (x *InitWalletRequest) GetExtendedMasterKeyBirthdayTimestamp() uint64 {
if x != nil {
return x.ExtendedMasterKeyBirthdayTimestamp
}
return 0
}
type InitWalletResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -605,8 +642,8 @@ var file_walletunlocker_proto_rawDesc = []byte{
0x09, 0x52, 0x12, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x65, 0x65, 0x64, 0x4d, 0x6e, 0x65,
0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65,
0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e,
0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0xaf,
0x02, 0x0a, 0x11, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71,
0x65, 0x6e, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x65, 0x64, 0x53, 0x65, 0x65, 0x64, 0x22, 0xb3,
0x03, 0x0a, 0x11, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f, 0x70,
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x77,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x30, 0x0a,
@@ -625,63 +662,71 @@ var file_walletunlocker_proto_rawDesc = []byte{
0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61,
0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74,
0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f,
0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x22, 0xd2, 0x01,
0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x5f,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e,
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x27,
0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f,
0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72,
0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e,
0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63,
0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x0e, 0x63, 0x68, 0x61,
0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73,
0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x04, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e,
0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf, 0x01, 0x0a, 0x15, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f,
0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f,
0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74,
0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x6e, 0x65, 0x77,
0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x6b,
0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x65, 0x77, 0x4d, 0x61, 0x63,
0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x3f, 0x0a, 0x16,
0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f,
0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d,
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x32, 0xa5, 0x02,
0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72,
0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12, 0x15, 0x2e, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65,
0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x49, 0x6e,
0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a,
0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x1a, 0x2e,
0x12, 0x2e, 0x0a, 0x13, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x65,
0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79,
0x12, 0x52, 0x0a, 0x26, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x73,
0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79,
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
0x52, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x42, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x22, 0x3b, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f,
0x6e, 0x22, 0xd2, 0x01, 0x0a, 0x13, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x61, 0x6c,
0x6c, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x77,
0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x63,
0x6f, 0x76, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x12, 0x42, 0x0a, 0x0f, 0x63,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61,
0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52,
0x0e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12,
0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65,
0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x22, 0x16, 0x0a, 0x14, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b,
0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xbf,
0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6c,
0x65, 0x73, 0x73, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
0x73, 0x74, 0x61, 0x74, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x31, 0x0a,
0x15, 0x6e, 0x65, 0x77, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x72, 0x6f,
0x6f, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6e, 0x65,
0x77, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x4b, 0x65, 0x79,
0x22, 0x3f, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,
0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0d, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f,
0x6e, 0x32, 0xa5, 0x02, 0x0a, 0x0e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x55, 0x6e, 0x6c, 0x6f,
0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x12,
0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47,
0x65, 0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41,
0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x18, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49,
0x6e, 0x69, 0x74, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x47, 0x0a, 0x0c, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65,
0x74, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b,
0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c,
0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43,
0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74,
0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68,
0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1c, 0x2e, 0x6c,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e,
0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72,
0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -149,6 +149,33 @@ message InitWalletRequest {
RPC as otherwise all access to the daemon will be lost!
*/
bool stateless_init = 6;
/*
extended_master_key is an alternative to specifying cipher_seed_mnemonic and
aezeed_passphrase. Instead of deriving the master root key from the entropy
of an aezeed cipher seed, the given extended master root key is used
directly as the wallet's master key. This allows users to import/use a
master key from another wallet. When doing so, lnd still uses its default
SegWit only (BIP49/84) derivation paths and funds from custom/non-default
derivation paths will not automatically appear in the on-chain wallet. Using
an 'xprv' instead of an aezeed also has the disadvantage that the wallet's
birthday is not known as that is an information that's only encoded in the
aezeed, not the xprv. Therefore a birthday needs to be specified in
extended_master_key_birthday_timestamp or a "safe" default value will be
used.
*/
string extended_master_key = 7;
/*
extended_master_key_birthday_timestamp is the optional unix timestamp in
seconds to use as the wallet's birthday when using an extended master key
to restore the wallet. lnd will only start scanning for funds in blocks that
are after the birthday which can speed up the process significantly. If the
birthday is not known, this should be left at its default value of 0 in
which case lnd will start scanning from the first SegWit block (481824 on
mainnet).
*/
uint64 extended_master_key_birthday_timestamp = 8;
}
message InitWalletResponse {
/*

View File

@@ -300,6 +300,15 @@
"stateless_init": {
"type": "boolean",
"title": "stateless_init is an optional argument instructing the daemon NOT to create\nany *.macaroon files in its filesystem. If this parameter is set, then the\nadmin macaroon returned in the response MUST be stored by the caller of the\nRPC as otherwise all access to the daemon will be lost!"
},
"extended_master_key": {
"type": "string",
"description": "extended_master_key is an alternative to specifying cipher_seed_mnemonic and\naezeed_passphrase. Instead of deriving the master root key from the entropy\nof an aezeed cipher seed, the given extended master root key is used\ndirectly as the wallet's master key. This allows users to import/use a\nmaster key from another wallet. When doing so, lnd still uses its default\nSegWit only (BIP49/84) derivation paths and funds from custom/non-default\nderivation paths will not automatically appear in the on-chain wallet. Using\nan 'xprv' instead of an aezeed also has the disadvantage that the wallet's\nbirthday is not known as that is an information that's only encoded in the\naezeed, not the xprv. Therefore a birthday needs to be specified in\nextended_master_key_birthday_timestamp or a \"safe\" default value will be\nused."
},
"extended_master_key_birthday_timestamp": {
"type": "string",
"format": "uint64",
"description": "extended_master_key_birthday_timestamp is the optional unix timestamp in\nseconds to use as the wallet's birthday when using an extended master key\nto restore the wallet. lnd will only start scanning for funds in blocks that\nare after the birthday which can speed up the process significantly. If the\nbirthday is not known, this should be left at its default value of 0 in\nwhich case lnd will start scanning from the first SegWit block (481824 on\nmainnet)."
}
}
},

View File

@@ -426,7 +426,7 @@ func (n *NetworkHarness) newNodeWithSeed(name string, extraArgs []string,
// will finish initializing the LightningClient such that the HarnessNode can
// be used for regular rpc operations.
func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
password []byte, mnemonic []string, recoveryWindow int32,
password []byte, mnemonic []string, rootKey string, recoveryWindow int32,
chanBackups *lnrpc.ChanBackupSnapshot,
opts ...NodeOption) (*HarnessNode, error) {
@@ -441,6 +441,7 @@ func (n *NetworkHarness) RestoreNodeWithSeed(name string, extraArgs []string,
WalletPassword: password,
CipherSeedMnemonic: mnemonic,
AezeedPassphrase: password,
ExtendedMasterKey: rootKey,
RecoveryWindow: recoveryWindow,
ChannelBackups: chanBackups,
}

View File

@@ -122,8 +122,8 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
// obtained above.
return func() (*lntest.HarnessNode, error) {
return net.RestoreNodeWithSeed(
"dave", nil, password,
mnemonic, 1000, backupSnapshot,
"dave", nil, password, mnemonic,
"", 1000, backupSnapshot,
copyPorts(oldNode),
)
}, nil
@@ -159,8 +159,8 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
// restart it again using Unlock.
return func() (*lntest.HarnessNode, error) {
newNode, err := net.RestoreNodeWithSeed(
"dave", nil, password,
mnemonic, 1000, nil,
"dave", nil, password, mnemonic,
"", 1000, nil,
copyPorts(oldNode),
)
if err != nil {
@@ -208,7 +208,8 @@ func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
return func() (*lntest.HarnessNode, error) {
newNode, err := net.RestoreNodeWithSeed(
"dave", nil, password, mnemonic,
1000, nil, copyPorts(oldNode),
"", 1000, nil,
copyPorts(oldNode),
)
if err != nil {
return nil, fmt.Errorf("unable to "+
@@ -1309,7 +1310,7 @@ func chanRestoreViaRPC(net *lntest.NetworkHarness, password []byte,
return func() (*lntest.HarnessNode, error) {
newNode, err := net.RestoreNodeWithSeed(
"dave", nil, password, mnemonic, 1000, nil,
"dave", nil, password, mnemonic, "", 1000, nil,
copyPorts(oldNode),
)
if err != nil {

View File

@@ -5,9 +5,12 @@ import (
"math"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testGetRecoveryInfo checks whether lnd gives the right information about
@@ -34,7 +37,8 @@ func testGetRecoveryInfo(net *lntest.NetworkHarness, t *harnessTest) {
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node, err := net.RestoreNodeWithSeed(
"Carol", nil, password, mnemonic, recoveryWindow, nil,
"Carol", nil, password, mnemonic, "", recoveryWindow,
nil,
)
if err != nil {
t.Fatalf("unable to restore node: %v", err)
@@ -124,11 +128,14 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
carol, mnemonic, _, err := net.NewNodeWithSeed(
"Carol", nil, password, false,
)
if err != nil {
t.Fatalf("unable to create node with seed; %v", err)
}
require.NoError(t.t, err)
shutdownAndAssert(net, t, carol)
// As long as the mnemonic is non-nil and the extended key is empty, the
// closure below will always restore the node from the seed. The tests
// need to manually overwrite this value to change that behavior.
rootKey := ""
// Create a closure for testing the recovery of Carol's wallet. This
// method takes the expected value of Carol's balance when using the
// given recovery window. Additionally, the caller can specify an action
@@ -136,14 +143,15 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
restoreCheckBalance := func(expAmount int64, expectedNumUTXOs uint32,
recoveryWindow int32, fn func(*lntest.HarnessNode)) {
t.t.Helper()
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node, err := net.RestoreNodeWithSeed(
"Carol", nil, password, mnemonic, recoveryWindow, nil,
"Carol", nil, password, mnemonic, rootKey,
recoveryWindow, nil,
)
if err != nil {
t.Fatalf("unable to restore node: %v", err)
}
require.NoError(t.t, err)
// Query carol for her current wallet balance, and also that we
// gain the expected number of UTXOs.
@@ -155,10 +163,7 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
req := &lnrpc.WalletBalanceRequest{}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
resp, err := node.WalletBalance(ctxt, req)
if err != nil {
t.Fatalf("unable to query wallet balance: %v",
err)
}
require.NoError(t.t, err)
currBalance = resp.ConfirmedBalance
utxoReq := &lnrpc.ListUnspentRequest{
@@ -166,9 +171,7 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
utxoResp, err := node.ListUnspent(ctxt, utxoReq)
if err != nil {
t.Fatalf("unable to query utxos: %v", err)
}
require.NoError(t.t, err)
currNumUTXOs = uint32(len(utxoResp.Utxos))
// Verify that Carol's balance and number of UTXOs
@@ -206,6 +209,8 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
// behavior to both default P2WKH and NP2WKH scopes.
skipAndSend := func(nskip int) func(*lntest.HarnessNode) {
return func(node *lntest.HarnessNode) {
t.t.Helper()
newP2WKHAddrReq := &lnrpc.NewAddressRequest{
Type: AddrTypeWitnessPubkeyHash,
}
@@ -218,17 +223,11 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
for i := 0; i < nskip; i++ {
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
_, err = node.NewAddress(ctxt, newP2WKHAddrReq)
if err != nil {
t.Fatalf("unable to generate new "+
"p2wkh address: %v", err)
}
require.NoError(t.t, err)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
_, err = node.NewAddress(ctxt, newNP2WKHAddrReq)
if err != nil {
t.Fatalf("unable to generate new "+
"np2wkh address: %v", err)
}
require.NoError(t.t, err)
}
// Send one BTC to the next P2WKH address.
@@ -291,28 +290,22 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
const minerAmt = 5 * btcutil.SatoshiPerBitcoin
const finalBalance = 6 * btcutil.SatoshiPerBitcoin
promptChangeAddr := func(node *lntest.HarnessNode) {
t.t.Helper()
minerAddr, err := net.Miner.NewAddress()
if err != nil {
t.Fatalf("unable to create new miner address: %v", err)
}
require.NoError(t.t, err)
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
resp, err := node.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
Amount: minerAmt,
})
if err != nil {
t.Fatalf("unable to send coins to miner: %v", err)
}
require.NoError(t.t, err)
txid, err := waitForTxInMempool(
net.Miner.Client, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("transaction not found in mempool: %v", err)
}
if resp.Txid != txid.String() {
t.Fatalf("txid mismatch: %v vs %v", resp.Txid,
txid.String())
}
require.NoError(t.t, err)
require.Equal(t.t, txid.String(), resp.Txid)
block := mineBlocks(t, net, 1, 1)[0]
assertTxInBlock(t, block, txid)
}
@@ -323,4 +316,19 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
// only have one UTXO present (the change output) of 6 - 5 - fee BTC.
const fee = 27750
restoreCheckBalance(finalBalance-minerAmt-fee, 1, 21, nil)
// Last of all, make sure we can also restore a node from the extended
// master root key directly instead of the seed.
var seedMnemonic aezeed.Mnemonic
copy(seedMnemonic[:], mnemonic)
cipherSeed, err := seedMnemonic.ToCipherSeed(password)
require.NoError(t.t, err)
extendedRootKey, err := hdkeychain.NewMaster(
cipherSeed.Entropy[:], harnessNetParams,
)
require.NoError(t.t, err)
rootKey = extendedRootKey.String()
mnemonic = nil
restoreCheckBalance(finalBalance-minerAmt-fee, 1, 21, nil)
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup"
@@ -50,9 +51,19 @@ type WalletInitMsg struct {
Passphrase []byte
// WalletSeed is the deciphered cipher seed that the wallet should use
// to initialize itself.
// to initialize itself. The seed might be nil if the wallet should be
// created from an extended master root key instead.
WalletSeed *aezeed.CipherSeed
// WalletExtendedKey is the wallet's extended master root key that
// should be used instead of the seed, if non-nil. The extended key is
// mutually exclusive to the wallet seed, but one of both is always set.
WalletExtendedKey *hdkeychain.ExtendedKey
// ExtendedKeyBirthday is the birthday of a wallet that's being restored
// through an extended key instead of an aezeed.
ExtendedKeyBirthday time.Time
// 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
@@ -353,29 +364,104 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
return nil, fmt.Errorf("wallet already exists")
}
// At this point, we know that the wallet doesn't already exist. So
// we'll map the user provided aezeed and passphrase into a decoded
// cipher seed instance.
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], in.CipherSeedMnemonic[:])
// If we're unable to map it back into the ciphertext, then either the
// mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
if err != nil {
return nil, err
}
// With the cipher seed deciphered, and the auth service created, we'll
// now send over the wallet password and the seed. This will allow the
// daemon to initialize itself and startup.
// At this point, we know the wallet doesn't already exist so we can
// prepare the message that we'll send over the channel later.
initMsg := &WalletInitMsg{
Passphrase: password,
WalletSeed: cipherSeed,
RecoveryWindow: uint32(recoveryWindow),
StatelessInit: in.StatelessInit,
}
// There are two supported ways to initialize the wallet. Either from
// the aezeed or the final extended master key directly.
switch {
// Don't allow the user to specify both as that would be ambiguous.
case len(in.CipherSeedMnemonic) > 0 && len(in.ExtendedMasterKey) > 0:
return nil, fmt.Errorf("cannot specify both the cipher " +
"seed mnemonic and the extended master key")
// The aezeed is the preferred and default way of initializing a wallet.
case len(in.CipherSeedMnemonic) > 0:
// We'll map the user provided aezeed and passphrase into a
// decoded cipher seed instance.
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], in.CipherSeedMnemonic)
// If we're unable to map it back into the ciphertext, then
// either the mnemonic is wrong, or the passphrase is wrong.
cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
if err != nil {
return nil, err
}
initMsg.WalletSeed = cipherSeed
// To support restoring a wallet where the seed isn't known or a wallet
// created externally to lnd, we also allow the extended master key
// (xprv) to be imported directly. This is what'll be stored in the
// btcwallet database anyway.
case len(in.ExtendedMasterKey) > 0:
extendedKey, err := hdkeychain.NewKeyFromString(
in.ExtendedMasterKey,
)
if err != nil {
return nil, err
}
// The on-chain wallet of lnd is going to derive keys based on
// the BIP49/84 key derivation paths from this root key. To make
// sure we use default derivation paths, we want to avoid
// deriving keys from something other than the master key (at
// depth 0, denoted with "m/" in BIP32 notation).
if extendedKey.Depth() != 0 {
return nil, fmt.Errorf("extended master key must " +
"be at depth 0 not a child key")
}
// Because we need the master key (at depth 0), it must be an
// extended private key as the first levels of BIP49/84
// derivation paths are hardened, which isn't possible with
// extended public keys.
if !extendedKey.IsPrivate() {
return nil, fmt.Errorf("extended master key must " +
"contain private keys")
}
// To avoid using the wrong master key, we check that it was
// issued for the correct network. This will cause problems if
// someone tries to import a "new" BIP84 zprv key because with
// this we only support the "legacy" zprv prefix. But it is
// trivial to convert between those formats, as long as the user
// knows what they're doing.
if !extendedKey.IsForNet(u.netParams) {
return nil, fmt.Errorf("extended master key must be "+
"for network %s", u.netParams.Name)
}
// When importing a wallet from its extended private key 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.ExtendedKeyBirthday = time.Date(
2017, time.August, 24, 1, 57, 37, 0, time.UTC,
)
if in.ExtendedMasterKeyBirthdayTimestamp != 0 {
initMsg.ExtendedKeyBirthday = time.Unix(
int64(in.ExtendedMasterKeyBirthdayTimestamp), 0,
)
}
initMsg.WalletExtendedKey = extendedKey
// No key material was set, no wallet can be created.
default:
return nil, fmt.Errorf("must either specify cipher seed " +
"mnemonic or the extended master key")
}
// Before we return the unlock payload, we'll check if we can extract
// any channel backups to pass up to the higher level sub-system.
chansToRestore := extractChanBackups(in.ChannelBackups)