// receiverHtlcSpendTimeout constructs a valid witness allowing the sender of // an HTLC to recover the pending funds after an absolute timeout in the // scenario that the receiver of the HTLC broadcasts their version of the // commitment transaction. func receiverHtlcSpendTimeout(commitScript []byte, outputAmt btcutil.Amount, senderKey *btcec.PrivateKey, sweepTx *wire.MsgTx, absoluteTimeout uint32) (wire.TxWitness, error) { // The HTLC output has an absolute time period before we are permitted // to recover the pending funds. Therefore we need to set the locktime // on this sweeping transaction in order to pass Script verification. sweepTx.LockTime = absoluteTimeout hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, senderKey) if err != nil { return nil, err } witnessStack := wire.TxWitness(make([][]byte, 4)) witnessStack[0] = sweepSig witnessStack[1] = []byte{0} witnessStack[2] = []byte{0} witnessStack[3] = commitScript return witnessStack, nil }
// receiverHtlcSpendRevoke constructs a valid witness allowing the sender of an // HTLC within a previously revoked commitment transaction to re-claim the // pending funds in the case that the receiver broadcasts this revoked // commitment transaction. func receiverHtlcSpendRevoke(commitScript []byte, outputAmt btcutil.Amount, senderKey *btcec.PrivateKey, sweepTx *wire.MsgTx, revokePreimage []byte) (wire.TxWitness, error) { // TODO(roasbeef): move sig generate outside func, or just factor out? hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, senderKey) if err != nil { return nil, err } // We place a zero, then one as the first items in the evaluated // witness stack in order to force script execution to the HTLC // revocation clause. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = sweepSig witnessStack[1] = revokePreimage witnessStack[2] = []byte{1} witnessStack[3] = []byte{0} witnessStack[4] = commitScript return witnessStack, nil }
// receiverHtlcSpendRedeem constructs a valid witness allowing the receiver of // an HTLC to redeem the conditional payment in the event that their commitment // transaction is broadcast. Since this is a pay out to the receiving party as // an output on their commitment transaction, a relative time delay is required // before the output can be spent. func receiverHtlcSpendRedeem(commitScript []byte, outputAmt btcutil.Amount, reciverKey *btcec.PrivateKey, sweepTx *wire.MsgTx, paymentPreimage []byte, relativeTimeout uint32) (wire.TxWitness, error) { // In order to properly spend the transaction, we need to set the // sequence number. We do this by convering the relative block delay // into a sequence number value able to be interpeted by // OP_CHECKSEQUENCEVERIFY. sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, relativeTimeout) // Additionally, OP_CSV requires that the version of the transaction // spending a pkscript with OP_CSV within it *must* be >= 2. sweepTx.Version = 2 hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, reciverKey) if err != nil { return nil, err } // Place a one as the first item in the evaluated witness stack to // force script execution to the HTLC redemption clause. witnessStack := wire.TxWitness(make([][]byte, 4)) witnessStack[0] = sweepSig witnessStack[1] = paymentPreimage witnessStack[2] = []byte{1} witnessStack[3] = commitScript return witnessStack, nil }
// signFundingTx generates signatures for all the inputs in the funding tx // belonging to Bob. // NOTE: This generates the full witness stack. func (b *bobNode) signFundingTx(fundingTx *wire.MsgTx) ([]*lnwallet.InputScript, error) { bobInputScripts := make([]*lnwallet.InputScript, 0, len(b.availableOutputs)) bobPkScript := b.changeOutputs[0].PkScript inputValue := int64(7e8) hashCache := txscript.NewTxSigHashes(fundingTx) for i, _ := range fundingTx.TxIn { // Alice has already signed this input. if fundingTx.TxIn[i].Witness != nil { continue } witness, err := txscript.WitnessScript(fundingTx, hashCache, i, inputValue, bobPkScript, txscript.SigHashAll, b.privKey, true) if err != nil { return nil, err } inputScript := &lnwallet.InputScript{Witness: witness} bobInputScripts = append(bobInputScripts, inputScript) } return bobInputScripts, nil }
// signCommitTx generates a raw signature required for generating a spend from // the funding transaction. func (b *bobNode) signCommitTx(commitTx *wire.MsgTx, fundingScript []byte, channelValue int64) ([]byte, error) { hashCache := txscript.NewTxSigHashes(commitTx) return txscript.RawTxInWitnessSignature(commitTx, hashCache, 0, channelValue, fundingScript, txscript.SigHashAll, b.privKey) }
// 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 }
// commitSpendNoDelay constructs a valid witness allowing a node to spend their // settled no-delay output on the counter-party's commitment transaction. func commitSpendNoDelay(commitScript []byte, outputAmt btcutil.Amount, commitPriv *btcec.PrivateKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) { // This is just a regular p2wkh spend which looks something like: // * witness: <sig> <pubkey> hashCache := txscript.NewTxSigHashes(sweepTx) witness, err := txscript.WitnessScript(sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, commitPriv, true) if err != nil { return nil, err } return wire.TxWitness(witness), nil }
// AddAllInputScripts modifies transaction a transaction by adding inputs // scripts for each input. Previous output scripts being redeemed by each input // are passed in prevPkScripts and the slice length must match the number of // inputs. Private keys and redeem scripts are looked up using a SecretsSource // based on the previous output script. func AddAllInputScripts(tx *wire.MsgTx, prevPkScripts [][]byte, inputValues []btcutil.Amount, secrets SecretsSource) error { inputs := tx.TxIn hashCache := txscript.NewTxSigHashes(tx) chainParams := secrets.ChainParams() if len(inputs) != len(prevPkScripts) { return errors.New("tx.TxIn and prevPkScripts slices must " + "have equal length") } for i := range inputs { pkScript := prevPkScripts[i] switch { // If this is a p2sh output, who's script hash pre-image is a // witness program, then we'll need to use a modified signing // function which generates both the sigScript, and the witness // script. case txscript.IsPayToScriptHash(pkScript): err := spendNestedWitnessPubKeyHash(inputs[i], pkScript, int64(inputValues[i]), chainParams, secrets, tx, hashCache, i) if err != nil { return err } case txscript.IsPayToWitnessPubKeyHash(pkScript): err := spendWitnessKeyHash(inputs[i], pkScript, int64(inputValues[i]), chainParams, secrets, tx, hashCache, i) if err != nil { return err } default: sigScript := inputs[i].SignatureScript script, err := txscript.SignTxOutput(chainParams, tx, i, pkScript, txscript.SigHashAll, secrets, secrets, sigScript) if err != nil { return err } inputs[i].SignatureScript = script } } return nil }
// commitSpendRevoke constructs a valid witness allowing a node to sweep the // settled output of a malicious counter-party who broadcasts a revoked // commitment trransaction. func commitSpendRevoke(commitScript []byte, outputAmt btcutil.Amount, revocationPriv *btcec.PrivateKey, sweepTx *wire.MsgTx) (wire.TxWitness, error) { hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, revocationPriv) if err != nil { return nil, err } // Place a 1 as the first item in the evaluated witness stack to // force script execution to the revocation clause. witnessStack := wire.TxWitness(make([][]byte, 3)) witnessStack[0] = sweepSig witnessStack[1] = []byte{1} witnessStack[2] = commitScript return witnessStack, nil }
// htlcSpendTimeout constructs a valid witness allowing the sender of an HTLC // to recover the pending funds after an absolute, then relative locktime // period. func senderHtlcSpendTimeout(commitScript []byte, outputAmt btcutil.Amount, senderKey *btcec.PrivateKey, sweepTx *wire.MsgTx, absoluteTimeout, relativeTimeout uint32) (wire.TxWitness, error) { // Since the HTLC output has an absolute timeout before we're permitted // to sweep the output, we need to set the locktime of this sweepign // transaction to that aboslute value in order to pass Script // verification. sweepTx.LockTime = absoluteTimeout // Additionally, we're required to wait a relative period of time // before we can sweep the output in order to allow the other party to // contest our claim of validity to this version of the commitment // transaction. sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, relativeTimeout) // Finally, OP_CSV requires that the version of the transaction // spending a pkscript with OP_CSV within it *must* be >= 2. sweepTx.Version = 2 hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, senderKey) if err != nil { return nil, err } // We place a zero as the first item of the evaluated witness stack in // order to force Script execution to the HTLC timeout clause. witnessStack := wire.TxWitness(make([][]byte, 3)) witnessStack[0] = sweepSig witnessStack[1] = []byte{0} witnessStack[2] = commitScript return witnessStack, nil }
// senderHtlcSpendRevoke constructs a valid witness allowing the reciever of an // HTLC to claim the output with knowledge of the revocation preimage in the // scenario that the sender of the HTLC broadcasts a previously revoked // commitment transaction. A valid spend requires knowledge of the pre-image to // the commitment transaction's revocation hash, and a valid signature under // the receiver's public key. func senderHtlcSpendRevoke(commitScript []byte, outputAmt btcutil.Amount, reciverKey *btcec.PrivateKey, sweepTx *wire.MsgTx, revokePreimage []byte) (wire.TxWitness, error) { hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, reciverKey) if err != nil { return nil, err } // In order to force script execution to enter the revocation clause, // we place two one's as the first items in the final evalulated // witness stack. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = sweepSig witnessStack[1] = revokePreimage witnessStack[2] = []byte{1} witnessStack[3] = []byte{1} witnessStack[4] = commitScript return witnessStack, nil }
// senderHtlcSpendRedeem constructs a valid witness allowing the receiver of an // HTLC to redeem the pending output in the scenario that the sender broadcasts // their version of the commitment transaction. A valid spend requires // knowledge of the payment pre-image, and a valid signature under the // receivers public key. func senderHtlcSpendRedeem(commitScript []byte, outputAmt btcutil.Amount, reciverKey *btcec.PrivateKey, sweepTx *wire.MsgTx, paymentPreimage []byte) (wire.TxWitness, error) { hashCache := txscript.NewTxSigHashes(sweepTx) sweepSig, err := txscript.RawTxInWitnessSignature( sweepTx, hashCache, 0, int64(outputAmt), commitScript, txscript.SigHashAll, reciverKey) if err != nil { return nil, err } // We force script execution into the HTLC redemption clause by placing // a one, then a zero as the first items in the final evalulated // witness stack. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = sweepSig witnessStack[1] = paymentPreimage witnessStack[2] = []byte{0} witnessStack[3] = []byte{1} witnessStack[4] = commitScript return witnessStack, 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 }
// handleContributionMsg processes the second workflow step for the lifetime of // a channel reservation. Upon completion, the reservation will carry a // completed funding transaction (minus the counterparty's input signatures), // both versions of the commitment transaction, and our signature for their // version of the commitment transaction. func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Create a blank, fresh transaction. Soon to be a complete funding // transaction which will allow opening a lightning channel. pendingReservation.fundingTx = wire.NewMsgTx() fundingTx := pendingReservation.fundingTx // Some temporary variables to cut down on the resolution verbosity. pendingReservation.theirContribution = req.contribution theirContribution := req.contribution ourContribution := pendingReservation.ourContribution // Add all multi-party inputs and outputs to the transaction. for _, ourInput := range ourContribution.Inputs { fundingTx.AddTxIn(ourInput) } for _, theirInput := range theirContribution.Inputs { fundingTx.AddTxIn(theirInput) } for _, ourChangeOutput := range ourContribution.ChangeOutputs { fundingTx.AddTxOut(ourChangeOutput) } for _, theirChangeOutput := range theirContribution.ChangeOutputs { fundingTx.AddTxOut(theirChangeOutput) } ourKey := pendingReservation.partialState.OurMultiSigKey theirKey := theirContribution.MultiSigKey // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. channelCapacity := int64(pendingReservation.partialState.Capacity) witnessScript, multiSigOut, err := GenFundingPkScript(ourKey.SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } pendingReservation.partialState.FundingWitnessScript = witnessScript // Sort the transaction. Since both side agree to a canonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. fundingTx.AddTxOut(multiSigOut) txsort.InPlaceSort(pendingReservation.fundingTx) // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. pendingReservation.ourFundingInputScripts = make([]*InputScript, 0, len(ourContribution.Inputs)) signDesc := SignDescriptor{ HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(fundingTx), } for i, txIn := range fundingTx.TxIn { info, err := l.FetchInputInfo(&txIn.PreviousOutPoint) if err == ErrNotMine { continue } else if err != nil { req.err <- err return } signDesc.Output = info signDesc.InputIndex = i inputScript, err := l.Signer.ComputeInputScript(fundingTx, &signDesc) if err != nil { req.err <- err return } txIn.SignatureScript = inputScript.ScriptSig txIn.Witness = inputScript.Witness pendingReservation.ourFundingInputScripts = append( pendingReservation.ourFundingInputScripts, inputScript, ) } // Locate the index of the multi-sig outpoint in order to record it // since the outputs are canonically sorted. If this is a single funder // workflow, then we'll also need to send this to the remote node. fundingTxID := fundingTx.TxSha() _, multiSigIndex := FindScriptOutputIndex(fundingTx, multiSigOut.PkScript) fundingOutpoint := wire.NewOutPoint(&fundingTxID, multiSigIndex) pendingReservation.partialState.FundingOutpoint = fundingOutpoint // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). e := &elkrem.ElkremReceiver{} pendingReservation.partialState.RemoteElkrem = e pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationKey masterElkremRoot, err := l.deriveMasterElkremRoot() if err != nil { req.err <- err return } // Now that we have their commitment key, we can create the revocation // key for the first version of our commitment transaction. To do so, // we'll first create our elkrem root, then grab the first pre-iamge // from it. elkremRoot := deriveElkremRoot(masterElkremRoot, ourKey, theirKey) elkremSender := elkrem.NewElkremSender(elkremRoot) pendingReservation.partialState.LocalElkrem = elkremSender firstPreimage, err := elkremSender.AtIndex(0) if err != nil { req.err <- err return } theirCommitKey := theirContribution.CommitKey ourRevokeKey := DeriveRevocationPubkey(theirCommitKey, firstPreimage[:]) // Create the txIn to our commitment transaction; required to construct // the commitment transactions. fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingTxID, multiSigIndex), nil, nil) // With the funding tx complete, create both commitment transactions. // TODO(roasbeef): much cleanup + de-duplication pendingReservation.fundingLockTime = theirContribution.CsvDelay ourBalance := ourContribution.FundingAmount theirBalance := theirContribution.FundingAmount ourCommitKey := ourContribution.CommitKey ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourRevokeKey, ourContribution.CsvDelay, ourBalance, theirBalance) if err != nil { req.err <- err return } theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, theirContribution.RevocationKey, theirContribution.CsvDelay, theirBalance, ourBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) deliveryScript, err := txscript.PayToAddrScript(theirContribution.DeliveryAddress) if err != nil { req.err <- err return } // Record newly available information witin the open channel state. pendingReservation.partialState.RemoteCsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryScript = deliveryScript pendingReservation.partialState.ChanID = fundingOutpoint pendingReservation.partialState.TheirCommitKey = theirCommitKey pendingReservation.partialState.TheirMultiSigKey = theirContribution.MultiSigKey pendingReservation.partialState.OurCommitTx = ourCommitTx pendingReservation.ourContribution.RevocationKey = ourRevokeKey // Generate a signature for their version of the initial commitment // transaction. signDesc = SignDescriptor{ WitnessScript: witnessScript, PubKey: ourKey, Output: multiSigOut, HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
// handleSingleFunderSigs is called once the remote peer who initiated the // single funder workflow has assembled the funding transaction, and generated // a signature for our version of the commitment transaction. This method // progresses the workflow by generating a signature for the remote peer's // version of the commitment transaction. func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { l.limboMtx.RLock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.RUnlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() pendingReservation.partialState.FundingOutpoint = req.fundingOutpoint pendingReservation.partialState.TheirCurrentRevocation = req.revokeKey pendingReservation.partialState.ChanID = req.fundingOutpoint fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil) // Now that we have the funding outpoint, we can generate both versions // of the commitment transaction, and generate a signature for the // remote node's commitment transactions. ourCommitKey := pendingReservation.ourContribution.CommitKey theirCommitKey := pendingReservation.theirContribution.CommitKey ourBalance := pendingReservation.ourContribution.FundingAmount theirBalance := pendingReservation.theirContribution.FundingAmount ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, pendingReservation.ourContribution.RevocationKey, pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance) if err != nil { req.err <- err return } theirCommitTx, err := CreateCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, req.revokeKey, pendingReservation.theirContribution.CsvDelay, theirBalance, ourBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This ensures that both parties sign the same sighash // without further synchronization. txsort.InPlaceSort(ourCommitTx) pendingReservation.partialState.OurCommitTx = ourCommitTx txsort.InPlaceSort(theirCommitTx) witnessScript := pendingReservation.partialState.FundingWitnessScript channelValue := int64(pendingReservation.partialState.Capacity) hashCache := txscript.NewTxSigHashes(ourCommitTx) theirKey := pendingReservation.theirContribution.MultiSigKey ourKey := pendingReservation.partialState.OurMultiSigKey sigHash, err := txscript.CalcWitnessSigHash(witnessScript, hashCache, txscript.SigHashAll, ourCommitTx, 0, channelValue) if err != nil { req.err <- 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(req.theirCommitmentSig, btcec.S256()) if err != nil { req.err <- err return } else if !sig.Verify(sigHash, theirKey) { req.err <- fmt.Errorf("counterparty's commitment signature is invalid") return } pendingReservation.partialState.OurCommitSig = req.theirCommitmentSig // With their signature for our version of the commitment transactions // verified, we can now generate a signature for their version, // allowing the funding transaction to be safely broadcast. p2wsh, err := witnessScriptHash(witnessScript) if err != nil { req.err <- err return } signDesc := SignDescriptor{ WitnessScript: witnessScript, PubKey: ourKey, Output: &wire.TxOut{ PkScript: p2wsh, Value: channelValue, }, HashType: txscript.SigHashAll, SigHashes: txscript.NewTxSigHashes(theirCommitTx), InputIndex: 0, } sigTheirCommit, err := l.Signer.SignOutputRaw(theirCommitTx, &signDesc) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
// SendCoins does send coins, but it's very rudimentary // wit makes it into p2wpkh. Which is not yet spendable. func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { if len(adrs) != len(sendAmts) { return fmt.Errorf("%d addresses and %d amounts", len(adrs), len(sendAmts)) } var err error var score, totalSend, fee int64 dustCutoff := int64(20000) // below this amount, just give to miners satPerByte := int64(80) // satoshis per byte fee; have as arg later rawUtxos, err := s.TS.GetAllUtxos() if err != nil { return err } var allUtxos SortableUtxoSlice // start with utxos sorted by value. for _, utxo := range rawUtxos { score += utxo.Value allUtxos = append(allUtxos, *utxo) } // smallest and unconfirmed last (because it's reversed) sort.Sort(sort.Reverse(allUtxos)) // sort.Reverse(allUtxos) for _, amt := range sendAmts { totalSend += amt } // important rule in bitcoin, output total > input total is invalid. if totalSend > score { return fmt.Errorf("trying to send %d but %d available.", totalSend, score) } tx := wire.NewMsgTx() // make new tx // add non-change (arg) outputs for i, adr := range adrs { // make address script 76a914...88ac or 0014... outAdrScript, err := txscript.PayToAddrScript(adr) if err != nil { return err } // make user specified txout and add to tx txout := wire.NewTxOut(sendAmts[i], outAdrScript) tx.AddTxOut(txout) } // generate a utxo slice for your inputs var ins utxoSlice // add utxos until we've had enough nokori := totalSend // nokori is how much is needed on input side for _, utxo := range allUtxos { // skip unconfirmed. Or de-prioritize? // if utxo.AtHeight == 0 { // continue // } // yeah, lets add this utxo! ins = append(ins, utxo) // as we add utxos, fill in sigscripts // generate previous pkscripts (subscritpt?) for all utxos // then make txins with the utxo and prevpk, and insert them into the tx // these are all zeroed out during signing but it's an easy way to keep track var prevPKs []byte if utxo.IsWit { //tx.Flags = 0x01 wa, err := btcutil.NewAddressWitnessPubKeyHash( s.TS.Adrs[utxo.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param) prevPKs, err = txscript.PayToAddrScript(wa) if err != nil { return err } } else { // otherwise generate directly prevPKs, err = txscript.PayToAddrScript( s.TS.Adrs[utxo.KeyIdx].PkhAdr) if err != nil { return err } } tx.AddTxIn(wire.NewTxIn(&utxo.Op, prevPKs, nil)) nokori -= utxo.Value // if nokori is positive, don't bother checking fee yet. if nokori < 0 { fee = EstFee(tx, satPerByte) if nokori < -fee { // done adding utxos: nokori below negative est. fee break } } } // see if there's enough left to also add a change output changeOld, err := s.TS.NewAdr() // change is witnessy if err != nil { return err } changeAdr, err := btcutil.NewAddressWitnessPubKeyHash( changeOld.ScriptAddress(), s.TS.Param) if err != nil { return err } changeScript, err := txscript.PayToAddrScript(changeAdr) if err != nil { return err } changeOut := wire.NewTxOut(0, changeScript) tx.AddTxOut(changeOut) fee = EstFee(tx, satPerByte) changeOut.Value = -(nokori + fee) if changeOut.Value < dustCutoff { // remove last output (change) : not worth it tx.TxOut = tx.TxOut[:len(tx.TxOut)-1] } // sort utxos on the input side. use this instead of txsort // because we want to remember which keys are associated with which inputs sort.Sort(ins) // sort tx -- this only will change txouts since inputs are already sorted txsort.InPlaceSort(tx) // tx is ready for signing, sigStash := make([][]byte, len(ins)) witStash := make([][][]byte, len(ins)) // generate tx-wide hashCache for segwit stuff // middle index number doesn't matter for sighashAll. hCache := txscript.NewTxSigHashes(tx) for i, txin := range tx.TxIn { // pick key child, err := s.TS.rootPrivKey.Child( ins[i].KeyIdx + hdkeychain.HardenedKeyStart) if err != nil { return err } priv, err := child.ECPrivKey() if err != nil { return err } // This is where witness based sighash types need to happen // sign into stash if ins[i].IsWit { witStash[i], err = txscript.WitnessScript( tx, hCache, i, ins[i].Value, txin.SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } else { sigStash[i], err = txscript.SignatureScript( tx, i, txin.SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } } // swap sigs into sigScripts in txins for i, txin := range tx.TxIn { if sigStash[i] != nil { txin.SignatureScript = sigStash[i] } if witStash[i] != nil { txin.Witness = witStash[i] txin.SignatureScript = nil } } // fmt.Printf("tx: %s", TxToString(tx)) // buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) // send it out on the wire. hope it gets there. // we should deal with rejects. Don't yet. err = s.NewOutgoingTx(tx) if err != nil { return err } return nil }
func (s *SPVCon) SendOne(u Utxo, adr btcutil.Address) error { // fixed fee fee := int64(5000) sendAmt := u.Value - fee tx := wire.NewMsgTx() // make new tx // add single output outAdrScript, err := txscript.PayToAddrScript(adr) if err != nil { return err } // make user specified txout and add to tx txout := wire.NewTxOut(sendAmt, outAdrScript) tx.AddTxOut(txout) var prevPKs []byte if u.IsWit { //tx.Flags = 0x01 wa, err := btcutil.NewAddressWitnessPubKeyHash( s.TS.Adrs[u.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param) prevPKs, err = txscript.PayToAddrScript(wa) if err != nil { return err } } else { // otherwise generate directly prevPKs, err = txscript.PayToAddrScript( s.TS.Adrs[u.KeyIdx].PkhAdr) if err != nil { return err } } tx.AddTxIn(wire.NewTxIn(&u.Op, prevPKs, nil)) var sig []byte var wit [][]byte hCache := txscript.NewTxSigHashes(tx) child, err := s.TS.rootPrivKey.Child(u.KeyIdx + hdkeychain.HardenedKeyStart) if err != nil { return err } priv, err := child.ECPrivKey() if err != nil { return err } // This is where witness based sighash types need to happen // sign into stash if u.IsWit { wit, err = txscript.WitnessScript( tx, hCache, 0, u.Value, tx.TxIn[0].SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } else { sig, err = txscript.SignatureScript( tx, 0, tx.TxIn[0].SignatureScript, txscript.SigHashAll, priv, true) if err != nil { return err } } // swap sigs into sigScripts in txins if sig != nil { tx.TxIn[0].SignatureScript = sig } if wit != nil { tx.TxIn[0].Witness = wit tx.TxIn[0].SignatureScript = nil } return s.NewOutgoingTx(tx) }
// 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) } }
// checkBlockScripts executes and validates the scripts for all transactions in // the passed block using multiple goroutines. func checkBlockScripts(block *btcutil.Block, utxoView *UtxoViewpoint, scriptFlags txscript.ScriptFlags, sigCache *txscript.SigCache, hashCache *txscript.HashCache) error { // First determine if segwit is active according to the scriptFlags. If // it isn't then we don't need to interact with the HashCache. segwitActive := scriptFlags&txscript.ScriptVerifyWitness == txscript.ScriptVerifyWitness // Collect all of the transaction inputs and required information for // validation for all transactions in the block into a single slice. numInputs := 0 for _, tx := range block.Transactions() { numInputs += len(tx.MsgTx().TxIn) } txValItems := make([]*txValidateItem, 0, numInputs) for _, tx := range block.Transactions() { sha := tx.Hash() // If the HashCache is present, and it doesn't yet contain the // partial sighashes for this transaction, then we add the // sighashes for the transaction. This allows us to take // advantage of the potential speed savings due to the new // digest algorithm (BIP0143). if segwitActive && hashCache != nil && !hashCache.ContainsHashes(sha) { hashCache.AddSigHashes(tx.MsgTx()) } var cachedHashes *txscript.TxSigHashes if segwitActive { if hashCache != nil { cachedHashes, _ = hashCache.GetSigHashes(sha) } else { cachedHashes = txscript.NewTxSigHashes(tx.MsgTx()) } } for txInIdx, txIn := range tx.MsgTx().TxIn { // Skip coinbases. if txIn.PreviousOutPoint.Index == math.MaxUint32 { continue } txVI := &txValidateItem{ txInIndex: txInIdx, txIn: txIn, tx: tx, sigHashes: cachedHashes, } txValItems = append(txValItems, txVI) } } // Validate all of the inputs. validator := newTxValidator(utxoView, scriptFlags, sigCache, hashCache) start := time.Now() if err := validator.Validate(txValItems); err != nil { return err } elapsed := time.Since(start) log.Tracef("block %v took %v to verify", block.Hash(), elapsed) // If the HashCache is present, once we have validated the block, we no // longer need the cached hashes for these transactions, so we purge // them from the cache. if segwitActive && hashCache != nil { for _, tx := range block.Transactions() { hashCache.PurgeSigHashes(tx.Hash()) } } return nil }
// createSweepTx creates a final sweeping transaction with all witnesses // inplace for all inputs. The created transaction has a single output sending // all the funds back to the source wallet. func (u *utxoNursery) createSweepTx(matureOutputs []*immatureOutput) (*wire.MsgTx, error) { sweepAddr, err := u.wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return nil, err } pkScript, err := txscript.PayToAddrScript(sweepAddr) if err != nil { return nil, err } var totalSum btcutil.Amount for _, o := range matureOutputs { totalSum += o.amt } sweepTx := wire.NewMsgTx() sweepTx.Version = 2 sweepTx.AddTxOut(&wire.TxOut{ PkScript: pkScript, Value: int64(totalSum - 1000), }) for _, utxo := range matureOutputs { sweepTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: utxo.outPoint, // TODO(roasbeef): assumes pure block delays Sequence: utxo.blocksToMaturity, }) } // TODO(roasbeef): insert fee calculation // * remove hardcoded fee above // With all the inputs in place, use each output's unique witness // function to generate the final witness required for spending. hashCache := txscript.NewTxSigHashes(sweepTx) for i, txIn := range sweepTx.TxIn { witness, err := matureOutputs[i].witnessFunc(sweepTx, hashCache, i) if err != nil { return nil, err } txIn.Witness = witness } return sweepTx, nil }