mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
7dfe4018ce
This commit was previously split into the following parts to ease review: - 2d746f68: replace imports - 4008f0fd: use ecdsa.Signature - 849e33d1: remove btcec.S256() - b8f6ebbd: use v2 library correctly - fa80bca9: bump go modules
242 lines
6.7 KiB
Go
242 lines
6.7 KiB
Go
package btcwallet
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/integration/rpctest"
|
|
"github.com/btcsuite/btcwallet/chain"
|
|
"github.com/lightningnetwork/lnd/blockcache"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// seedBytes is the raw entropy of the aezeed:
|
|
// able promote dizzy mixture sword myth share public find tattoo
|
|
// catalog cousin bulb unfair machine alarm cool large promote kick
|
|
// shop rug mean year
|
|
// Which corresponds to the master root key:
|
|
// xprv9s21ZrQH143K2KADjED57FvNbptdKLp4sqKzssegwEGKQMGoDkbyhUeCKe5m3A
|
|
// MU44z4vqkmGswwQVKrv599nFG16PPZDEkNrogwoDGeCmZ
|
|
seedBytes, _ = hex.DecodeString("4a7611b6979ba7c4bc5c5cd2239b2973")
|
|
|
|
// firstAddress is the first address that we should get from the wallet,
|
|
// corresponding to the derivation path m/84'/0'/0'/0/0 (even on regtest
|
|
// which is a special case for the BIP49/84 addresses in btcwallet).
|
|
firstAddress = "bcrt1qgdlgjc5ede7fjv350wcjqat80m0zsmfaswsj9p"
|
|
|
|
testCases = []struct {
|
|
name string
|
|
path []uint32
|
|
err string
|
|
wif string
|
|
}{{
|
|
name: "m/84'/0'/0'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(84), hardenedKey(0), hardenedKey(0), 0, 0,
|
|
},
|
|
wif: "cPp3XUewCBQVg3pgwVWbtpzDwhWTQpHhu8saN3SdGRTkiLpu1R6h",
|
|
}, {
|
|
name: "m/84'/0'/0'/1/0",
|
|
path: []uint32{
|
|
hardenedKey(84), hardenedKey(0), hardenedKey(0), 1, 0,
|
|
},
|
|
wif: "cPUR1nFAeYAtSWSkKoWB6WbzRTbDSGdrGRmv1kVLRPyo7QXph2gt",
|
|
}, {
|
|
name: "m/84'/0'/0'/0/12345",
|
|
path: []uint32{
|
|
hardenedKey(84), hardenedKey(0), hardenedKey(0), 0,
|
|
12345,
|
|
},
|
|
wif: "cQCdGxqKeGZKiC2uRYMAGenJHkDvajiPieT4Yg7k1BKawjKkywvz",
|
|
}, {
|
|
name: "m/49'/0'/0'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(49), hardenedKey(0), hardenedKey(0), 0, 0,
|
|
},
|
|
wif: "cMwVK2bcTzivPZfcCH585rBGghqsJAP9MdVy8inRti1wZvLn5DvY",
|
|
}, {
|
|
name: "m/49'/0'/0'/1/0",
|
|
path: []uint32{
|
|
hardenedKey(49), hardenedKey(0), hardenedKey(0), 1, 0,
|
|
},
|
|
wif: "cNPW9bMtdc2YGBzWzSCXFN4excjrT34nZzGYtfkzkazUrt3dXuv7",
|
|
}, {
|
|
name: "m/49'/0'/0'/1/12345",
|
|
path: []uint32{
|
|
hardenedKey(49), hardenedKey(0), hardenedKey(0), 1,
|
|
12345,
|
|
},
|
|
wif: "cNdJt2fSNUJYVSb8JFjhosPcQgNvJ92SjNeNpsf1gUwDVDv2KVRa",
|
|
}, {
|
|
name: "m/1017'/1'/0'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(1017), hardenedKey(1), hardenedKey(0), 0, 0,
|
|
},
|
|
wif: "cPsCmbWQENgptj3eTiyd85QSAD1xqYKPM9jUkfvm7vgN3SoVPWSP",
|
|
}, {
|
|
name: "m/1017'/1'/6'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(1017), hardenedKey(1), hardenedKey(6), 0, 0,
|
|
},
|
|
wif: "cPeQdpcGJmLqpdmvokh3DK9ZtjYAXxiw4p4ELNUWkWt6bMRqArEV",
|
|
}, {
|
|
name: "m/1017'/1'/7'/0/123",
|
|
path: []uint32{
|
|
hardenedKey(1017), hardenedKey(1), hardenedKey(7), 0,
|
|
123,
|
|
},
|
|
wif: "cPcWZMqY4YErkcwjtFJaYoXkzd7bKxrfxAVzhDgy3n5BGH8CU8sn",
|
|
}, {
|
|
name: "m/84'/1'/0'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(84), hardenedKey(1), hardenedKey(0), 0, 0,
|
|
},
|
|
err: "coin type must be 0 for BIP49/84 btcwallet keys",
|
|
}, {
|
|
name: "m/1017'/0'/0'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(1017), hardenedKey(0), hardenedKey(0), 0, 0,
|
|
},
|
|
err: "expected coin type 1, instead was 0",
|
|
}, {
|
|
name: "m/84'/0'/1'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(84), hardenedKey(0), hardenedKey(1), 0, 0,
|
|
},
|
|
err: "account 1 not found",
|
|
}, {
|
|
name: "m/49'/0'/1'/0/0",
|
|
path: []uint32{
|
|
hardenedKey(49), hardenedKey(0), hardenedKey(1), 0, 0,
|
|
},
|
|
err: "account 1 not found",
|
|
}, {
|
|
name: "non-hardened purpose m/84/0/0/0/0",
|
|
path: []uint32{84, 0, 0, 0, 0},
|
|
err: "element at index 0 is not hardened",
|
|
}, {
|
|
name: "non-hardened account m/84'/0'/0/0/0",
|
|
path: []uint32{hardenedKey(84), hardenedKey(0), 0, 0, 0},
|
|
err: "element at index 2 is not hardened",
|
|
}}
|
|
)
|
|
|
|
// TestBip32KeyDerivation makes sure that private keys can be derived from a
|
|
// BIP32 key path correctly.
|
|
func TestBip32KeyDerivation(t *testing.T) {
|
|
netParams := &chaincfg.RegressionNetParams
|
|
w, cleanup := newTestWallet(t, netParams, seedBytes)
|
|
defer cleanup()
|
|
|
|
// This is just a sanity check that the wallet was initialized
|
|
// correctly. We make sure the first derived address is the expected
|
|
// one.
|
|
firstDerivedAddr, err := w.NewAddress(
|
|
lnwallet.WitnessPubKey, false, lnwallet.DefaultAccountName,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, firstAddress, firstDerivedAddr.String())
|
|
|
|
// Let's go through the test cases now that we know our wallet is ready.
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
privKey, err := w.deriveKeyByBIP32Path(tc.path)
|
|
|
|
if tc.err == "" {
|
|
require.NoError(t, err)
|
|
wif, err := btcutil.NewWIF(
|
|
privKey, netParams, true,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.wif, wif.String())
|
|
} else {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func newTestWallet(t *testing.T, netParams *chaincfg.Params,
|
|
seedBytes []byte) (*BtcWallet, func()) {
|
|
|
|
tempDir, err := ioutil.TempDir("", "lnwallet")
|
|
if err != nil {
|
|
_ = os.RemoveAll(tempDir)
|
|
t.Fatalf("creating temp dir failed: %v", err)
|
|
}
|
|
|
|
chainBackend, backendCleanup := getChainBackend(t, netParams)
|
|
cleanup := func() {
|
|
_ = os.RemoveAll(tempDir)
|
|
backendCleanup()
|
|
}
|
|
|
|
loaderOpt := LoaderWithLocalWalletDB(tempDir, false, time.Minute)
|
|
config := Config{
|
|
PrivatePass: []byte("some-pass"),
|
|
HdSeed: seedBytes,
|
|
NetParams: netParams,
|
|
CoinType: netParams.HDCoinType,
|
|
ChainSource: chainBackend,
|
|
// wallet starts in recovery mode
|
|
RecoveryWindow: 2,
|
|
LoaderOptions: []LoaderOption{loaderOpt},
|
|
}
|
|
blockCache := blockcache.NewBlockCache(10000)
|
|
w, err := New(config, blockCache)
|
|
if err != nil {
|
|
cleanup()
|
|
t.Fatalf("creating wallet failed: %v", err)
|
|
}
|
|
|
|
err = w.Start()
|
|
if err != nil {
|
|
cleanup()
|
|
t.Fatalf("starting wallet failed: %v", err)
|
|
}
|
|
|
|
return w, cleanup
|
|
}
|
|
|
|
// getChainBackend returns a simple btcd based chain backend to back the wallet.
|
|
func getChainBackend(t *testing.T, netParams *chaincfg.Params) (chain.Interface,
|
|
func()) {
|
|
|
|
miningNode, err := rpctest.New(netParams, nil, nil, "")
|
|
require.NoError(t, err)
|
|
require.NoError(t, miningNode.SetUp(true, 25))
|
|
|
|
// Next, mine enough blocks in order for SegWit and the CSV package
|
|
// soft-fork to activate on RegNet.
|
|
numBlocks := netParams.MinerConfirmationWindow * 2
|
|
_, err = miningNode.Client.Generate(numBlocks)
|
|
require.NoError(t, err)
|
|
|
|
rpcConfig := miningNode.RPCConfig()
|
|
chainClient, err := chain.NewRPCClient(
|
|
netParams, rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
|
|
rpcConfig.Certificates, false, 20,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
return chainClient, func() {
|
|
_ = miningNode.TearDown()
|
|
}
|
|
}
|
|
|
|
// hardenedKey returns a key of a hardened derivation key path.
|
|
func hardenedKey(part uint32) uint32 {
|
|
return part + hdkeychain.HardenedKeyStart
|
|
}
|