lnwallet: when signing create account if not found

In this commit, we address an edge case that can happen a user rescans
w/ their seed, while retaining their existing `channel.db`. Once they
rescan, if they go to sign for a channel sweep for example, the
commitment key family (actually an account) may not yet have been
created, causing the signing attempt to fail.

We remedy this always creating the account if we go to sign, and the
account isn't found. The change has been structured to make this the
exception, so we'll avoid always needing to do 2 DB hits (check if
account exists, sign), each time we sign.

A new test has been added to exercise this behavior. If the diff from
the `signer.go` file is removed, then the test will fail.
This commit is contained in:
Olaoluwa Osuntokun 2019-08-20 19:08:03 -07:00
parent 2f8d3c4526
commit cc4daa04ad
No known key found for this signature in database
GPG key ID: CE58F7F8E20FD9A2
2 changed files with 87 additions and 11 deletions

View file

@ -83,6 +83,25 @@ func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, err
return nil, lnwallet.ErrNotMine
}
// deriveFromKeyLoc attempts to derive a private key using a fully specified
// KeyLocator.
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
addrmgrNs walletdb.ReadWriteBucket,
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
path := waddrmgr.DerivationPath{
Account: uint32(keyLoc.Family),
Branch: 0,
Index: uint32(keyLoc.Index),
}
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
if err != nil {
return nil, err
}
return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
}
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
// valid key locator.
func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
@ -95,20 +114,31 @@ func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.Priva
}
var key *btcec.PrivateKey
err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
path := waddrmgr.DerivationPath{
Account: uint32(keyLoc.Family),
Branch: 0,
Index: uint32(keyLoc.Index),
}
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
if err != nil {
return err
key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
// If we've reached this point, then the account
// doesn't yet exist, so we'll create it now to ensure
// we can sign.
acctErr := scopedMgr.NewRawAccount(
addrmgrNs, uint32(keyLoc.Family),
)
if acctErr != nil {
return acctErr
}
// Now that we know the account exists, we'll attempt
// to re-derive the private key.
key, err = deriveFromKeyLoc(
scopedMgr, addrmgrNs, keyLoc,
)
if err != nil {
return err
}
}
key, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
return err
})
if err != nil {

View file

@ -2427,6 +2427,48 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
}
}
// testSignOutputCreateAccount tests that we're able to properly sign for an
// output if the target account hasn't yet been created on disk. In this case,
// we'll create the account, then sign.
func testSignOutputCreateAccount(r *rpctest.Harness, w *lnwallet.LightningWallet,
_ *lnwallet.LightningWallet, t *testing.T) {
// First, we'll create a sign desc that references a non-default key
// family. Under the hood, key families are actually accounts, so this
// should force create of the account so we can sign with it.
fakeTx := wire.NewMsgTx(2)
fakeTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
})
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: 99,
Index: 1,
},
},
WitnessScript: []byte{},
Output: &wire.TxOut{
Value: 1000,
},
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(fakeTx),
InputIndex: 0,
}
// We'll now sign and expect this to succeed, as even though the
// account doesn't exist atm, it should be created in order to process
// the inbound signing request.
_, err := w.Cfg.Signer.SignOutputRaw(fakeTx, signDesc)
if err != nil {
t.Fatalf("unable to sign for output with non-existent "+
"account: %v", err)
}
}
type walletTestCase struct {
name string
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
@ -2493,6 +2535,10 @@ var walletTests = []walletTestCase{
name: "create simple tx",
test: testCreateSimpleTx,
},
{
name: "test sign create account",
test: testSignOutputCreateAccount,
},
}
func clearWalletStates(a, b *lnwallet.LightningWallet) error {