walletunlocker: accept recovery window from InitWallet

This commit is contained in:
Conner Fromknecht 2018-03-26 14:07:22 -07:00
parent c824af11a1
commit f8c0357770
No known key found for this signature in database
GPG Key ID: 39DE78FBE6ACB0EF
2 changed files with 63 additions and 23 deletions

@ -14,7 +14,7 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// WalletInitMsg is a message sent to the UnlockerService when a user wishes to // WalletInitMsg is a message sent by the UnlockerService when a user wishes to
// set up the internal wallet for the first time. The user MUST provide a // set up the internal wallet for the first time. The user MUST provide a
// passphrase, but is also able to provide their own source of entropy. If // passphrase, but is also able to provide their own source of entropy. If
// provided, then this source of entropy will be used to generate the wallet's // provided, then this source of entropy will be used to generate the wallet's
@ -27,6 +27,28 @@ type WalletInitMsg struct {
// WalletSeed is the deciphered cipher seed that the wallet should use // WalletSeed is the deciphered cipher seed that the wallet should use
// to initialize itself. // to initialize itself.
WalletSeed *aezeed.CipherSeed WalletSeed *aezeed.CipherSeed
// 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
// creation.
RecoveryWindow uint32
}
// WalletUnlockMsg is a message sent by the UnlockerService when a user wishes
// to unlock the internal wallet after initial setup. The user can optionally
// specify a recovery window, which will resume an interrupted rescan for used
// addresses.
type WalletUnlockMsg struct {
// Passphrase is the passphrase that will be used to encrypt the wallet
// itself. This MUST be at least 8 characters.
Passphrase []byte
// 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
// creation, but before any addresses have been created.
RecoveryWindow uint32
} }
// UnlockerService implements the WalletUnlocker service used to provide lnd // UnlockerService implements the WalletUnlocker service used to provide lnd
@ -37,10 +59,10 @@ type UnlockerService struct {
// InitMsgs is a channel that carries all wallet init messages. // InitMsgs is a channel that carries all wallet init messages.
InitMsgs chan *WalletInitMsg InitMsgs chan *WalletInitMsg
// UnlockPasswords is a channel where passwords provided by the rpc // UnlockMsgs is a channel where unlock parameters provided by the rpc
// client to be used to unlock and decrypt an existing wallet will be // client to be used to unlock and decrypt an existing wallet will be
// sent. // sent.
UnlockPasswords chan []byte UnlockMsgs chan *WalletUnlockMsg
chainDir string chainDir string
netParams *chaincfg.Params netParams *chaincfg.Params
@ -52,10 +74,10 @@ func New(authSvc *macaroons.Service, chainDir string,
params *chaincfg.Params) *UnlockerService { params *chaincfg.Params) *UnlockerService {
return &UnlockerService{ return &UnlockerService{
InitMsgs: make(chan *WalletInitMsg, 1), InitMsgs: make(chan *WalletInitMsg, 1),
UnlockPasswords: make(chan []byte, 1), UnlockMsgs: make(chan *WalletUnlockMsg, 1),
chainDir: chainDir, chainDir: chainDir,
netParams: params, netParams: params,
} }
} }
@ -73,7 +95,7 @@ func (u *UnlockerService) GenSeed(ctx context.Context,
// Before we start, we'll ensure that the wallet hasn't already created // Before we start, we'll ensure that the wallet hasn't already created
// so we don't show a *new* seed to the user if one already exists. // so we don't show a *new* seed to the user if one already exists.
netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
loader := wallet.NewLoader(u.netParams, netDir) loader := wallet.NewLoader(u.netParams, netDir, 0)
walletExists, err := loader.WalletExists() walletExists, err := loader.WalletExists()
if err != nil { if err != nil {
return nil, err return nil, err
@ -156,10 +178,17 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
"at least 8 characters") "at least 8 characters")
} }
// Require that the recovery window be non-negative.
recoveryWindow := in.RecoveryWindow
if recoveryWindow < 0 {
return nil, fmt.Errorf("recovery window %d must be "+
"non-negative", recoveryWindow)
}
// We'll then open up the directory that will be used to store the // We'll then open up the directory that will be used to store the
// wallet's files so we can check if the wallet already exists. // wallet's files so we can check if the wallet already exists.
netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
loader := wallet.NewLoader(u.netParams, netDir) loader := wallet.NewLoader(u.netParams, netDir, uint32(recoveryWindow))
walletExists, err := loader.WalletExists() walletExists, err := loader.WalletExists()
if err != nil { if err != nil {
@ -198,8 +227,9 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
// now send over the wallet password and the seed. This will allow the // now send over the wallet password and the seed. This will allow the
// daemon to initialize itself and startup. // daemon to initialize itself and startup.
initMsg := &WalletInitMsg{ initMsg := &WalletInitMsg{
Passphrase: password, Passphrase: password,
WalletSeed: cipherSeed, WalletSeed: cipherSeed,
RecoveryWindow: uint32(recoveryWindow),
} }
u.InitMsgs <- initMsg u.InitMsgs <- initMsg
@ -208,13 +238,16 @@ func (u *UnlockerService) InitWallet(ctx context.Context,
} }
// UnlockWallet sends the password provided by the incoming UnlockWalletRequest // UnlockWallet sends the password provided by the incoming UnlockWalletRequest
// over the UnlockPasswords channel in case it successfully decrypts an // over the UnlockMsgs channel in case it successfully decrypts an existing
// existing wallet found in the chain's wallet database directory. // wallet found in the chain's wallet database directory.
func (u *UnlockerService) UnlockWallet(ctx context.Context, func (u *UnlockerService) UnlockWallet(ctx context.Context,
in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) { in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) {
password := in.WalletPassword
recoveryWindow := uint32(in.RecoveryWindow)
netDir := btcwallet.NetworkDir(u.chainDir, u.netParams) netDir := btcwallet.NetworkDir(u.chainDir, u.netParams)
loader := wallet.NewLoader(u.netParams, netDir) loader := wallet.NewLoader(u.netParams, netDir, recoveryWindow)
// Check if wallet already exists. // Check if wallet already exists.
walletExists, err := loader.WalletExists() walletExists, err := loader.WalletExists()
@ -228,7 +261,7 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context,
} }
// Try opening the existing wallet with the provided password. // Try opening the existing wallet with the provided password.
_, err = loader.OpenExistingWallet(in.WalletPassword, false) _, err = loader.OpenExistingWallet(password, false)
if err != nil { if err != nil {
// Could not open wallet, most likely this means that provided // Could not open wallet, most likely this means that provided
// password was incorrect. // password was incorrect.
@ -244,17 +277,22 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context,
// Attempt to create a password for the macaroon service. // Attempt to create a password for the macaroon service.
if u.authSvc != nil { if u.authSvc != nil {
err = u.authSvc.CreateUnlock(&in.WalletPassword) err = u.authSvc.CreateUnlock(&password)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create/unlock "+ return nil, fmt.Errorf("unable to create/unlock "+
"macaroon store: %v", err) "macaroon store: %v", err)
} }
} }
walletUnlockMsg := &WalletUnlockMsg{
Passphrase: password,
RecoveryWindow: recoveryWindow,
}
// At this point we was able to open the existing wallet with the // At this point we was able to open the existing wallet with the
// provided password. We send the password over the UnlockPasswords // provided password. We send the password over the UnlockMsgs
// channel, such that it can be used by lnd to open the wallet. // channel, such that it can be used by lnd to open the wallet.
u.UnlockPasswords <- in.WalletPassword u.UnlockMsgs <- walletUnlockMsg
return &lnrpc.UnlockWalletResponse{}, nil return &lnrpc.UnlockWalletResponse{}, nil
} }

@ -37,8 +37,10 @@ var (
func createTestWallet(t *testing.T, dir string, netParams *chaincfg.Params) { func createTestWallet(t *testing.T, dir string, netParams *chaincfg.Params) {
netDir := btcwallet.NetworkDir(dir, netParams) netDir := btcwallet.NetworkDir(dir, netParams)
loader := wallet.NewLoader(netParams, netDir) loader := wallet.NewLoader(netParams, netDir, 0)
_, err := loader.CreateNewWallet(testPassword, testPassword, testSeed) _, err := loader.CreateNewWallet(
testPassword, testPassword, testSeed, time.Time{},
)
if err != nil { if err != nil {
t.Fatalf("failed creating wallet: %v", err) t.Fatalf("failed creating wallet: %v", err)
} }
@ -340,10 +342,10 @@ func TestUnlockWallet(t *testing.T) {
// Password should be sent over the channel. // Password should be sent over the channel.
select { select {
case pw := <-service.UnlockPasswords: case unlockMsg := <-service.UnlockMsgs:
if !bytes.Equal(pw, testPassword) { if !bytes.Equal(unlockMsg.Passphrase, testPassword) {
t.Fatalf("expected to receive password %x, got %x", t.Fatalf("expected to receive password %x, got %x",
testPassword, pw) testPassword, unlockMsg.Passphrase)
} }
case <-time.After(3 * time.Second): case <-time.After(3 * time.Second):
t.Fatalf("password not received") t.Fatalf("password not received")