Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2/?] - input: add taproot chan scripts, control block logic, and spending routines #7333

Merged
Show file tree
Hide file tree
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 Jan 17, 2023
f233976
input: add GenTaprootFundingScript based on musig2
Roasbeef Jan 17, 2023
fbdc28e
input: add TaprootCommitScriptToSelf for taproot to self script
Roasbeef Jan 17, 2023
578a16a
input: add TaprootCommitScriptToRemote for taproot to remote script
Roasbeef Jan 17, 2023
99066d7
input: add TaprootOutputKeyAnchor for taproot anchor outputs
Roasbeef Jan 17, 2023
84b36ec
input: add tapscript utils for the sender HTLC script
Roasbeef Jan 17, 2023
8eb9490
input: add spending funcs for taproot sender HTLCs
Roasbeef Jan 17, 2023
5c10f28
input: add taproot script funcs for receiver HTLCs
Roasbeef Jan 17, 2023
006113a
input: add spending funcs for taproot receiver HTLC ctrl blocks
Roasbeef Jan 17, 2023
3e59b1b
input: add taproot second level HTLC scripts
Roasbeef Jan 17, 2023
9a5151f
input: add spending funcs for second level HTLC tapscript ctrl blocks
Roasbeef Jan 17, 2023
9485cc0
input: add new maybeAppendSighashType helper func
Roasbeef Jan 25, 2023
110c29a
input: add exhaustive unit tests for new taproot scripts
Roasbeef Feb 4, 2023
6aca853
input: restore usage of NUMS key for to_remote output
Roasbeef May 23, 2023
37079b2
input: use script path for revocation clause for to_local output
Roasbeef May 24, 2023
7ef2306
input: fix linter errors
Roasbeef May 24, 2023
34a9cfb
input: use explicit CSV 1 script for to remote output
Roasbeef May 25, 2023
388a70c
input: eliminate CSV trick for HTLC outputs
Roasbeef May 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 71 additions & 24 deletions input/script_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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.
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
// 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:
//
Expand All @@ -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
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be OP_DROP.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not fixed yet, can be confusing for future development.

// <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.
Show resolved Hide resolved
//
// 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) {

Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions input/taproot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,6 @@ func localCommitSweepWitGen(sigHash txscript.SigHashType,

return TaprootCommitSpendSuccess(
signer, signDesc, spendTx,
commitScriptTree.revokeKey.PubKey(),
commitScriptTree.TapscriptTree,
)
}
Expand All @@ -1000,17 +999,18 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType,
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
WitnessScript: commitScriptTree.RevocationLeaf.Script,
Output: commitScriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootKeySpendSignMethod,
TapTweak: commitScriptTree.TapscriptRoot,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}

return TaprootCommitSpendRevoke(
signer, signDesc, spendTx,
commitScriptTree.TapscriptTree,
)
}
}
Expand Down