mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-08 14:57:38 +02:00
walletunlocker: modify service to implement new 2-step wallet creation
In this commit, we extend the UnlockerService to account for the new changes in the lnrpc definition. Setting up the daemon for the first time is now two step process: first the user will generate a new seed via the GenSeed method, then the user will present this new seed (and optional pass) to the InitWallet method which then will finalize the wallet creation. This two step process ensures that we don't commit the wallet changes in the case that the user doesn't actually "ACK" the new seed. In the case that the user already has an existing seed, they can re-enter it and skip straight to the InitWallet step. We also update the tests to account for the new API changes.
This commit is contained in:
@@ -4,9 +4,11 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/aezeed"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||
"github.com/lightningnetwork/lnd/walletunlocker"
|
||||
@@ -23,6 +25,13 @@ var (
|
||||
testPassword = []byte("test-password")
|
||||
testSeed = []byte("test-seed-123456789")
|
||||
|
||||
testEntropy = [aezeed.EntropySize]byte{
|
||||
0x81, 0xb6, 0x37, 0xd8,
|
||||
0x63, 0x59, 0xe6, 0x96,
|
||||
0x0d, 0xe7, 0x95, 0xe4,
|
||||
0x1e, 0x0b, 0x4c, 0xfd,
|
||||
}
|
||||
|
||||
testNetParams = &chaincfg.MainNetParams
|
||||
)
|
||||
|
||||
@@ -39,10 +48,127 @@ func createTestWallet(t *testing.T, dir string, netParams *chaincfg.Params) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateWallet checks that CreateWallet correctly returns a password that
|
||||
// can be used for creating a wallet if no wallet exists from before, and
|
||||
// returns an error when it already exists.
|
||||
func TestCreateWallet(t *testing.T) {
|
||||
// TestGenSeedUserEntropy tests that the gen seed method generates a valid
|
||||
// cipher seed mnemonic phrase and user provided source of entropy.
|
||||
func TestGenSeed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll create a new test directory and unlocker service for
|
||||
// that directory.
|
||||
testDir, err := ioutil.TempDir("", "testcreate")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(testDir)
|
||||
}()
|
||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
||||
|
||||
// Now that the service has been created, we'll ask it to generate a
|
||||
// new seed for us given a test passphrase.
|
||||
aezeedPass := []byte("kek")
|
||||
genSeedReq := &lnrpc.GenSeedRequest{
|
||||
AezeedPassphrase: aezeedPass,
|
||||
SeedEntropy: testEntropy[:],
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
seedResp, err := service.GenSeed(ctx, genSeedReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate seed: %v", err)
|
||||
}
|
||||
|
||||
// We should then be able to take the generated mnemonic, and properly
|
||||
// decipher both it.
|
||||
var mnemonic aezeed.Mnemonic
|
||||
copy(mnemonic[:], seedResp.CipherSeedMnemonic[:])
|
||||
_, err = mnemonic.ToCipherSeed(aezeedPass)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decipher cipher seed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenSeedInvalidEntropy tests that the gen seed method generates a valid
|
||||
// cipher seed mnemonic pass phrase even when the user doesn't provide its own
|
||||
// source of entropy.
|
||||
func TestGenSeedGenerateEntropy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll create a new test directory and unlocker service for
|
||||
// that directory.
|
||||
testDir, err := ioutil.TempDir("", "testcreate")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(testDir)
|
||||
}()
|
||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
||||
|
||||
// Now that the service has been created, we'll ask it to generate a
|
||||
// new seed for us given a test passphrase. Note that we don't actually
|
||||
aezeedPass := []byte("kek")
|
||||
genSeedReq := &lnrpc.GenSeedRequest{
|
||||
AezeedPassphrase: aezeedPass,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
seedResp, err := service.GenSeed(ctx, genSeedReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate seed: %v", err)
|
||||
}
|
||||
|
||||
// We should then be able to take the generated mnemonic, and properly
|
||||
// decipher both it.
|
||||
var mnemonic aezeed.Mnemonic
|
||||
copy(mnemonic[:], seedResp.CipherSeedMnemonic[:])
|
||||
_, err = mnemonic.ToCipherSeed(aezeedPass)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decipher cipher seed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenSeedInvalidEntropy tests that if a user attempt to create a seed with
|
||||
// the wrong number of bytes for the initial entropy, then the proper error is
|
||||
// returned.
|
||||
func TestGenSeedInvalidEntropy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll create a new test directory and unlocker service for
|
||||
// that directory.
|
||||
testDir, err := ioutil.TempDir("", "testcreate")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(testDir)
|
||||
}()
|
||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
||||
|
||||
// Now that the service has been created, we'll ask it to generate a
|
||||
// new seed for us given a test passphrase. However, we'll be using an
|
||||
// invalid set of entropy that's 55 bytes, instead of 15 bytes.
|
||||
aezeedPass := []byte("kek")
|
||||
genSeedReq := &lnrpc.GenSeedRequest{
|
||||
AezeedPassphrase: aezeedPass,
|
||||
SeedEntropy: bytes.Repeat([]byte("a"), 55),
|
||||
}
|
||||
|
||||
// We should get an error now since the entropy source was invalid.
|
||||
ctx := context.Background()
|
||||
_, err = service.GenSeed(ctx, genSeedReq)
|
||||
if err == nil {
|
||||
t.Fatalf("seed creation should've failed")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "incorrect entropy length") {
|
||||
t.Fatalf("wrong error, expected incorrect entropy length")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitWallet tests that the user is able to properly initialize the wallet
|
||||
// given an existing cipher seed passphrase.
|
||||
func TestInitWallet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// testDir is empty, meaning wallet was not created from before.
|
||||
@@ -57,22 +183,60 @@ func TestCreateWallet(t *testing.T) {
|
||||
// Create new UnlockerService.
|
||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
||||
|
||||
ctx := context.Background()
|
||||
req := &lnrpc.CreateWalletRequest{
|
||||
Password: testPassword,
|
||||
}
|
||||
_, err = service.CreateWallet(ctx, req)
|
||||
// Once we have the unlocker service created, we'll now instantiate a
|
||||
// new cipher seed instance.
|
||||
cipherSeed, err := aezeed.New(0, &testEntropy, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("CreateWallet call failed: %v", err)
|
||||
t.Fatalf("unable to create seed: %v", err)
|
||||
}
|
||||
|
||||
// Password should be sent over the channel.
|
||||
// With the new seed created, we'll convert it into a mnemonic phrase
|
||||
// that we'll send over to initialize the wallet.
|
||||
pass := []byte("test")
|
||||
mnemonic, err := cipherSeed.ToMnemonic(pass)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create mnemonic: %v", err)
|
||||
}
|
||||
|
||||
// Now that we have all the necessary items, we'll now issue the Init
|
||||
// command to the wallet. This should check the validity of the cipher
|
||||
// seed, then send over the initialization information over the init
|
||||
// channel.
|
||||
ctx := context.Background()
|
||||
req := &lnrpc.InitWalletRequest{
|
||||
WalletPassword: testPassword,
|
||||
CipherSeedMnemonic: []string(mnemonic[:]),
|
||||
AezeedPassphrase: pass,
|
||||
}
|
||||
_, err = service.InitWallet(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("InitWallet call failed: %v", err)
|
||||
}
|
||||
|
||||
// The same user passphrase, and also the plaintext cipher seed
|
||||
// should be sent over and match exactly.
|
||||
select {
|
||||
case pw := <-service.CreatePasswords:
|
||||
if !bytes.Equal(pw, testPassword) {
|
||||
t.Fatalf("expected to receive password %x, got %x",
|
||||
testPassword, pw)
|
||||
case msg := <-service.InitMsgs:
|
||||
if !bytes.Equal(msg.Passphrase, testPassword) {
|
||||
t.Fatalf("expected to receive password %x, "+
|
||||
"got %x", testPassword, msg.Passphrase)
|
||||
}
|
||||
if msg.WalletSeed.InternalVersion != cipherSeed.InternalVersion {
|
||||
t.Fatalf("mismatched versions: expected %v, "+
|
||||
"got %v", cipherSeed.InternalVersion,
|
||||
msg.WalletSeed.InternalVersion)
|
||||
}
|
||||
if msg.WalletSeed.Birthday != cipherSeed.Birthday {
|
||||
t.Fatalf("mismatched birthday: expected %v, "+
|
||||
"got %v", cipherSeed.Birthday,
|
||||
msg.WalletSeed.Birthday)
|
||||
}
|
||||
if msg.WalletSeed.Entropy != cipherSeed.Entropy {
|
||||
t.Fatalf("mismatched versions: expected %x, "+
|
||||
"got %x", cipherSeed.Entropy[:],
|
||||
msg.WalletSeed.Entropy[:])
|
||||
}
|
||||
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("password not received")
|
||||
}
|
||||
@@ -80,11 +244,50 @@ func TestCreateWallet(t *testing.T) {
|
||||
// Create a wallet in testDir.
|
||||
createTestWallet(t, testDir, testNetParams)
|
||||
|
||||
// Now calling CreateWallet should fail, since a wallet already exists
|
||||
// in the directory.
|
||||
_, err = service.CreateWallet(ctx, req)
|
||||
// Now calling InitWallet should fail, since a wallet already exists in
|
||||
// the directory.
|
||||
_, err = service.InitWallet(ctx, req)
|
||||
if err == nil {
|
||||
t.Fatalf("CreateWallet did not fail as expected")
|
||||
t.Fatalf("InitWallet did not fail as expected")
|
||||
}
|
||||
|
||||
// Similarly, if we try to do GenSeed again, we should get an error as
|
||||
// the wallet already exists.
|
||||
_, err = service.GenSeed(ctx, &lnrpc.GenSeedRequest{})
|
||||
if err == nil {
|
||||
t.Fatalf("seed generation should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitWalletInvalidCipherSeed tests that if we attempt to create a wallet
|
||||
// with an invalid cipher seed, then we'll receive an error.
|
||||
func TestCreateWalletInvalidEntropy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// testDir is empty, meaning wallet was not created from before.
|
||||
testDir, err := ioutil.TempDir("", "testcreate")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
os.RemoveAll(testDir)
|
||||
}()
|
||||
|
||||
// Create new UnlockerService.
|
||||
service := walletunlocker.New(nil, testDir, testNetParams)
|
||||
|
||||
// We'll attempt to init the wallet with an invalid cipher seed and
|
||||
// passphrase.
|
||||
req := &lnrpc.InitWalletRequest{
|
||||
WalletPassword: testPassword,
|
||||
CipherSeedMnemonic: []string{"invalid", "seed"},
|
||||
AezeedPassphrase: []byte("fake pass"),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_, err = service.InitWallet(ctx, req)
|
||||
if err == nil {
|
||||
t.Fatalf("wallet creation should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +311,7 @@ func TestUnlockWallet(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
req := &lnrpc.UnlockWalletRequest{
|
||||
Password: testPassword,
|
||||
WalletPassword: testPassword,
|
||||
}
|
||||
|
||||
// Should fail to unlock non-existing wallet.
|
||||
@@ -122,7 +325,7 @@ func TestUnlockWallet(t *testing.T) {
|
||||
|
||||
// Try unlocking this wallet with the wrong passphrase.
|
||||
wrongReq := &lnrpc.UnlockWalletRequest{
|
||||
Password: []byte("wrong-ofc"),
|
||||
WalletPassword: []byte("wrong-ofc"),
|
||||
}
|
||||
_, err = service.UnlockWallet(ctx, wrongReq)
|
||||
if err == nil {
|
||||
@@ -145,5 +348,4 @@ func TestUnlockWallet(t *testing.T) {
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatalf("password not received")
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user