From e83d2186adca0bf6fa78269cc98defa3366ea9db Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 31 May 2023 07:37:04 +0200 Subject: [PATCH] channeldb+input [temp]: add required Taproot variables --- channeldb/channel.go | 9 ++++ input/script_utils.go | 108 +++++++++++++++++++++++++--------------- input/signdescriptor.go | 7 +++ input/size.go | 41 +++++++++++++++ input/taproot_test.go | 4 +- input/witnessgen.go | 58 +++++++++++++++++++++ 6 files changed, 186 insertions(+), 41 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index d71f17047d..6b623c5592 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -295,8 +295,17 @@ const ( // ScidAliasFeatureBit indicates that the scid-alias feature bit was // negotiated during the lifetime of this channel. ScidAliasFeatureBit ChannelType = 1 << 9 + + // SimpleTaprootFeatureBit indicates that the simple taproot channel + // feature bit was negotiated during the lifetime of this channel. + SimpleTaprootFeatureBit ChannelType = 1 << 10 ) +// IsTaproot returns true if the channel is using taproot features. +func (c ChannelType) IsTaproot() bool { + return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit +} + // IsSingleFunder returns true if the channel type if one of the known single // funder variants. func (c ChannelType) IsSingleFunder() bool { diff --git a/input/script_utils.go b/input/script_utils.go index 2b18780dbc..0e42aee513 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -1811,13 +1811,9 @@ type CommitScriptTree struct { TapscriptRoot []byte } -// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to -// create and spend the commitment output for the local party. -func NewLocalCommitScriptTree(csvTimeout uint32, - selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) { +func NewLocalCommitDelayScript(csvTimeout uint32, + selfKey *btcec.PublicKey) ([]byte, error) { - // First, we'll need to construct the tapLeaf that'll be our delay CSV - // clause. builder := txscript.NewScriptBuilder() builder.AddData(schnorr.SerializePubKey(selfKey)) builder.AddOp(txscript.OP_CHECKSIG) @@ -1825,20 +1821,36 @@ func NewLocalCommitScriptTree(csvTimeout uint32, builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) builder.AddOp(txscript.OP_DROP) - delayScript, err := builder.Script() - if err != nil { - return nil, err - } + return builder.Script() +} - // Next, we'll need to construct the revocation path, which is just a - // simple checksig script. - builder = txscript.NewScriptBuilder() +func NewLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) ([]byte, + error) { + + 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() + return builder.Script() +} + +// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to +// create and spend the commitment output for the local party. +func NewLocalCommitScriptTree(csvTimeout uint32, + selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) { + + // First, we'll need to construct the tapLeaf that'll be our delay CSV + // clause. + delayScript, err := NewLocalCommitDelayScript(csvTimeout, selfKey) + if err != nil { + return nil, err + } + + // Next, we'll need to construct the revocation path, which is just a + // simple checksig script. + revokeScript, err := NewLocalCommitRevokeScript(selfKey, revokeKey) if err != nil { return nil, err } @@ -1955,19 +1967,30 @@ 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, - scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + revokeTx *wire.MsgTx, scriptTree *txscript.IndexedTapScriptTree, + ctrlBytes []byte) (wire.TxWitness, error) { // 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 ( + ctrlB = ctrlBytes + err error ) + if len(ctrlBytes) == 0 { + revokeTapleafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + revokeIdx := scriptTree.LeafProofIndex[revokeTapleafHash] + revokeMerkleProof := scriptTree.LeafMerkleProofs[revokeIdx] + revokeControlBlock := revokeMerkleProof.ToControlBlock( + &TaprootNUMSKey, + ) + + ctrlB, err = revokeControlBlock.ToBytes() + if err != nil { + return nil, err + } + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -1982,10 +2005,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] = ctrlB return witnessStack, nil } @@ -2263,17 +2283,30 @@ func TaprootCommitScriptToRemote(remoteKey *btcec.PublicKey, // 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, - scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + sweepTx *wire.MsgTx, scriptTree *txscript.IndexedTapScriptTree, + ctrl []byte) (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(&TaprootNUMSKey) + var ( + ctrlBytes = ctrl + err error + ) + if len(ctrlBytes) == 0 { + settleTapleafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] + settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] + settleControlBlock := settleMerkleProof.ToControlBlock( + &TaprootNUMSKey, + ) + + ctrlBytes, err = settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + } // With the control block created, we'll now generate the signature we // need to authorize the spend. @@ -2288,10 +2321,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] = ctrlBytes return witnessStack, nil } diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 17008288bf..f4c69b8df9 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -101,6 +101,13 @@ type SignDescriptor struct { // InputIndex is the target input within the transaction that should be // signed. InputIndex int + + // 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 } // SignMethod defines the different ways a signer can sign, given a specific diff --git a/input/size.go b/input/size.go index 9796314cba..60b1c065f1 100644 --- a/input/size.go +++ b/input/size.go @@ -556,6 +556,47 @@ const ( // - leafVersionAndParity: 1 byte // - schnorrPubKey: 32 byte TaprootBaseControlBlockWitnessSize = 33 + + // TaprootToLocalRevokeScriptSize 68 bytes + // - OP_DATA: 1 byte + // - local key: 32 bytes + // - OP_DROP: 1 byte + // - OP_DATA: 1 byte + // - revocation key: 32 bytes + // - OP_CHECKSIG: 1 byte + TaprootToLocalRevokeScriptSize = 1 + 32 + 1 + 1 + 32 + 1 + + // TaprootToLocalRevokeWitnessSize + // - NumberOfWitnessElements: 1 byte + // - sigLength: 1 byte + // - sweep sig: 64 bytes + // - script len: 1 byte + // - revocation script size: 68 bytes + // - ctrl block size: 1 byte + // - base control block: 33 bytes + // - merkle proof: 32 + TaprootToLocalRevokeWitnessSize = 1 + 1 + 64 + 1 + + TaprootToLocalRevokeScriptSize + 1 + 33 + 32 + + // TaprootToRemoteScriptSize + // - OP_DATA: 1 byte + // - remote key: 32 bytes + // - OP_CHECKSIG: 1 byte + // - OP_1: 1 byte + // - OP_CHECKSEQUENCEVERIFY: 1 byte + // - OP_DROP: 1 byte + TaprootToRemoteScriptSize = 1 + 32 + 1 + 1 + 1 + 1 + + // TaprootToRemoteWitnessSize + // - NumberOfWitnessElements: 1 byte + // - sigLength: 1 byte + // - sweep sig: 64 bytes + // - script len: 1 byte + // - remote script size: 37 bytes + // - ctrl block size: 1 byte + // - base control block: 33 bytes + TaprootToRemoteWitnessSize = 1 + 1 + 64 + 1 + + TaprootToRemoteScriptSize + 1 + 33 ) // EstimateCommitTxWeight estimate commitment transaction weight depending on diff --git a/input/taproot_test.go b/input/taproot_test.go index 6263e3aa03..05fa32e105 100644 --- a/input/taproot_test.go +++ b/input/taproot_test.go @@ -1011,7 +1011,7 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType, return TaprootCommitSpendRevoke( signer, signDesc, spendTx, - commitScriptTree.TapscriptTree, + commitScriptTree.TapscriptTree, nil, ) } } @@ -1211,7 +1211,7 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType, return TaprootCommitRemoteSpend( signer, signDesc, spendTx, - commitScriptTree.TapscriptTree, + commitScriptTree.TapscriptTree, nil, ) } } diff --git a/input/witnessgen.go b/input/witnessgen.go index 7df388c7c3..8b3e22f4d7 100644 --- a/input/witnessgen.go +++ b/input/witnessgen.go @@ -180,6 +180,16 @@ const ( // regular p2tr output that's sent to an output which is under complete // control of the backing wallet. TaprootPubKeySpend StandardWitnessType = 21 + + // TaprootRemoteCommitSpend is a witness type that allows us to spend + // our settled local commitment after a CSV delay when the remote party + // has force closed the channel. + TaprootRemoteCommitSpend StandardWitnessType = 23 + + // TaprootCommitmentRevoke is a witness that allows us to sweep the + // settled output of a malicious counterparty's who broadcasts a + // revoked taproot commitment transaction. + TaprootCommitmentRevoke StandardWitnessType = 34 ) // String returns a human readable version of the target WitnessType. @@ -253,6 +263,12 @@ func (wt StandardWitnessType) String() string { case TaprootPubKeySpend: return "TaprootPubKeySpend" + case TaprootCommitmentRevoke: + return "TaprootCommitmentRevoke" + + case TaprootRemoteCommitSpend: + return "TaprootRemoteCommitSpend" + default: return fmt.Sprintf("Unknown WitnessType: %v", uint32(wt)) } @@ -400,7 +416,49 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer, fallthrough case NestedWitnessKeyHash: return signer.ComputeInputScript(tx, desc) + 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 script spend") + } + witness, err := TaprootCommitSpendRevoke( + signer, desc, tx, nil, desc.ControlBlock, + ) + 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, desc.ControlBlock, + ) + if err != nil { + return nil, err + } + + return &Script{ + Witness: witness, + }, nil default: return nil, fmt.Errorf("unknown witness type: %v", wt) }