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.
This commit is contained in:
Olaoluwa Osuntokun 2023-03-01 21:34:56 -08:00
parent 405a435a84
commit 4c7da7df49
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
5 changed files with 354 additions and 92 deletions

View file

@ -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:
// <receiver sig> <preimage> <success_script> <control_block>
@ -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:
// <receiver sig> <local sig> <timeout_script> <control_block>
@ -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:
// * <sender sig> <receiver sig> <preimage> <success_script>
@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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,
)
}
}

View file

@ -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)
}