-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[2/?] - input: add taproot chan scripts, control block logic, and spending routines #7333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Roasbeef
merged 18 commits into
lightningnetwork:simple-taproot-chans-staging
from
Roasbeef:simple-taproot-chans-scripts
May 26, 2023
+3,145
−6
Merged
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
5f16e8e
input: add PayToTaprootScript helper func
Roasbeef f233976
input: add GenTaprootFundingScript based on musig2
Roasbeef fbdc28e
input: add TaprootCommitScriptToSelf for taproot to self script
Roasbeef 578a16a
input: add TaprootCommitScriptToRemote for taproot to remote script
Roasbeef 99066d7
input: add TaprootOutputKeyAnchor for taproot anchor outputs
Roasbeef 84b36ec
input: add tapscript utils for the sender HTLC script
Roasbeef 8eb9490
input: add spending funcs for taproot sender HTLCs
Roasbeef 5c10f28
input: add taproot script funcs for receiver HTLCs
Roasbeef 006113a
input: add spending funcs for taproot receiver HTLC ctrl blocks
Roasbeef 3e59b1b
input: add taproot second level HTLC scripts
Roasbeef 9a5151f
input: add spending funcs for second level HTLC tapscript ctrl blocks
Roasbeef 9485cc0
input: add new maybeAppendSighashType helper func
Roasbeef 110c29a
input: add exhaustive unit tests for new taproot scripts
Roasbeef 6aca853
input: restore usage of NUMS key for to_remote output
Roasbeef 37079b2
input: use script path for revocation clause for to_local output
Roasbeef 7ef2306
input: fix linter errors
Roasbeef 34a9cfb
input: use explicit CSV 1 script for to remote output
Roasbeef 388a70c
input: eliminate CSV trick for HTLC outputs
Roasbeef File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1793,6 +1793,10 @@ type CommitScriptTree struct { | |
| // SettleLeaf is the leaf used to settle the output after the delay. | ||
| SettleLeaf txscript.TapLeaf | ||
|
|
||
| // RevocationLeaf is the leaf used to spend the output with the | ||
| // revocation key signature. | ||
| RevocationLeaf txscript.TapLeaf | ||
|
|
||
| // TapscriptTree is the full tapscript tree that also includes the | ||
| // control block needed to spend each of the leaves. | ||
| TapscriptTree *txscript.IndexedTapScriptTree | ||
|
|
@@ -1808,8 +1812,6 @@ func NewLocalCommitScriptTree(csvTimeout uint32, | |
|
|
||
| // First, we'll need to construct the tapLeaf that'll be our delay CSV | ||
| // clause. | ||
| // | ||
| // TODO(roasbeef): extract into diff func | ||
| builder := txscript.NewScriptBuilder() | ||
| builder.AddData(schnorr.SerializePubKey(selfKey)) | ||
| builder.AddOp(txscript.OP_CHECKSIG) | ||
|
|
@@ -1822,10 +1824,26 @@ func NewLocalCommitScriptTree(csvTimeout uint32, | |
| return nil, err | ||
| } | ||
|
|
||
| // With the delay script computed, we'll now create a tapscript tree | ||
| // with a single leaf, and then obtain a root from that. | ||
| tapLeaf := txscript.NewBaseTapLeaf(delayScript) | ||
| tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) | ||
| // Next, we'll need to construct the revocation path, which is just a | ||
| // simple checksig script. | ||
| builder = txscript.NewScriptBuilder() | ||
| builder.AddData(schnorr.SerializePubKey(selfKey)) | ||
| builder.AddOp(txscript.OP_DROP) | ||
| builder.AddData(schnorr.SerializePubKey(revokeKey)) | ||
| builder.AddOp(txscript.OP_CHECKSIG) | ||
|
|
||
| revokeScript, err := builder.Script() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // With both scripts computed, we'll now create a tapscript tree with | ||
| // the two leaves, and then obtain a root from that. | ||
| delayTapLeaf := txscript.NewBaseTapLeaf(delayScript) | ||
| revokeTapLeaf := txscript.NewBaseTapLeaf(revokeScript) | ||
| tapScriptTree := txscript.AssembleTaprootScriptTree( | ||
| delayTapLeaf, revokeTapLeaf, | ||
| ) | ||
| tapScriptRoot := tapScriptTree.RootNode.TapHash() | ||
|
|
||
| // Now that we have our root, we can arrive at the final output script | ||
|
|
@@ -1835,16 +1853,19 @@ func NewLocalCommitScriptTree(csvTimeout uint32, | |
| ) | ||
|
|
||
| return &CommitScriptTree{ | ||
| SettleLeaf: tapLeaf, | ||
| TaprootKey: toLocalOutputKey, | ||
| TapscriptTree: tapScriptTree, | ||
| TapscriptRoot: tapScriptRoot[:], | ||
| SettleLeaf: delayTapLeaf, | ||
| RevocationLeaf: revokeTapLeaf, | ||
| 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. | ||
| // to the revocation (script path) and delay path (script path) in a single | ||
| // taproot output key. Both the delay script and the revocation script are part | ||
| // of the tapscript tree to ensure that the internal key is always revealed. | ||
| // This ensures that a 3rd party can always sweep the set of anchor outputs. | ||
| // | ||
| // For the delay path we have the following tapscript leaf script: | ||
| // | ||
|
|
@@ -1858,12 +1879,20 @@ func NewLocalCommitScriptTree(csvTimeout uint32, | |
| // Where the to_delay_script is listed above, and the delay_control_block | ||
| // computed as: | ||
| // | ||
| // delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey | ||
| // delay_control_block = (output_key_y_parity | 0xc0) || taproot_nums_key | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing the opposite leaf |
||
| // | ||
| // The revocation key spend path will simply present a valid signature with the | ||
| // witness being just: | ||
| // The revocation path is simply: | ||
| // | ||
| // <local_delayedpubkey> OP_CHECKSIG | ||
| // <revocationkey> OP_CHECKSIG | ||
| // | ||
| // The revocation path can be spent with a control block similar to the above | ||
| // (but contains the hash of the other script), and with the following witness: | ||
| // | ||
| // <revocation_sig> | ||
|
Roasbeef marked this conversation as resolved.
Outdated
|
||
| // | ||
| // We use a noop data push to ensure that the local public key is also revealed | ||
| // on chain, which enables the anchor output to be swept. | ||
| func TaprootCommitScriptToSelf(csvTimeout uint32, | ||
| selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { | ||
|
|
||
|
|
@@ -1881,7 +1910,7 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, | |
| // 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, | ||
| sweepTx *wire.MsgTx, | ||
| scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { | ||
|
|
||
| // First, we'll need to construct a valid control block to execute the | ||
|
|
@@ -1919,19 +1948,37 @@ func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, | |
| // 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) { | ||
| revokeTx *wire.MsgTx, | ||
| scriptTree *txscript.IndexedTapScriptTree) (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) | ||
| // 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, | ||
| ) | ||
|
|
||
| // With the control block created, we'll now generate the signature we | ||
| // need to authorize the spend. | ||
| revokeSig, 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) | ||
| // The final witness stack will be: | ||
| // | ||
| // <revoke sig sig> <revoke script> <control block> | ||
| 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 | ||
| } | ||
|
|
||
| return witnessStack, nil | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.