lnd/itest/lnd_remote_signer_test.go

332 lines
9.1 KiB
Go

package itest
import (
"fmt"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/stretchr/testify/require"
)
var (
rootKey = "tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3" +
"PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg"
nodePubKey = "033f55d436d4f7d24aeffb1b976647380f22ebf9e74390e8c76dcff" +
"9fea0093b7a"
accounts = []*lnrpc.WatchOnlyAccount{{
Purpose: waddrmgr.KeyScopeBIP0049Plus.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khk" +
"QoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCk" +
"FPhTq",
}, {
Purpose: waddrmgr.KeyScopeBIP0084.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" +
"W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" +
"KTPPV",
}, {
Purpose: waddrmgr.KeyScopeBIP0086.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDtdXpdJFU2zFKWHJwe5M2WtYtcV7qSWtKohT9VP9zarNSwKnm" +
"kwDQawsu1vUf9xwXhUDYXbdUqpcrRTn9bLyW4BAVRimZ4K7r5o1J" +
"S924u",
}}
)
// testRemoteSigner tests that a watch-only wallet can use a remote signing
// wallet to perform any signing or ECDH operations.
func testRemoteSigner(ht *lntest.HarnessTest) {
type testCase struct {
name string
randomSeed bool
sendCoins bool
commitType lnrpc.CommitmentType
fn func(tt *lntest.HarnessTest,
wo, carol *node.HarnessNode)
}
subTests := []testCase{{
name: "random seed",
randomSeed: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
// Nothing more to test here.
},
}, {
name: "account import",
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runWalletImportAccountScenario(
tt, walletrpc.AddressType_WITNESS_PUBKEY_HASH,
carol, wo,
)
},
}, {
name: "basic channel open close",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runBasicChannelCreationAndUpdates(tt, wo, carol)
},
}, {
name: "channel funding input types",
sendCoins: false,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runChannelFundingInputTypes(tt, carol, wo)
},
}, {
name: "async payments",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runAsyncPayments(tt, wo, carol, nil)
},
}, {
name: "async payments taproot",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
commitType := lnrpc.CommitmentType_SIMPLE_TAPROOT
runAsyncPayments(
tt, wo, carol, &commitType,
)
},
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
}, {
name: "shared key",
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runDeriveSharedKey(tt, wo)
},
}, {
name: "bumpfee",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runBumpFee(tt, wo)
},
}, {
name: "psbt",
randomSeed: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runPsbtChanFunding(
tt, carol, wo, false,
lnrpc.CommitmentType_LEGACY,
)
runSignPsbtSegWitV0P2WKH(tt, wo)
runSignPsbtSegWitV1KeySpendBip86(tt, wo)
runSignPsbtSegWitV1KeySpendRootHash(tt, wo)
runSignPsbtSegWitV1ScriptSpend(tt, wo)
// The above tests all make sure we can sign for keys
// that aren't in the wallet. But we also want to make
// sure we can fund and then sign PSBTs from our wallet.
runFundAndSignPsbt(ht, wo)
},
}, {
name: "sign output raw",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runSignOutputRaw(tt, wo)
},
}, {
name: "sign verify msg",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runSignVerifyMessage(tt, wo)
},
}, {
name: "taproot",
sendCoins: true,
randomSeed: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
testTaprootSendCoinsKeySpendBip86(tt, wo)
testTaprootComputeInputScriptKeySpendBip86(tt, wo)
testTaprootSignOutputRawScriptSpend(tt, wo)
testTaprootSignOutputRawKeySpendBip86(tt, wo)
testTaprootSignOutputRawKeySpendRootHash(tt, wo)
muSig2Versions := []signrpc.MuSig2Version{
signrpc.MuSig2Version_MUSIG2_VERSION_V040,
signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
}
for _, version := range muSig2Versions {
testTaprootMuSig2KeySpendRootHash(
tt, wo, version,
)
testTaprootMuSig2ScriptSpend(tt, wo, version)
testTaprootMuSig2KeySpendBip86(tt, wo, version)
testTaprootMuSig2CombinedLeafKeySpend(
tt, wo, version,
)
}
},
}}
prepareTest := func(st *lntest.HarnessTest,
subTest testCase) (*node.HarnessNode,
*node.HarnessNode, *node.HarnessNode) {
// Signer is our signing node and has the wallet with the full
// master private key. We test that we can create the watch-only
// wallet from the exported accounts but also from a static key
// to make sure the derivation of the account public keys is
// correct in both cases.
password := []byte("itestpassword")
var (
signerNodePubKey = nodePubKey
watchOnlyAccounts = deriveCustomScopeAccounts(ht.T)
signer *node.HarnessNode
err error
)
if !subTest.randomSeed {
signer = st.RestoreNodeWithSeed(
"Signer", nil, password, nil, rootKey, 0, nil,
)
} else {
signer = st.NewNode("Signer", nil)
signerNodePubKey = signer.PubKeyStr
rpcAccts := signer.RPC.ListAccounts(
&walletrpc.ListAccountsRequest{},
)
watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly(
rpcAccts.Accounts,
)
require.NoError(st, err)
}
var commitArgs []string
if subTest.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
commitArgs = lntest.NodeArgsForCommitType(
subTest.commitType,
)
}
// WatchOnly is the node that has a watch-only wallet and uses
// the Signer node for any operation that requires access to
// private keys.
watchOnly := st.NewNodeRemoteSigner(
"WatchOnly", append([]string{
"--remotesigner.enable",
fmt.Sprintf(
"--remotesigner.rpchost=localhost:%d",
signer.Cfg.RPCPort,
),
fmt.Sprintf(
"--remotesigner.tlscertpath=%s",
signer.Cfg.TLSCertPath,
),
fmt.Sprintf(
"--remotesigner.macaroonpath=%s",
signer.Cfg.AdminMacPath,
),
}, commitArgs...),
password, &lnrpc.WatchOnly{
MasterKeyBirthdayTimestamp: 0,
MasterKeyFingerprint: nil,
Accounts: watchOnlyAccounts,
},
)
resp := watchOnly.RPC.GetInfo()
require.Equal(st, signerNodePubKey, resp.IdentityPubkey)
if subTest.sendCoins {
st.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly)
ht.AssertWalletAccountBalance(
watchOnly, "default",
btcutil.SatoshiPerBitcoin, 0,
)
}
carol := st.NewNode("carol", commitArgs)
st.EnsureConnected(watchOnly, carol)
return signer, watchOnly, carol
}
for _, testCase := range subTests {
subTest := testCase
success := ht.Run(subTest.name, func(tt *testing.T) {
// Skip the cleanup here as no standby node is used.
st := ht.Subtest(tt)
_, watchOnly, carol := prepareTest(st, subTest)
subTest.fn(st, watchOnly, carol)
})
if !success {
return
}
}
}
// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd
// internal key scope.
func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount {
allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts))
allAccounts = append(allAccounts, accounts...)
extendedRootKey, err := hdkeychain.NewKeyFromString(rootKey)
require.NoError(t, err)
path := []uint32{
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
harnessNetParams.HDCoinType + hdkeychain.HardenedKeyStart,
}
coinTypeKey, err := derivePath(extendedRootKey, path)
require.NoError(t, err)
for idx := uint32(0); idx <= 255; idx++ {
accountPath := []uint32{idx + hdkeychain.HardenedKeyStart}
accountKey, err := derivePath(coinTypeKey, accountPath)
require.NoError(t, err)
accountXPub, err := accountKey.Neuter()
require.NoError(t, err)
allAccounts = append(allAccounts, &lnrpc.WatchOnlyAccount{
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: idx,
Xpub: accountXPub.String(),
})
}
return allAccounts
}
// derivePath derives the given path from an extended key.
func derivePath(key *hdkeychain.ExtendedKey, path []uint32) (
*hdkeychain.ExtendedKey, error) {
var (
currentKey = key
err error
)
for _, pathPart := range path {
currentKey, err = currentKey.Derive(pathPart)
if err != nil {
return nil, err
}
}
return currentKey, nil
}