From 4c7da7df491a220328f9af89d45e601331fcbd7b Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 1 Mar 2023 21:34:56 -0800 Subject: [PATCH] input: update all taproot script spends to optionally make the ctrl block In this commit, we update all the taproot scripts spends to optionally make the control block. This is useful in cases where we've already created the control block, or may not have the items needed to construct it in the first place. We also add the control block to the sign descriptor itself. --- input/script_utils.go | 232 +++++++++++++++++++++++++--------------- input/signdescriptor.go | 9 ++ input/size_test.go | 2 +- input/taproot_test.go | 4 +- input/witnessgen.go | 199 ++++++++++++++++++++++++++++++++++ 5 files changed, 354 insertions(+), 92 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index 5054c57fe..7e1c4efb4 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -665,7 +665,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key) // for an outgoing HTLC on the sender's version of the commitment transaction. // This method returns the top level tweaked public key that commits to both -// the script paths. +// the script paths. This is also known as an offered HTLC. // // The returned key commits to a tapscript tree with two possible paths: // @@ -727,12 +727,21 @@ func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - successTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] - successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] - successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + successControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + + ctrlBytes, err := successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlock = ctrlBytes + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness stack is: // @@ -740,10 +749,7 @@ func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = preimage witnessStack[2] = signDesc.WitnessScript - witnessStack[3], err = successControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[3] = ctrlBlock return witnessStack, nil } @@ -765,12 +771,20 @@ func SenderHTLCScriptTaprootTimeout(receiverSig Signature, // With the sweep signature obtained, we'll obtain the control block // proof needed to perform a valid spend for the timeout path. - timeoutTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] - timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] - timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + timeoutControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBytes, err := timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // The final witness stack is: // @@ -778,10 +792,7 @@ func SenderHTLCScriptTaprootTimeout(receiverSig Signature, witnessStack[0] = maybeAppendSighash(receiverSig, receiverSigHash) witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[2] = signDesc.WitnessScript - witnessStack[3], err = timeoutControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[3] = ctrlBlockBytes return witnessStack, nil } @@ -1200,7 +1211,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, // ReceiverHTLCScriptTaproot constructs the taproot witness program (schnor // key) for an incoming HTLC on the receiver's version of the commitment // transaction. This method returns the top level tweaked public key that -// commits to both the script paths. From the PoV for the receiver, this is an +// commits to both the script paths. From the PoV of the receiver, this is an // accepted HTLC. // // The returned key commits to a tapscript tree with two possible paths: @@ -1257,12 +1268,20 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - successTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] - successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] - successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + redeemControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBytes, err := redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlock = ctrlBytes + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness stack is: // * @@ -1272,10 +1291,7 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[2] = paymentPreimage witnessStack[3] = signDesc.WitnessScript - witnessStack[4], err = successControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[4] = ctrlBlock return witnessStack, nil } @@ -1311,22 +1327,25 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, // In addition to the signature and the witness/leaf script, we also // need to make a control block proof using the tapscript tree. - timeoutTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] - timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] - timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + timeoutControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + ctrlBlock, err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + } else { + ctrlBlock = signDesc.ControlBlock + } // The final witness is pretty simple, we just need to present a valid // signature for the script, and then provide the control block. witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = timeoutControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlock return witnessStack, nil } @@ -1542,7 +1561,7 @@ func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, // NOTE: The caller MUST set the txn version, sequence number, and sign // descriptor's sig hash cache before invocation. func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, - revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx, + sweepTx *wire.MsgTx, revokeKey *btcec.PublicKey, tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { // First, we'll generate the sweep signature based on the populated @@ -1553,14 +1572,21 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, return nil, err } - // Now that we have the sweep signature, we'll construct the control - // block needed to spend the script path. - redeemTapLeafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - redeemIdx := tapscriptTree.LeafProofIndex[redeemTapLeafHash] - redeemMerkleProof := tapscriptTree.LeafMerkleProofs[redeemIdx] - redeemControlBlock := redeemMerkleProof.ToControlBlock(revokeKey) + var ctrlBlock []byte + if signDesc.ControlBlock == nil { + // Now that we have the sweep signature, we'll construct the control + // block needed to spend the script path. + redeemControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, revokeKey, tapscriptTree, + ) + + ctrlBlock, err = redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + } else { + ctrlBlock = signDesc.ControlBlock + } // Now that we have the redeem control block, we can construct the // final witness needed to spend the script: @@ -1569,10 +1595,7 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = redeemControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlock return witnessStack, nil } @@ -1946,6 +1969,19 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, return commitScriptTree.TaprootKey, nil } +// MakeTaprootSCtrlBlock takes a leaf script, the internal key (usually the +// revoke key), and a script tree and creates a valid control block for a spend +// of the leaf. +func MakeTaprootCtrlBlock(leafScript []byte, internalKey *btcec.PublicKey, + scriptTree *txscript.IndexedTapScriptTree) txscript.ControlBlock { + + tapLeafHash := txscript.NewBaseTapLeaf(leafScript).TapHash() + scriptIdx := scriptTree.LeafProofIndex[tapLeafHash] + settleMerkleProof := scriptTree.LeafMerkleProofs[scriptIdx] + + return settleMerkleProof.ToControlBlock(internalKey) +} + // TaprootCommitSpendSuccess constructs a valid witness allowing a node to // sweep the settled taproot output after the delay has passed for a force // close. @@ -1955,14 +1991,24 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for sweep settlement. - settleTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] - settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] - settleControlBlock := settleMerkleProof.ToControlBlock( - &TaprootNUMSKey, - ) + // + // TODO(roasbeef); make into closure instead? only need reovke key and + // scriptTree to make the ctrl block -- then default version that would + // take froms ign desc? + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + settleControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + ctrlBytes, err := settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -1977,7 +2023,7 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = settleControlBlock.ToBytes() + witnessStack[2] = ctrlBlockBytes if err != nil { return nil, err } @@ -1993,14 +2039,20 @@ func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for revocation path. - revokeTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - revokeIdx := scriptTree.LeafProofIndex[revokeTapleafHash] - revokeMerkleProof := scriptTree.LeafMerkleProofs[revokeIdx] - revokeControlBlock := revokeMerkleProof.ToControlBlock( - &TaprootNUMSKey, - ) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + revokeCtrlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + revokeBytes, err := revokeCtrlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = revokeBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -2015,10 +2067,7 @@ func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(revokeSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = revokeControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlockBytes return witnessStack, nil } @@ -2307,12 +2356,20 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, // First, we'll need to construct a valid control block to execute the // leaf script for sweep settlement. - settleTapleafHash := txscript.NewBaseTapLeaf( - signDesc.WitnessScript, - ).TapHash() - settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] - settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] - settleControlBlock := settleMerkleProof.ToControlBlock(&TaprootNUMSKey) + var ctrlBlockBytes []byte + if signDesc.ControlBlock == nil { + settleControlBlock := MakeTaprootCtrlBlock( + signDesc.WitnessScript, &TaprootNUMSKey, scriptTree, + ) + ctrlBytes, err := settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + ctrlBlockBytes = ctrlBytes + } else { + ctrlBlockBytes = signDesc.ControlBlock + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -2327,10 +2384,7 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, witnessStack := make(wire.TxWitness, 3) witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript - witnessStack[2], err = settleControlBlock.ToBytes() - if err != nil { - return nil, err - } + witnessStack[2] = ctrlBlockBytes return witnessStack, nil } @@ -2507,11 +2561,11 @@ func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { // TaprootAnchorSpend constructs a valid witness allowing a node to sweep their // anchor output. func TaprootAnchorSpend(signer Signer, signDesc *SignDescriptor, - revokeTx *wire.MsgTx) (wire.TxWitness, error) { + sweepTx *wire.MsgTx) (wire.TxWitness, error) { // For this spend type, we only need a single signature which'll be a - // keyspend using the revoke private key. - sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + // keyspend using the anchor private key. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) if err != nil { return nil, err } diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 17008288b..3ed18cf11 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -98,6 +98,13 @@ type SignDescriptor struct { // This MUST be set when spending Taproot outputs. PrevOutputFetcher txscript.PrevOutputFetcher + // ControlBlock is a fully serialized control block that contains the + // merkle proof necessary to spend a taproot output. This may + // optionally be set if the SignMethod is + // input.TaprootScriptSpendSignMethod. In which case, this should be an + // inclusion proof for the WitnessScript. + ControlBlock []byte + // InputIndex is the target input within the transaction that should be // signed. InputIndex int @@ -218,6 +225,8 @@ func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { return err } + // TODO(roasbeef): also write ctrl block? + return nil } diff --git a/input/size_test.go b/input/size_test.go index b8d819f29..f1a65963e 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -998,7 +998,7 @@ var witnessSizeTests = []witnessSizeTest{ } witness, err := input.TaprootHtlcSpendSuccess( - signer, signDesc, revokeKey.PubKey(), testTx, + signer, signDesc, testTx, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) diff --git a/input/taproot_test.go b/input/taproot_test.go index b9e4b28be..3a0f85d41 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -1675,8 +1675,8 @@ func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType, } return TaprootHtlcSpendSuccess( - signer, signDesc, scriptTree.revokeKey.PubKey(), - spendTx, scriptTree.scriptTree, + signer, signDesc, spendTx, + scriptTree.revokeKey.PubKey(), scriptTree.scriptTree, ) } } diff --git a/input/witnessgen.go b/input/witnessgen.go index 72c518d9b..4bb217052 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -515,6 +515,205 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) + case TaprootLocalCommitSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootCommitSpendSuccess( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootRemoteCommitSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootCommitRemoteSpend( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootAnchorSweepSpend: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := TaprootAnchorSpend( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedTimeoutSecondLevel, + TaprootHtlcAcceptedSuccessSecondLevel: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := TaprootHtlcSpendSuccess( + signer, desc, tx, nil, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcSecondLevelRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := TaprootHtlcSpendRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := SenderHTLCScriptTaprootRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcAcceptedRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootKeySpendSignMethod + + // The tap tweak must be set at this point. + if desc.TapTweak == nil { + return nil, fmt.Errorf("tap tweak must be " + + "set for keyspend") + } + + witness, err := ReceiverHTLCScriptTaprootRevoke( + signer, desc, tx, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootHtlcOfferedRemoteTimeout: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block must be " + + "set for taproot spend") + } + + witness, err := ReceiverHTLCScriptTaprootTimeout( + signer, desc, tx, -1, nil, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + + case TaprootCommitmentRevoke: + // Ensure that the sign desc has the proper sign method + // set, and a valid prev output fetcher. + desc.SignMethod = TaprootScriptSpendSignMethod + + // The control block bytes must be set at this point. + if desc.ControlBlock == nil { + return nil, fmt.Errorf("control block " + + "must be set for taproot spend") + } + + witness, err := TaprootCommitSpendRevoke( + signer, desc, tx, nil, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil + default: return nil, fmt.Errorf("unknown witness type: %v", wt) }