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 authored and GitHub committed 2021-08-24 13:01:41 +02:00
commit 23cd231483
12 files changed
+490 -198

No files matched your search

+2 -1
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,
}
+7 -6
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 {
+44 -36
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)
}