// validateSigScripts executes the signature script of the tx input with the // given index, returning an error if it fails. func validateSigScript(msgtx *wire.MsgTx, idx int, pkScript []byte) error { vm, err := txscript.NewEngine(pkScript, msgtx, idx, txscript.StandardVerifyFlags, nil) if err != nil { return newError(ErrTxSigning, "cannot create script engine", err) } if err = vm.Execute(); err != nil { return newError(ErrTxSigning, "cannot validate tx signature", err) } return nil }
// validateMsgTx verifies transaction input scripts for tx. All previous output // scripts from outputs redeemed by the transaction, in the same order they are // spent, must be passed in the prevScripts slice. func validateMsgTx(tx *wire.MsgTx, prevScripts [][]byte, inputValues []btcutil.Amount) error { hashCache := txscript.NewTxSigHashes(tx) for i, prevScript := range prevScripts { vm, err := txscript.NewEngine(prevScript, tx, i, txscript.StandardVerifyFlags, nil, hashCache, int64(inputValues[i])) if err != nil { return fmt.Errorf("cannot create script engine: %s", err) } err = vm.Execute() if err != nil { return fmt.Errorf("cannot validate transaction: %s", err) } } return nil }
// handleFundingCounterPartySigs is the final step in the channel reservation // workflow. During this step, we validate *all* the received signatures for // inputs to the funding transaction. If any of these are invalid, we bail, // and forcibly cancel this funding request. Additionally, we ensure that the // signature we received from the counterparty for our version of the commitment // transaction allows us to spend from the funding output with the addition of // our signature. func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigsMsg) { l.limboMtx.RLock() res, ok := l.fundingLimbo[msg.pendingFundingID] l.limboMtx.RUnlock() if !ok { msg.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety res.Lock() defer res.Unlock() // Now we can complete the funding transaction by adding their // signatures to their inputs. res.theirFundingInputScripts = msg.theirFundingInputScripts inputScripts := msg.theirFundingInputScripts fundingTx := res.fundingTx sigIndex := 0 fundingHashCache := txscript.NewTxSigHashes(fundingTx) for i, txin := range fundingTx.TxIn { if len(inputScripts) != 0 && len(txin.Witness) == 0 { // Attach the input scripts so we can verify it below. txin.Witness = inputScripts[sigIndex].Witness txin.SignatureScript = inputScripts[sigIndex].ScriptSig // 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) if output == nil { msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err) return } // Ensure that the witness+sigScript combo is valid. vm, err := txscript.NewEngine(output.PkScript, fundingTx, i, txscript.StandardVerifyFlags, nil, fundingHashCache, output.Value) if err != nil { // TODO(roasbeef): cancel at this stage if invalid sigs? msg.err <- fmt.Errorf("cannot create script engine: %s", err) return } if err = vm.Execute(); err != nil { msg.err <- fmt.Errorf("cannot validate transaction: %s", err) return } sigIndex++ } } // At this point, we can also record and verify their signature for our // commitment transaction. res.theirCommitmentSig = msg.theirCommitmentSig commitTx := res.partialState.OurCommitTx theirKey := res.theirContribution.MultiSigKey // Re-generate both the witnessScript and p2sh output. We sign the // witnessScript script, but include the p2sh output as the subscript // for verification. witnessScript := res.partialState.FundingWitnessScript // Next, create the spending scriptSig, and then verify that the script // is complete, allowing us to spend from the funding transaction. theirCommitSig := msg.theirCommitmentSig channelValue := int64(res.partialState.Capacity) hashCache := txscript.NewTxSigHashes(commitTx) sigHash, err := txscript.CalcWitnessSigHash(witnessScript, hashCache, txscript.SigHashAll, commitTx, 0, channelValue) if err != nil { msg.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err) return } // Verify that we've received a valid signature from the remote party // for our version of the commitment transaction. sig, err := btcec.ParseSignature(theirCommitSig, btcec.S256()) if err != nil { msg.err <- err return } else if !sig.Verify(sigHash, theirKey) { msg.err <- fmt.Errorf("counterparty's commitment signature is invalid") return } res.partialState.OurCommitSig = theirCommitSig // Funding complete, this entry can be removed from limbo. l.limboMtx.Lock() delete(l.fundingLimbo, res.reservationID) l.limboMtx.Unlock() walletLog.Infof("Broadcasting funding tx for ChannelPoint(%v): %v", res.partialState.FundingOutpoint, spew.Sdump(fundingTx)) // Broacast the finalized funding transaction to the network. if err := l.PublishTransaction(fundingTx); err != nil { msg.err <- err return } // Add the complete funding transaction to the DB, in it's open bucket // which will be used for the lifetime of this channel. // TODO(roasbeef): revisit faul-tolerance of this flow nodeAddr := res.nodeAddr if err := res.partialState.FullSyncWithAddr(nodeAddr); err != nil { msg.err <- err return } // Create a goroutine to watch the chain so we can open the channel once // the funding tx has enough confirmations. go l.openChannelAfterConfirmations(res) msg.err <- nil }
// TestHTLCReceiverSpendValidation tests all possible valid+invalid redemption // paths in the script used within the reciever's commitment transaction for an // incoming HTLC. // // The following cases are exercised by this test: // * reciever spends // * HTLC redemption w/ invalid preimage size // * HTLC redemption w/ invalid sequence // * HTLC redemption w/ valid preimage size // * sender spends // * revoke w/ sig // * refund w/ invalid lock time // * refund w/ valid lock time func TestHTLCReceiverSpendValidation(t *testing.T) { // We generate a fake output, and the coresponding txin. This output // doesn't need to exist, as we'll only be validating spending from the // transaction that references this. fundingOut := &wire.OutPoint{ Hash: testHdSeed, Index: 50, } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) // Generate a payment and revocation pre-image to be used below. revokePreimage := testHdSeed[:] revokeHash := fastsha256.Sum256(revokePreimage) paymentPreimage := revokeHash paymentPreimage[0] ^= 1 paymentHash := fastsha256.Sum256(paymentPreimage[:]) // We'll also need some tests keys for alice and bob, and meta-data of // the HTLC output. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) paymentAmt := btcutil.Amount(1 * 10e8) cltvTimeout := uint32(8) csvTimeout := uint32(5) // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout, aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) if err != nil { t.Fatalf("unable to create htlc sender script: %v", err) } htlcWitnessScript, err := witnessScriptHash(htlcScript) if err != nil { t.Fatalf("unable to create p2wsh htlc script: %v", err) } // This will be Bob's commitment transaction. In this scenario Alice // is sending an HTLC to a node she has a a path to (could be Bob, // could be multiple hops down, it doesn't really matter). recieverCommitTx := wire.NewMsgTx() recieverCommitTx.AddTxIn(fakeFundingTxIn) recieverCommitTx.AddTxOut(&wire.TxOut{ Value: int64(paymentAmt), PkScript: htlcWitnessScript, }) prevOut := &wire.OutPoint{ Hash: recieverCommitTx.TxSha(), Index: 0, } sweepTx := wire.NewMsgTx() sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil)) sweepTx.AddTxOut( &wire.TxOut{ PkScript: []byte("doesn't matter"), Value: 1 * 10e8, }, ) testCases := []struct { witness func() wire.TxWitness valid bool }{ { // HTLC redemption w/ invalid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, bytes.Repeat([]byte{1}, 45), csvTimeout, ) }), false, }, { // HTLC redemption w/ invalid sequence makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, paymentPreimage[:], csvTimeout-2, ) }), false, }, { // HTLC redemption w/ valid preimage size makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRedeem(htlcScript, paymentAmt, bobKeyPriv, sweepTx, paymentPreimage[:], csvTimeout, ) }), true, }, { // revoke w/ sig makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendRevoke(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, revokePreimage[:], ) }), true, }, { // refund w/ invalid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendTimeout(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, cltvTimeout-2) }), false, }, { // refund w/ valid lock time makeWitnessTestCase(t, func() (wire.TxWitness, error) { return receiverHtlcSpendTimeout(htlcScript, paymentAmt, aliceKeyPriv, sweepTx, cltvTimeout) }), true, }, } for i, testCase := range testCases { sweepTx.TxIn[0].Witness = testCase.witness() vm, err := txscript.NewEngine(htlcWitnessScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(paymentAmt)) if err != nil { t.Fatalf("unable to create engine: %v", err) } // This buffer will trace execution of the Script, only dumping // out to stdout in the case that a test fails. var debugBuf bytes.Buffer done := false for !done { dis, err := vm.DisasmPC() if err != nil { t.Fatalf("stepping (%v)\n", err) } debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) done, err = vm.Step() if err != nil && testCase.valid { fmt.Println(debugBuf.String()) t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err) } else if err == nil && !testCase.valid && done { fmt.Println(debugBuf.String()) t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err) } debugBuf.WriteString(fmt.Sprintf("Stack: ", vm.GetStack())) debugBuf.WriteString(fmt.Sprintf("AltStack: ", vm.GetAltStack())) } } }
// TestCommitmentSpendValidation test the spendability of both outputs within // the commitment transaction. // // The following spending cases are covered by this test: // * Alice's spend from the delayed output on her commitment transaction. // * Bob's spend from Alice's delayed output when she broadcasts a revoked // commitment transaction. // * Bob's spend from his unencumbered output within Alice's commitment // transaction. func TestCommitmentSpendValidation(t *testing.T) { // We generate a fake output, and the corresponding txin. This output // doesn't need to exist, as we'll only be validating spending from the // transaction that references this. fundingOut := &wire.OutPoint{ Hash: testHdSeed, Index: 50, } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) // We also set up set some resources for the commitment transaction. // Each side currently has 1 BTC within the channel, with a total // channel capacity of 2BTC. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) channelBalance := btcutil.Amount(1 * 10e8) csvTimeout := uint32(5) revocationPreimage := testHdSeed[:] revokePubKey := DeriveRevocationPubkey(bobKeyPub, revocationPreimage) aliceSelfOutputSigner := &mockSigner{aliceKeyPriv} // With all the test data set up, we create the commitment transaction. // We only focus on a single party's transactions, as the scripts are // identical with the roles reversed. // // This is Alice's commitment transaction, so she must wait a CSV delay // of 5 blocks before sweeping the output, while bob can spend // immediately with either the revocation key, or his regular key. commitmentTx, err := CreateCommitTx(fakeFundingTxIn, aliceKeyPub, bobKeyPub, revokePubKey, csvTimeout, channelBalance, channelBalance) if err != nil { t.Fatalf("unable to create commitment transaction: %v", nil) } delayOutput := commitmentTx.TxOut[0] regularOutput := commitmentTx.TxOut[1] // We're testing an uncooperative close, output sweep, so construct a // transaction which sweeps the funds to a random address. targetOutput, err := commitScriptUnencumbered(aliceKeyPub) if err != nil { t.Fatalf("unable to create target output: %v") } sweepTx := wire.NewMsgTx() sweepTx.Version = 2 sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{commitmentTx.TxSha(), 0}, nil, nil)) sweepTx.AddTxOut(&wire.TxOut{ PkScript: targetOutput, Value: 0.5 * 10e8, }) // First, we'll test spending with Alice's key after the timeout. delayScript, err := commitScriptToSelf(csvTimeout, aliceKeyPub, revokePubKey) if err != nil { t.Fatalf("unable to generate alice delay script: %v") } sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout) signDesc := &SignDescriptor{ WitnessScript: delayScript, SigHashes: txscript.NewTxSigHashes(sweepTx), Output: &wire.TxOut{ Value: int64(channelBalance), }, HashType: txscript.SigHashAll, InputIndex: 0, } aliceWitnessSpend, err := CommitSpendTimeout(aliceSelfOutputSigner, signDesc, sweepTx) if err != nil { t.Fatalf("unable to generate delay commit spend witness :%v") } sweepTx.TxIn[0].Witness = aliceWitnessSpend vm, err := txscript.NewEngine(delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("spend from delay output is invalid: %v", err) } // Next, we'll test bob spending with the derived revocation key to // simulate the scenario when alice broadcasts this commitmen // transaction after it's been revoked. revokePrivKey := DeriveRevocationPrivKey(bobKeyPriv, revocationPreimage) bobWitnessSpend, err := commitSpendRevoke(delayScript, channelBalance, revokePrivKey, sweepTx) if err != nil { t.Fatalf("unable to generate revocation witness: %v", err) } sweepTx.TxIn[0].Witness = bobWitnessSpend vm, err = txscript.NewEngine(delayOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("revocation spend is invalid: %v", err) } // Finally, we test bob sweeping his output as normal in the case that // alice broadcasts this commitment transaction. bobScriptp2wkh, err := commitScriptUnencumbered(bobKeyPub) if err != nil { t.Fatalf("unable to create bob p2wkh script: %v", err) } bobRegularSpend, err := commitSpendNoDelay(bobScriptp2wkh, channelBalance, bobKeyPriv, sweepTx) if err != nil { t.Fatalf("unable to create bob regular spend: %v", err) } sweepTx.TxIn[0].Witness = bobRegularSpend vm, err = txscript.NewEngine(regularOutput.PkScript, sweepTx, 0, txscript.StandardVerifyFlags, nil, nil, int64(channelBalance)) if err != nil { t.Fatalf("unable to create engine: %v", err) } if err := vm.Execute(); err != nil { t.Fatalf("bob p2wkh spend is invalid: %v", err) } }
// validateHandler consumes items to validate from the internal validate channel // and returns the result of the validation on the internal result channel. It // must be run as a goroutine. func (v *txValidator) validateHandler() { out: for { select { case txVI := <-v.validateChan: // Ensure the referenced input transaction is available. txIn := txVI.txIn originTxHash := &txIn.PreviousOutPoint.Hash originTxIndex := txIn.PreviousOutPoint.Index txEntry := v.utxoView.LookupEntry(originTxHash) if txEntry == nil { str := fmt.Sprintf("unable to find input "+ "transaction %v referenced from "+ "transaction %v", originTxHash, txVI.tx.Hash()) err := ruleError(ErrMissingTx, str) v.sendResult(err) break out } // Ensure the referenced input transaction public key // script is available. pkScript := txEntry.PkScriptByIndex(originTxIndex) if pkScript == nil { str := fmt.Sprintf("unable to find unspent "+ "output %v script referenced from "+ "transaction %s:%d", txIn.PreviousOutPoint, txVI.tx.Hash(), txVI.txInIndex) err := ruleError(ErrBadTxInput, str) v.sendResult(err) break out } // Create a new script engine for the script pair. sigScript := txIn.SignatureScript witness := txIn.Witness inputAmount := txEntry.AmountByIndex(originTxIndex) vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(), txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes, inputAmount) if err != nil { str := fmt.Sprintf("failed to parse input "+ "%s:%d which references output %s:%d - "+ "%v (input witness %v, input script "+ "bytes %x, prev output script bytes %x)", txVI.tx.Hash(), txVI.txInIndex, originTxHash, originTxIndex, err, witness, sigScript, pkScript) err := ruleError(ErrScriptMalformed, str) v.sendResult(err) break out } // Execute the script pair. if err := vm.Execute(); err != nil { str := fmt.Sprintf("failed to validate input "+ "%s:%d which references output %s:%d - "+ "%v (input witness %v, input script "+ "bytes %x, prev output script bytes %x)", txVI.tx.Hash(), txVI.txInIndex, originTxHash, originTxIndex, err, witness, sigScript, pkScript) err := ruleError(ErrScriptValidation, str) v.sendResult(err) break out } // Validation succeeded. v.sendResult(nil) case <-v.quitChan: break out } } }
// This example demonstrates manually creating and signing a redeem transaction. func ExampleSignTxOutput() { // Ordinarily the private key would come from whatever storage mechanism // is being used, but for this example just hard code it. privKeyBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2" + "d4f8720ee63e502ee2869afab7de234b80c") if err != nil { fmt.Println(err) return } privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } // For this example, create a fake transaction that represents what // would ordinarily be the real transaction that is being spent. It // contains a single output that pays to address in the amount of 1 BTC. originTx := wire.NewMsgTx(wire.TxVersion) prevOut := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}, nil) originTx.AddTxIn(txIn) pkScript, err := txscript.PayToAddrScript(addr) if err != nil { fmt.Println(err) return } txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) originTxHash := originTx.TxHash() // Create the transaction to redeem the fake transaction. redeemTx := wire.NewMsgTx(wire.TxVersion) // Add the input(s) the redeeming transaction will spend. There is no // signature script at this point since it hasn't been created or signed // yet, hence nil is provided for it. prevOut = wire.NewOutPoint(&originTxHash, 0) txIn = wire.NewTxIn(prevOut, nil, nil) redeemTx.AddTxIn(txIn) // Ordinarily this would contain that actual destination of the funds, // but for this example don't bother. txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) // Sign the redeeming transaction. lookupKey := func(a btcutil.Address) (*btcec.PrivateKey, bool, error) { // Ordinarily this function would involve looking up the private // key for the provided address, but since the only thing being // signed in this example uses the address associated with the // private key from above, simply return it with the compressed // flag set since the address is using the associated compressed // public key. // // NOTE: If you want to prove the code is actually signing the // transaction properly, uncomment the following line which // intentionally returns an invalid key to sign with, which in // turn will result in a failure during the script execution // when verifying the signature. // // privKey.D.SetInt64(12345) // return privKey, true, nil } // Notice that the script database parameter is nil here since it isn't // used. It must be specified when pay-to-script-hash transactions are // being signed. sigScript, err := txscript.SignTxOutput(&chaincfg.MainNetParams, redeemTx, 0, originTx.TxOut[0].PkScript, txscript.SigHashAll, txscript.KeyClosure(lookupKey), nil, nil) if err != nil { fmt.Println(err) return } redeemTx.TxIn[0].SignatureScript = sigScript // Prove that the transaction has been validly signed by executing the // script pair. flags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures | txscript.ScriptStrictMultiSig | txscript.ScriptDiscourageUpgradableNops vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0, flags, nil, nil, -1) if err != nil { fmt.Println(err) return } if err := vm.Execute(); err != nil { fmt.Println(err) return } fmt.Println("Transaction successfully signed") // Output: // Transaction successfully signed }