diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index ad42ac825..ddc45f3bf 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -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 { diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index ac650778b..bfed469eb 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -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 {