mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-12-04 18:01:57 +01:00
input: add exhaustive unit tests for new taproot scripts
This commit is contained in:
@@ -554,19 +554,24 @@ func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
|
|||||||
return txscript.NewBaseTapLeaf(successLeafScript), nil
|
return txscript.NewBaseTapLeaf(successLeafScript), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HtlcScriptTree...
|
// HtlcScriptTree holds the taproot output key, as well as the two script path
|
||||||
|
// leaves that every taproot HTLC script depends on.
|
||||||
type HtlcScriptTree struct {
|
type HtlcScriptTree struct {
|
||||||
// TaprootKey...
|
// TaprootKey is the key that will be used to generate the taproot output.
|
||||||
TaprootKey *btcec.PublicKey
|
TaprootKey *btcec.PublicKey
|
||||||
|
|
||||||
// SuccessTapLeaf...
|
// SuccessTapLeaf is the tapleaf for the redemption path.
|
||||||
SuccessTapLeaf txscript.TapLeaf
|
SuccessTapLeaf txscript.TapLeaf
|
||||||
|
|
||||||
// TimeoutTapLeaf...
|
// TimeoutTapLeaf is the tapleaf for the timeout path.
|
||||||
TimeoutTapLeaf txscript.TapLeaf
|
TimeoutTapLeaf txscript.TapLeaf
|
||||||
|
|
||||||
// TapscriptTree...
|
// TapscriptTree is the full tapscript tree that also includes the
|
||||||
|
// control block needed to spend each of the leaves.
|
||||||
TapscriptTree *txscript.IndexedTapScriptTree
|
TapscriptTree *txscript.IndexedTapScriptTree
|
||||||
|
|
||||||
|
// TapscriptTreeRoot is the root hash of the tapscript tree.
|
||||||
|
TapscriptRoot []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor
|
||||||
@@ -608,6 +613,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
SuccessTapLeaf: successTapLeaf,
|
SuccessTapLeaf: successTapLeaf,
|
||||||
TimeoutTapLeaf: timeoutTapLeaf,
|
TimeoutTapLeaf: timeoutTapLeaf,
|
||||||
TapscriptTree: tapscriptTree,
|
TapscriptTree: tapscriptTree,
|
||||||
|
TapscriptRoot: tapScriptRoot[:],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +660,7 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey,
|
|||||||
// the sighash type isn't sighash default.
|
// the sighash type isn't sighash default.
|
||||||
func maybeAppendSighash(sig Signature, sigHash txscript.SigHashType) []byte {
|
func maybeAppendSighash(sig Signature, sigHash txscript.SigHashType) []byte {
|
||||||
sigBytes := sig.Serialize()
|
sigBytes := sig.Serialize()
|
||||||
if sigHash != txscript.SigHashDefault {
|
if sigHash == txscript.SigHashDefault {
|
||||||
return sigBytes
|
return sigBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1066,8 +1072,8 @@ func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey,
|
|||||||
//
|
//
|
||||||
// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
|
// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
|
||||||
// <RIPEMD160(payment_hash)> OP_EQUALVERIFY
|
// <RIPEMD160(payment_hash)> OP_EQUALVERIFY
|
||||||
// <local_htlcpubkey> OP_CHECKSIGVERIFY
|
// <receiver_htlcpubkey> OP_CHECKSIGVERIFY
|
||||||
// <remote_htlcpubkey> OP_CHECKSIG
|
// <sender_htlcpubkey> OP_CHECKSIG
|
||||||
func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
|
func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
|
||||||
senderHtlcKey *btcec.PublicKey,
|
senderHtlcKey *btcec.PublicKey,
|
||||||
paymentHash []byte) (txscript.TapLeaf, error) {
|
paymentHash []byte) (txscript.TapLeaf, error) {
|
||||||
@@ -1115,7 +1121,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout(
|
timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout(
|
||||||
receiverHtlcKey, cltvExpiry,
|
senderHtlcKey, cltvExpiry,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1140,6 +1146,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey,
|
|||||||
SuccessTapLeaf: successTapLeaf,
|
SuccessTapLeaf: successTapLeaf,
|
||||||
TimeoutTapLeaf: timeoutTapLeaf,
|
TimeoutTapLeaf: timeoutTapLeaf,
|
||||||
TapscriptTree: tapscriptTree,
|
TapscriptTree: tapscriptTree,
|
||||||
|
TapscriptRoot: tapScriptRoot[:],
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,8 +1220,8 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature,
|
|||||||
// The final witness stack is:
|
// The final witness stack is:
|
||||||
// * <sender sig> <receiver sig> <preimage> <success_script> <control_block>
|
// * <sender sig> <receiver sig> <preimage> <success_script> <control_block>
|
||||||
witnessStack := wire.TxWitness(make([][]byte, 5))
|
witnessStack := wire.TxWitness(make([][]byte, 5))
|
||||||
witnessStack[0] = append(senderSig.Serialize(), byte(senderSigHash))
|
witnessStack[0] = maybeAppendSighash(senderSig, senderSigHash)
|
||||||
witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType))
|
witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
witnessStack[2] = paymentPreimage
|
witnessStack[2] = paymentPreimage
|
||||||
witnessStack[3] = signDesc.WitnessScript
|
witnessStack[3] = signDesc.WitnessScript
|
||||||
witnessStack[4], err = successControlBlock.ToBytes()
|
witnessStack[4], err = successControlBlock.ToBytes()
|
||||||
@@ -1266,7 +1273,7 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor,
|
|||||||
// The final witness is pretty simple, we just need to present a valid
|
// The final witness is pretty simple, we just need to present a valid
|
||||||
// signature for the script, and then provide the control block.
|
// signature for the script, and then provide the control block.
|
||||||
witnessStack := make(wire.TxWitness, 3)
|
witnessStack := make(wire.TxWitness, 3)
|
||||||
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
witnessStack[1] = signDesc.WitnessScript
|
witnessStack[1] = signDesc.WitnessScript
|
||||||
witnessStack[2], err = timeoutControlBlock.ToBytes()
|
witnessStack[2], err = timeoutControlBlock.ToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1276,6 +1283,25 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor,
|
|||||||
return witnessStack, nil
|
return witnessStack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReceiverHTLCScriptTaprootRevoke creates a valid witness needed to spend the
|
||||||
|
// revocation path of the HTLC from the PoV of the sender (offerer) of the
|
||||||
|
// HTLC. This uses a plain keyspend using the specified revocation key.
|
||||||
|
func ReceiverHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor,
|
||||||
|
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
|
||||||
|
|
||||||
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The witness stack in this case is pretty simple: we only need to
|
||||||
|
// specify the signature generated.
|
||||||
|
witnessStack := make(wire.TxWitness, 1)
|
||||||
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SecondLevelHtlcScript is the uniform script that's used as the output for
|
// SecondLevelHtlcScript is the uniform script that's used as the output for
|
||||||
// the second-level HTLC transactions. The second level transaction act as a
|
// the second-level HTLC transactions. The second level transaction act as a
|
||||||
// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
|
// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
|
||||||
@@ -1457,7 +1483,7 @@ func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor,
|
|||||||
// The witness stack in this case is pretty simple: we only need to
|
// The witness stack in this case is pretty simple: we only need to
|
||||||
// specify the signature generated.
|
// specify the signature generated.
|
||||||
witnessStack := make(wire.TxWitness, 1)
|
witnessStack := make(wire.TxWitness, 1)
|
||||||
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
|
|
||||||
return witnessStack, nil
|
return witnessStack, nil
|
||||||
}
|
}
|
||||||
@@ -1494,7 +1520,7 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
|
|||||||
//
|
//
|
||||||
// <success sig> <success script> <control_block>
|
// <success sig> <success script> <control_block>
|
||||||
witnessStack := make(wire.TxWitness, 3)
|
witnessStack := make(wire.TxWitness, 3)
|
||||||
witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType))
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
witnessStack[1] = signDesc.WitnessScript
|
witnessStack[1] = signDesc.WitnessScript
|
||||||
witnessStack[2], err = redeemControlBlock.ToBytes()
|
witnessStack[2], err = redeemControlBlock.ToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1748,30 +1774,29 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey)
|
|||||||
return builder.Script()
|
return builder.Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaprootCommitScriptToSelf creates the taproot witness program that commits
|
// CommitScript holds the taproot output key (in this case the revocation key,
|
||||||
// to the revocation (keyspend) and delay path (script path) in a single
|
// or a NUMs point for the remote output) along with the tapscript leaf that
|
||||||
// taproot output key.
|
// can spend the output after a delay.
|
||||||
//
|
type CommitScriptTree struct {
|
||||||
// For the delay path we have the following tapscript leaf script:
|
// TaprootKey is the key that will be used to generate the taproot
|
||||||
//
|
// output.
|
||||||
// <local_delayedpubkey> OP_CHECKSIG
|
TaprootKey *btcec.PublicKey
|
||||||
// <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
|
|
||||||
//
|
// SettleLeaf is the leaf used to settle the output after the delay.
|
||||||
// This can then be spent with just:
|
SettleLeaf txscript.TapLeaf
|
||||||
//
|
|
||||||
// <local_delayedsig> <to_delay_script> <delay_control_block>
|
// TapscriptTree is the full tapscript tree that also includes the
|
||||||
//
|
// control block needed to spend each of the leaves.
|
||||||
// Where the to_delay_script is listed above, and the delay_control_block
|
TapscriptTree *txscript.IndexedTapScriptTree
|
||||||
// computed as:
|
|
||||||
//
|
// TapscriptTreeRoot is the root hash of the tapscript tree.
|
||||||
// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey
|
TapscriptRoot []byte
|
||||||
//
|
}
|
||||||
// The revocation key spend path will simply present a valid signature with the
|
|
||||||
// witness being just:
|
// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to
|
||||||
//
|
// create and spend the commitment output for the local party.
|
||||||
// <revocation_sig>
|
func NewLocalCommitScriptTree(csvTimeout uint32,
|
||||||
func TaprootCommitScriptToSelf(csvTimeout uint32,
|
selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) {
|
||||||
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
|
||||||
|
|
||||||
// First, we'll need to construct the tapLeaf that'll be our delay CSV
|
// First, we'll need to construct the tapLeaf that'll be our delay CSV
|
||||||
// clause.
|
// clause.
|
||||||
@@ -1801,7 +1826,104 @@ func TaprootCommitScriptToSelf(csvTimeout uint32,
|
|||||||
revokeKey, tapScriptRoot[:],
|
revokeKey, tapScriptRoot[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
return toLocalOutputKey, nil
|
return &CommitScriptTree{
|
||||||
|
SettleLeaf: tapLeaf,
|
||||||
|
TaprootKey: toLocalOutputKey,
|
||||||
|
TapscriptTree: tapScriptTree,
|
||||||
|
TapscriptRoot: tapScriptRoot[:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootCommitScriptToSelf creates the taproot witness program that commits
|
||||||
|
// to the revocation (keyspend) and delay path (script path) in a single
|
||||||
|
// taproot output key.
|
||||||
|
//
|
||||||
|
// For the delay path we have the following tapscript leaf script:
|
||||||
|
//
|
||||||
|
// <local_delayedpubkey> OP_CHECKSIG
|
||||||
|
// <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||||
|
//
|
||||||
|
// This can then be spent with just:
|
||||||
|
//
|
||||||
|
// <local_delayedsig> <to_delay_script> <delay_control_block>
|
||||||
|
//
|
||||||
|
// Where the to_delay_script is listed above, and the delay_control_block
|
||||||
|
// computed as:
|
||||||
|
//
|
||||||
|
// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey
|
||||||
|
//
|
||||||
|
// The revocation key spend path will simply present a valid signature with the
|
||||||
|
// witness being just:
|
||||||
|
//
|
||||||
|
// <revocation_sig>
|
||||||
|
func TaprootCommitScriptToSelf(csvTimeout uint32,
|
||||||
|
selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
||||||
|
|
||||||
|
commitScriptTree, err := NewLocalCommitScriptTree(
|
||||||
|
csvTimeout, selfKey, revokeKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitScriptTree.TaprootKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootCommitSpendSuccess constructs a valid witness allowing a node to
|
||||||
|
// sweep the settled taproot output after the delay has passed for a force
|
||||||
|
// close.
|
||||||
|
func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor,
|
||||||
|
sweepTx *wire.MsgTx, revokeKey *btcec.PublicKey,
|
||||||
|
scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
|
||||||
|
|
||||||
|
// 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(revokeKey)
|
||||||
|
|
||||||
|
// With the control block created, we'll now generate the signature we
|
||||||
|
// need to authorize the spend.
|
||||||
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final witness stack will be:
|
||||||
|
//
|
||||||
|
// <sweep sig> <sweep script> <control block>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootCommitSpendRevoke constructs a valid witness allowing a node to sweep
|
||||||
|
// the revoked taproot output of a malicious peer.
|
||||||
|
func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor,
|
||||||
|
revokeTx *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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The witness stack in this case is pretty simple: we only need to
|
||||||
|
// specify the signature generated.
|
||||||
|
witnessStack := make(wire.TxWitness, 1)
|
||||||
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeaseCommitScriptToSelf constructs the public key script for the output on the
|
// LeaseCommitScriptToSelf constructs the public key script for the output on the
|
||||||
@@ -2018,26 +2140,10 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
|
|||||||
return builder.Script()
|
return builder.Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaprootCommitScriptToRemote constructs a taproot witness program for the
|
// NewRemoteCommitScriptTree constructs a new script tree for the remote party
|
||||||
// output on the commitment transaction for the remote party. For the top level
|
// to sweep their funds after a hard coded 1 block delay.
|
||||||
// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a
|
func NewRemoteCommitScriptTree(numsKey,
|
||||||
// sort of practical NUMs point (the local party would never sign for this). We
|
remoteKey *btcec.PublicKey) (*CommitScriptTree, error) {
|
||||||
// then commit to a single tapscript leaf that holds the normal CSV 1 delay
|
|
||||||
// script.
|
|
||||||
//
|
|
||||||
// Our single tapleaf will use the following script:
|
|
||||||
//
|
|
||||||
// <remotepubkey> OP_CHECKSIG
|
|
||||||
// OP_CHECKSEQUENCEVERIFY
|
|
||||||
//
|
|
||||||
// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it
|
|
||||||
// succeeds, which then enforces our 1 CSV. The true will remain on the stack,
|
|
||||||
// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on
|
|
||||||
// the stack.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): double check here can't pass additional stack elements?
|
|
||||||
func TaprootCommitScriptToRemote(combinedFundingKey,
|
|
||||||
remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
|
||||||
|
|
||||||
// First, construct the remote party's tapscript they'll use to sweep their
|
// First, construct the remote party's tapscript they'll use to sweep their
|
||||||
// outputs.
|
// outputs.
|
||||||
@@ -2060,10 +2166,82 @@ func TaprootCommitScriptToRemote(combinedFundingKey,
|
|||||||
// Now that we have our root, we can arrive at the final output script
|
// Now that we have our root, we can arrive at the final output script
|
||||||
// by tweaking the internal key with this root.
|
// by tweaking the internal key with this root.
|
||||||
toRemoteOutputKey := txscript.ComputeTaprootOutputKey(
|
toRemoteOutputKey := txscript.ComputeTaprootOutputKey(
|
||||||
combinedFundingKey, tapScriptRoot[:],
|
numsKey, tapScriptRoot[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
return toRemoteOutputKey, nil
|
return &CommitScriptTree{
|
||||||
|
TaprootKey: toRemoteOutputKey,
|
||||||
|
SettleLeaf: tapLeaf,
|
||||||
|
TapscriptTree: tapScriptTree,
|
||||||
|
TapscriptRoot: tapScriptRoot[:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootCommitScriptToRemote constructs a taproot witness program for the
|
||||||
|
// output on the commitment transaction for the remote party. For the top level
|
||||||
|
// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a
|
||||||
|
// sort of practical NUMs point (the local party would never sign for this). We
|
||||||
|
// then commit to a single tapscript leaf that holds the normal CSV 1 delay
|
||||||
|
// script.
|
||||||
|
//
|
||||||
|
// Our single tapleaf will use the following script:
|
||||||
|
//
|
||||||
|
// <remotepubkey> OP_CHECKSIG
|
||||||
|
// OP_CHECKSEQUENCEVERIFY
|
||||||
|
//
|
||||||
|
// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it
|
||||||
|
// succeeds, which then enforces our 1 CSV. The true will remain on the stack,
|
||||||
|
// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on
|
||||||
|
// the stack.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): double check here can't pass additional stack elements?
|
||||||
|
func TaprootCommitScriptToRemote(combinedFundingKey,
|
||||||
|
remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) {
|
||||||
|
|
||||||
|
commitScriptTree, err := NewRemoteCommitScriptTree(
|
||||||
|
combinedFundingKey, remoteKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return commitScriptTree.TaprootKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootCommitRemoteSpend allows the remote party to sweep their output into
|
||||||
|
// their wallet after an enforced 1 block delay.
|
||||||
|
func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor,
|
||||||
|
sweepTx *wire.MsgTx, numsKey *btcec.PublicKey,
|
||||||
|
scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) {
|
||||||
|
|
||||||
|
// 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(numsKey)
|
||||||
|
|
||||||
|
// With the control block created, we'll now generate the signature we
|
||||||
|
// need to authorize the spend.
|
||||||
|
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final witness stack will be:
|
||||||
|
//
|
||||||
|
// <sweep sig> <sweep script> <control block>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeaseCommitScriptToRemoteConfirmed constructs the script for the output on
|
// LeaseCommitScriptToRemoteConfirmed constructs the script for the output on
|
||||||
@@ -2164,15 +2342,29 @@ func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
|
|||||||
return builder.Script()
|
return builder.Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that
|
// AnchorScriptTree holds all the contents needed to to sweep a taproot anchor
|
||||||
// encodes the anchor output spending conditions: the passed key can be used
|
// output on chain.
|
||||||
// for keyspend, with the OP_CSV 16 clause living within an internal tapscript
|
|
||||||
// leaf.
|
|
||||||
//
|
//
|
||||||
// Spend paths:
|
// TODO(roasbeef): refactor trees to reduce dedup
|
||||||
// - Key spend: <key_signature>
|
type AnchorScriptTree struct {
|
||||||
// - Script spend: OP_16 CSV <control_block>
|
// TaprootKey is the key that will be used to generate the taproot
|
||||||
func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) {
|
// output.
|
||||||
|
TaprootKey *btcec.PublicKey
|
||||||
|
|
||||||
|
// SweepLeaf is the leaf used to settle the output after the delay.
|
||||||
|
SweepLeaf txscript.TapLeaf
|
||||||
|
|
||||||
|
// TapscriptTree is the full tapscript tree that also includes the
|
||||||
|
// control block needed to spend each of the leaves.
|
||||||
|
TapscriptTree *txscript.IndexedTapScriptTree
|
||||||
|
|
||||||
|
// TapscriptTreeRoot is the root hash of the tapscript tree.
|
||||||
|
TapscriptRoot []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnchorScriptTree makes a new script tree for an anchor output with the
|
||||||
|
// passed anchor key.
|
||||||
|
func NewAnchorScriptTree(anchorKey *btcec.PublicKey) (*AnchorScriptTree, error) {
|
||||||
// The main script used is just a OP_16 CSV (anyone can sweep after 16
|
// The main script used is just a OP_16 CSV (anyone can sweep after 16
|
||||||
// blocks).
|
// blocks).
|
||||||
builder := txscript.NewScriptBuilder()
|
builder := txscript.NewScriptBuilder()
|
||||||
@@ -2192,11 +2384,83 @@ func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) {
|
|||||||
|
|
||||||
// Now that we have our root, we can arrive at the final output script
|
// Now that we have our root, we can arrive at the final output script
|
||||||
// by tweaking the internal key with this root.
|
// by tweaking the internal key with this root.
|
||||||
anchorKey := txscript.ComputeTaprootOutputKey(
|
anchorOutputKey := txscript.ComputeTaprootOutputKey(
|
||||||
key, tapScriptRoot[:],
|
anchorKey, tapScriptRoot[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
return anchorKey, nil
|
return &AnchorScriptTree{
|
||||||
|
TaprootKey: anchorOutputKey,
|
||||||
|
SweepLeaf: tapLeaf,
|
||||||
|
TapscriptTree: tapScriptTree,
|
||||||
|
TapscriptRoot: tapScriptRoot[:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that
|
||||||
|
// encodes the anchor output spending conditions: the passed key can be used
|
||||||
|
// for keyspend, with the OP_CSV 16 clause living within an internal tapscript
|
||||||
|
// leaf.
|
||||||
|
//
|
||||||
|
// Spend paths:
|
||||||
|
// - Key spend: <key_signature>
|
||||||
|
// - Script spend: OP_16 CSV <control_block>
|
||||||
|
func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) {
|
||||||
|
anchorScriptTree, err := NewAnchorScriptTree(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchorScriptTree.TaprootKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The witness stack in this case is pretty simple: we only need to
|
||||||
|
// specify the signature generated.
|
||||||
|
witnessStack := make(wire.TxWitness, 1)
|
||||||
|
witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType)
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaprootAnchorSpendAny constructs a valid witness allowing anyone to sweep
|
||||||
|
// the anchor output after 16 blocks.
|
||||||
|
func TaprootAnchorSpendAny(anchorKey *btcec.PublicKey) (wire.TxWitness, error) {
|
||||||
|
anchorScriptTree, err := NewAnchorScriptTree(anchorKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For this spend, the only thing we need to do is create a valid
|
||||||
|
// control block. Other than that, there're no restrictions to how the
|
||||||
|
// output can be spent.
|
||||||
|
scriptTree := anchorScriptTree.TapscriptTree
|
||||||
|
sweepLeaf := anchorScriptTree.SweepLeaf
|
||||||
|
sweepIdx := scriptTree.LeafProofIndex[sweepLeaf.TapHash()]
|
||||||
|
sweepMerkleProof := scriptTree.LeafMerkleProofs[sweepIdx]
|
||||||
|
sweepControlBlock := sweepMerkleProof.ToControlBlock(anchorKey)
|
||||||
|
|
||||||
|
// The final witness stack will be:
|
||||||
|
//
|
||||||
|
// <sweep script> <control block>
|
||||||
|
witnessStack := make(wire.TxWitness, 2)
|
||||||
|
witnessStack[0] = sweepLeaf.Script
|
||||||
|
witnessStack[1], err = sweepControlBlock.ToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return witnessStack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitSpendAnchor constructs a valid witness allowing a node to spend their
|
// CommitSpendAnchor constructs a valid witness allowing a node to spend their
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("stepping (%v)\n", err)
|
t.Fatalf("stepping (%v)\n", err)
|
||||||
}
|
}
|
||||||
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
|
debugBuf.WriteString(fmt.Sprintf("Stepping %v\n", dis))
|
||||||
|
|
||||||
done, err = vm.Step()
|
done, err = vm.Step()
|
||||||
if err != nil && valid {
|
if err != nil && valid {
|
||||||
@@ -65,8 +65,11 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool,
|
|||||||
"should be invalid: %v", testNum, err)
|
"should be invalid: %v", testNum, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
|
debugBuf.WriteString(fmt.Sprintf("Stack: %v\n",
|
||||||
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
|
vm.GetStack()))
|
||||||
|
debugBuf.WriteString(fmt.Sprintf("AltStack: %v\n",
|
||||||
|
vm.GetAltStack()))
|
||||||
|
debugBuf.WriteString("-----\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get to this point the unexpected case was not reached
|
// If we get to this point the unexpected case was not reached
|
||||||
|
|||||||
1877
input/taproot_test.go
Normal file
1877
input/taproot_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -72,9 +72,70 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx,
|
|||||||
return nil, fmt.Errorf("mock signer does not have key")
|
return nil, fmt.Errorf("mock signer does not have key")
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
|
// In case of a taproot output any signature is always a Schnorr
|
||||||
signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript,
|
// signature, based on the new tapscript sighash algorithm.
|
||||||
signDesc.HashType, privKey)
|
//
|
||||||
|
// TODO(roasbeef): should conslidate with btcwallet/signer.go
|
||||||
|
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
|
||||||
|
sigHashes := txscript.NewTxSigHashes(
|
||||||
|
tx, signDesc.PrevOutputFetcher,
|
||||||
|
)
|
||||||
|
|
||||||
|
witnessScript := signDesc.WitnessScript
|
||||||
|
|
||||||
|
// Are we spending a script path or the key path? The API is
|
||||||
|
// slightly different, so we need to account for that to get the
|
||||||
|
// raw signature.
|
||||||
|
var (
|
||||||
|
rawSig []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch signDesc.SignMethod {
|
||||||
|
case TaprootKeySpendBIP0086SignMethod,
|
||||||
|
TaprootKeySpendSignMethod:
|
||||||
|
|
||||||
|
// This function tweaks the private key using the tap
|
||||||
|
// root key supplied as the tweak.
|
||||||
|
rawSig, err = txscript.RawTxInTaprootSignature(
|
||||||
|
tx, sigHashes, signDesc.InputIndex,
|
||||||
|
signDesc.Output.Value, signDesc.Output.PkScript,
|
||||||
|
signDesc.TapTweak, signDesc.HashType,
|
||||||
|
privKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case TaprootScriptSpendSignMethod:
|
||||||
|
leaf := txscript.TapLeaf{
|
||||||
|
LeafVersion: txscript.BaseLeafVersion,
|
||||||
|
Script: witnessScript,
|
||||||
|
}
|
||||||
|
rawSig, err = txscript.RawTxInTapscriptSignature(
|
||||||
|
tx, sigHashes, signDesc.InputIndex,
|
||||||
|
signDesc.Output.Value, signDesc.Output.PkScript,
|
||||||
|
leaf, signDesc.HashType, privKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := schnorr.ParseSignature(
|
||||||
|
rawSig[:schnorr.SignatureSize],
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := txscript.RawTxInWitnessSignature(
|
||||||
|
tx, signDesc.SigHashes, signDesc.InputIndex,
|
||||||
|
signDesc.Output.Value, signDesc.WitnessScript,
|
||||||
|
signDesc.HashType, privKey,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user