diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index d4c2506a..470208fc 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -24,7 +24,7 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) ) // First check to see if the output is already within the utxo cache. - // If so we can return directly saving usk a disk access. + // If so we can return directly saving a disk access. b.cacheMtx.RLock() if output, ok := b.utxoCache[*prevOut]; ok { b.cacheMtx.RUnlock() @@ -72,7 +72,7 @@ func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, err return nil, fmt.Errorf("address not found") } -// fetchPrivKey attempts to retrieve the raw private key coresponding to the +// fetchPrivKey attempts to retrieve the raw private key corresponding to the // passed public key. // TODO(roasbeef): alternatively can extract all the data pushes within the // script, then attempt to match keys one by one @@ -98,14 +98,24 @@ func (b *BtcWallet) fetchPrivKey(pub *btcec.PublicKey) (*btcec.PrivateKey, error func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, signDesc *lnwallet.SignDescriptor) ([]byte, error) { witnessScript := signDesc.WitnessScript + // First attempt to fetch the private key which corresponds to the + // specified public key. privKey, err := b.fetchPrivKey(signDesc.PubKey) if err != nil { return nil, err } + // If a tweak is specified, then we'll need to use this tweak to derive + // the final private key to be used for signing this output. + if signDesc.PrivateTweak != nil { + privKey = lnwallet.DeriveRevocationPrivKey(privKey, + signDesc.PrivateTweak) + } + amt := signDesc.Output.Value - sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, 0, - amt, witnessScript, txscript.SigHashAll, privKey) + sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, + signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll, + privKey) if err != nil { return nil, err } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 2b0ce9cd..8ffc49a2 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -140,7 +140,7 @@ type WalletController interface { // SendOutputs funds, signs, and broadcasts a Bitcoin transaction // paying out to the specified outputs. In the case the wallet has - // insufficient funds, or the outputs are non-standard, and error + // insufficient funds, or the outputs are non-standard, an error // should be returned. SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error) @@ -219,6 +219,15 @@ type SignDescriptor struct { // key corresponding to this public key. PubKey *btcec.PublicKey + // PrivateTweak is a scalar value that should be added to the private + // key corresponding to the above public key to obtain the private key + // to be used to sign this input. This value is typically a leaf node + // from the revocation tree. + // + // NOTE: If this value is nil, then the input can be signed using only + // the above public key. + PrivateTweak []byte + // WitnessScript is the full script required to properly redeem the // output. This field will only be populated if a p2wsh or a p2sh // output is being signed. diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index eea887b0..868cb1d2 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -1120,11 +1120,115 @@ func testTransactionSubscriptions(miner *rpctest.Harness, w *lnwallet.LightningW select { case <-time.After(time.Second * 5): t.Fatalf("transactions not received after 3 seconds") - case <-confirmedNtfns: // Fall through on successs + case <-confirmedNtfns: // Fall through on success + } +} + +func testSignOutputPrivateTweak(r *rpctest.Harness, w *lnwallet.LightningWallet, t *testing.T) { + t.Logf("Running private tweak test") + + // We'd like to test the ability of the wallet's Signer implementation + // to be able to sign with a private key derived from tweaking the + // specific public key. This scenario exercises the case when the + // wallet needs to sign for a sweep of a revoked output. + + // First, generate a new public key under th control of the wallet, + // then generate a revocation key using it. + pubkey, err := w.NewRawKey() + if err != nil { + t.Fatalf("unable to obtain public key: %v", err) + } + revocation := bytes.Repeat([]byte{2}, 32) + revocationKey := lnwallet.DeriveRevocationPubkey(pubkey, revocation) + + // With the revocation key generated, create a pkScript that pays to + // the revocation key using a simple p2wkh script. + pubkeyHash := btcutil.Hash160(revocationKey.SerializeCompressed()) + revokeAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash, + &chaincfg.SimNetParams) + if err != nil { + t.Fatalf("unable to create addr: %v", err) + } + revokeScript, err := txscript.PayToAddrScript(revokeAddr) + if err != nil { + t.Fatalf("unable to generate script: %v", err) + } + + // With the script fully assemebld, instruct the wallet to fund the + // output with a newly creaed transaction. + revokeOutput := &wire.TxOut{ + Value: btcutil.SatoshiPerBitcoin, + PkScript: revokeScript, + } + txid, err := w.SendOutputs([]*wire.TxOut{revokeOutput}) + if err != nil { + t.Fatalf("unable to create output: %v", err) + } + + // Query for the transaction generated above so we can located the + // index of our output. + tx, err := w.ChainIO.GetTransaction(txid) + if err != nil { + t.Fatalf("unable to query for tx: %v", err) + } + var outputIndex uint32 + if bytes.Equal(tx.TxOut[0].PkScript, revokeScript) { + outputIndex = 0 + } else { + outputIndex = 1 + } + + /// WIth the index located, we can create a transaction spending the + //referenced output. + sweepTx := wire.NewMsgTx() + sweepTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: tx.TxSha(), + Index: outputIndex, + }, + }) + + // Now we can populate the sign descriptor which we'll use to generate + // the signature. Within the descriptor we set the private tweak value + // as the key in the script is derived based on this tweak value and + // the key we originally generated above. + signDesc := &lnwallet.SignDescriptor{ + PubKey: pubkey, + PrivateTweak: revocation, + WitnessScript: revokeScript, + Output: revokeOutput, + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes(sweepTx), + InputIndex: 0, + } + + // With the descriptor created, we use it to generate a signature, then + // manually create a valid witness stack we'll use for signing. + spendSig, err := w.Signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + t.Fatalf("unable to generate signature: %v", err) + } + witness := make([][]byte, 2) + witness[0] = append(spendSig, byte(txscript.SigHashAll)) + witness[1] = revocationKey.SerializeCompressed() + sweepTx.TxIn[0].Witness = witness + + // Finally, attempt to validate the completed transaction. This should + // succeed if the wallet was able to properly generate the proper + // private key. + vm, err := txscript.NewEngine(revokeScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, int64(btcutil.SatoshiPerBitcoin)) + if err != nil { + t.Fatalf("unable to create engine: %v", err) + } + if err := vm.Execute(); err != nil { + t.Fatalf("revocation spend invalid: %v", err) } } var walletTests = []func(miner *rpctest.Harness, w *lnwallet.LightningWallet, test *testing.T){ + // TODO(roasbeef): reservation tests should prob be split out testDualFundingReservationWorkflow, testSingleFunderReservationWorkflowInitiator, testSingleFunderReservationWorkflowResponder, @@ -1133,6 +1237,7 @@ var walletTests = []func(miner *rpctest.Harness, w *lnwallet.LightningWallet, te testFundingReservationInvalidCounterpartySigs, testTransactionSubscriptions, testListTransactionDetails, + testSignOutputPrivateTweak, } type testLnWallet struct { diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 2f19b2f2..57e27ffa 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -271,9 +271,9 @@ type LightningWallet struct { // update the commitment state. Signer Signer - // chainIO is an instance of the BlockChainIO interface. chainIO is + // ChainIO is an instance of the BlockChainIO interface. ChainIO is // used to lookup the existence of outputs within the UTXO set. - chainIO BlockChainIO + ChainIO BlockChainIO // rootKey is the root HD key derived from a WalletController private // key. This rootKey is used to derive all LN specific secrets. @@ -341,7 +341,7 @@ func NewLightningWallet(cdb *channeldb.DB, notifier chainntnfs.ChainNotifier, chainNotifier: notifier, Signer: signer, WalletController: wallet, - chainIO: bio, + ChainIO: bio, ChannelDB: cdb, msgChan: make(chan interface{}, msgBufferSize), nextFundingID: 0, @@ -961,7 +961,7 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // Fetch the alleged previous output along with the // pkscript referenced by this input. prevOut := txin.PreviousOutPoint - output, err := l.chainIO.GetUtxo(&prevOut.Hash, prevOut.Index) + output, err := l.ChainIO.GetUtxo(&prevOut.Hash, prevOut.Index) if output == nil { msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err) return @@ -1200,7 +1200,7 @@ func (l *LightningWallet) handleChannelOpen(req *channelOpenMsg) { // Finally, create and officially open the payment channel! // TODO(roasbeef): CreationTime once tx is 'open' - channel, _ := NewLightningChannel(l.Signer, l.chainIO, l.chainNotifier, + channel, _ := NewLightningChannel(l.Signer, l.ChainIO, l.chainNotifier, res.partialState) res.chanOpen <- channel @@ -1242,7 +1242,7 @@ out: // Finally, create and officially open the payment channel! // TODO(roasbeef): CreationTime once tx is 'open' - channel, _ := NewLightningChannel(l.Signer, l.chainIO, l.chainNotifier, + channel, _ := NewLightningChannel(l.Signer, l.ChainIO, l.chainNotifier, res.partialState) res.chanOpen <- channel }