Skip to content

multi: implement awareness of the final/production taproot channel variant#9985

Open
Roasbeef wants to merge 40 commits intolightningnetwork:masterfrom
Roasbeef:prod-taproot-chans
Open

multi: implement awareness of the final/production taproot channel variant#9985
Roasbeef wants to merge 40 commits intolightningnetwork:masterfrom
Roasbeef:prod-taproot-chans

Conversation

@Roasbeef
Copy link
Copy Markdown
Member

@Roasbeef Roasbeef commented Jun 24, 2025

This pull request implements comprehensive support for production taproot channels in LND, introducing the ability to create and manage channels using the finalized taproot specification with optimized script structures. Production taproot channels utilize feature bits 80/81, and utilize resolution scripts that have been optimized using miniscript.

This PR builds on top of #9982 which adds some prepatory TLVs to support splicing in the future.

In aggregate, these changes are intended to implement the final set of upcoming changes to the taproot chans spec.

Implementation Overview

Core Infrastructure

The implementation introduces a comprehensive witness type system that distinguishes between staging and production taproot channels. Seven new "Final" witness types have been added to handle all taproot channel operations including commitment spends, HTLC resolution, and revocation scenarios.

A new channel type bit TaprootFinalBit has been added to the channel database infrastructure, allowing the system to persistently track whether a channel uses production or staging taproot scripts.

Contract Resolution System

The contract court system has been extensively updated to support production taproot channels throughout the resolution process. All HTLC resolvers now accept channel type information and use it to select appropriate witness types during contract resolution. The UTXO nursery has been integrated to properly handle time-locked outputs from production taproot channels.

The witness type selection follows a consistent three-way pattern throughout the codebase: production taproot channels use Final witness types, staging taproot channels use existing taproot witness types, and legacy channels continue using their established witness types.

Script Generation and Wallet Integration

The wallet's commitment transaction generation logic has been updated to use appropriate script options based on the channel type. A new script option system allows callers to specify whether production or staging script variants should be generated, with the wallet automatically selecting the correct option based on the channel's type.

Script generation functions throughout the input package now accept TaprootScriptOpt parameters that enable production script generation when appropriate.

External Interfaces

The RPC interface has been extended with a new SIMPLE_TAPROOT_FINAL commitment type that allows external clients to explicitly request production taproot channels. The funding manager's negotiation logic has been updated to handle production taproot channel requests and ensure that both parties support the necessary feature bits before establishing the channel.

Backward Compatibility

This implementation maintains full backward compatibility with existing channel types. Staging taproot channels continue to function exactly as before, and legacy channels remain unaffected. The new production taproot functionality is additive and does not modify any existing behavior.


TODO Checklist

  • Add breach arbitrator support for production taproot channels
  • Update integration test matrix to include production taproot scenarios
  • Complete watchtower justice transaction support for production channels
  • Update documentation with production taproot channel guidelines

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jun 24, 2025

Important

Review skipped

Auto reviews are limited to specific labels.

🏷️ Labels to auto review (1)
  • llm-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Roasbeef
Copy link
Copy Markdown
Member Author

Rebased on top of the updated dep PR.

@Roasbeef Roasbeef force-pushed the prod-taproot-chans branch 2 times, most recently from 4652c8a to 56d79e2 Compare October 30, 2025 00:11
@Roasbeef Roasbeef marked this pull request as ready for review October 30, 2025 00:17
@Roasbeef
Copy link
Copy Markdown
Member Author

Added a final set of commits to add the new channel type to the relevant commitment type test matrices in the itests.

@Roasbeef
Copy link
Copy Markdown
Member Author

Tacked on two additional commits to add the nonce map to revoke and ack.

@Roasbeef
Copy link
Copy Markdown
Member Author

Roasbeef commented Dec 6, 2025

Pushed a commit to only set the new nonce map field based on the "final" feature bit.

@saubyk saubyk added this to v0.21 Mar 5, 2026
@saubyk saubyk moved this to In progress in v0.21 Mar 5, 2026
@saubyk saubyk added this to the v0.21.0 milestone Mar 5, 2026
Comment on lines +233 to +243
// features where relevant. This is the staging version using development
// scripts.
CommitmentType_SIMPLE_TAPROOT CommitmentType = 5
// A channel that uses musig2 for the funding output, and the new tapscript
// features where relevant. This is the production version using final scripts
// and feature bits 80/81.
CommitmentType_SIMPLE_TAPROOT_FINAL CommitmentType = 6
// Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.
// This channel type also commits to additional meta data in the tapscript
// leaves for the scripts in a channel.
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 6
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 7
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This renumbers SIMPLE_TAPROOT_OVERLAY from 6 to 7. Since protobuf enum values are part of the wire format, existing clients that serialized value 6 as overlay would now read it as taproot final. Would it make sense to keep overlay at 6 and assign the new final type to 7 instead?

Suggested change
// features where relevant. This is the staging version using development
// scripts.
CommitmentType_SIMPLE_TAPROOT CommitmentType = 5
// A channel that uses musig2 for the funding output, and the new tapscript
// features where relevant. This is the production version using final scripts
// and feature bits 80/81.
CommitmentType_SIMPLE_TAPROOT_FINAL CommitmentType = 6
// Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.
// This channel type also commits to additional meta data in the tapscript
// leaves for the scripts in a channel.
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 6
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 7
// features where relevant. This is the staging version using development
// scripts.
CommitmentType_SIMPLE_TAPROOT CommitmentType = 5
// Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.
// This channel type also commits to additional meta data in the tapscript
// leaves for the scripts in a channel.
CommitmentType_SIMPLE_TAPROOT_OVERLAY CommitmentType = 6
// A channel that uses musig2 for the funding output, and the new tapscript
// features where relevant. This is the production version using final scripts
// and feature bits 80/81.
CommitmentType_SIMPLE_TAPROOT_FINAL CommitmentType = 7

input.TaprootHtlcOfferedTimeoutSecondLevelFinal: WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_FINAL,
input.TaprootHtlcAcceptedSuccessSecondLevelFinal: WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_FINAL,
input.TaprootHtlcOfferedRemoteTimeoutFinal: WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT_FINAL,
input.TaprootHtlcAcceptedRemoteSuccessFinal: WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS_FINAL,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

TaprootCommitmentRevokeFinal (witness type 41) is defined in input/witnessgen.go:292 with a complete witness generator, but it has no corresponding entry in the proto enum (walletkit.proto) or the witnessTypeMapping in walletkit_server.go:225-246.

@saubyk saubyk moved this from In progress to In review in v0.21 Mar 12, 2026
Copy link
Copy Markdown
Collaborator

@gijswijs gijswijs left a comment

Choose a reason for hiding this comment

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

Big PR for which I have a bunch of comments and remarks.

I'm reiterating the most important ones here, or the ones that couldn't be inlined:

  1. The isProdTaprootResolution in utxonursery.go uses a outdated heuristic. For a plain production taproot channel (the thing this PR adds), ResolutionBlob will be None, and isProdTaprootResolution will return false resulting in the use of staging witness types for production taproot channels.
  2. Three locations in the watchtower justice kit have commented-out production script detection with TODO markers. Currently the watchtower does not handle prod taproot channels. I'm wondering if that's a deliberate choice.

The entire PR needs to be squashed and rebased. Also, the final 6 commits, although not specifically fixups, should all be squashed into a single commit.

@lightninglabs-deploy
Copy link
Copy Markdown
Collaborator

@yyforyongyu: review reminder
@Roasbeef, remember to re-request review from reviewers when ready

@Roasbeef Roasbeef changed the base branch from splice-nonces to master March 26, 2026 00:44
@Roasbeef Roasbeef force-pushed the prod-taproot-chans branch 3 times, most recently from d8d2a45 to 12fb652 Compare March 26, 2026 03:39
Copy link
Copy Markdown
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Looks like there are empty commits created in the end. In addition we may need a followup PR to update the breach arbitrator as it needs to check IsTaprootFinal() and use TaprootRemoteCommitSpendFinal/TaprootCommitmentRevokeFinal.

) {
secondLevelTxes[index] = tx
secondLevelSigs[index] = sig
// Build a map from commitment tx output index to
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think both this commit and 87953b7 should be squashed into 69110dd?

remoteBalance: 3_000_000_000,
feePerKw: 100_000,
dustLimit: 546,
feePerKw: 644,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

again this should be squashed into 69110dd

@Roasbeef Roasbeef force-pushed the prod-taproot-chans branch from db32cbb to 128ff10 Compare April 8, 2026 23:16
@Roasbeef Roasbeef requested a review from erickcestari April 8, 2026 23:17
@github-actions github-actions bot added the severity-critical Requires expert review - security/consensus critical label Apr 8, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

🔴 PR Severity: CRITICAL

First classification | 44 non-test/non-generated files | ~1,607 lines changed

🔴 Critical (29 files)
  • channeldb/channel.go - channel state persistence
  • contractcourt/chain_arbitrator.go - on-chain dispute resolution
  • contractcourt/channel_arbitrator.go - on-chain dispute resolution
  • contractcourt/commit_sweep_resolver.go - on-chain dispute resolution
  • contractcourt/htlc_incoming_contest_resolver.go - on-chain dispute resolution
  • contractcourt/htlc_outgoing_contest_resolver.go - on-chain dispute resolution
  • contractcourt/htlc_success_resolver.go - on-chain dispute resolution
  • contractcourt/htlc_timeout_resolver.go - on-chain dispute resolution
  • contractcourt/utxonursery.go - on-chain dispute resolution / fund recovery
  • funding/commitment_type_negotiation.go - channel funding workflow
  • htlcswitch/link.go - HTLC forwarding state machine
  • input/input.go - script signing / witness generation
  • input/script_utils.go - script signing / witness generation
  • input/size.go - script signing / witness generation
  • input/witnessgen.go - witness generation
  • lnwallet/chancloser/rbf_coop_msg_mapper.go - wallet operations
  • lnwallet/channel.go - wallet channel state / commitment transactions
  • lnwallet/commitment.go - commitment transaction construction
  • lnwallet/musig_session.go - MuSig2 session management
  • lnwallet/reservation.go - channel funding / signing
  • lnwallet/test_vectors_taproot.json - taproot test vectors (lnwallet/)
  • lnwire/channel_reestablish.go - Lightning wire protocol
  • lnwire/musig2.go - Lightning wire protocol (MuSig2)
  • lnwire/revoke_and_ack.go - Lightning wire protocol
  • lnwire/test_message.go - Lightning wire protocol
  • peer/brontide.go - encrypted peer connections
  • peer/musig_chan_closer.go - peer MuSig2 channel closing
  • rpcserver.go - core server coordination
  • server.go - core server coordination
🟠 High (8 files)
  • feature/default_sets.go - feature bit management
  • feature/deps.go - feature bit management
  • feature/manager.go - feature bit management
  • lnrpc/walletrpc/walletkit_server.go - RPC/API server
  • lnrpc/wtclientrpc/wtclient.go - RPC/API (watchtower client)
  • watchtower/blob/commitments.go - breach remediation
  • watchtower/blob/justice_kit.go - breach remediation
  • watchtower/blob/type.go - breach remediation
🟡 Medium (6 files)
  • chanbackup/single.go - channel backup
  • cmd/commands/cmd_open_channel.go - CLI client command (cmd/*)
  • lnrpc/lightning.proto - API definition
  • lnrpc/lightning.swagger.json - API definition
  • lnrpc/walletrpc/walletkit.proto - API definition
  • lnrpc/walletrpc/walletkit.swagger.json - API definition
🟢 Low (1 file)
  • docs/release-notes/release-notes-0.21.0.md - release notes

Analysis

This PR is CRITICAL severity. It touches 9 distinct critical packages simultaneously:

  • lnwallet/* — commitment transaction construction, MuSig2 sessions, channel reservations, and cooperative closing
  • lnwire/* — wire protocol messages for channel reestablishment, revoke-and-ack, and MuSig2 nonces
  • contractcourt/* — HTLC resolvers (success, timeout, incoming/outgoing contest), utxo nursery, and chain arbitrator all updated for taproot channel support
  • htlcswitch/* — HTLC link updated for taproot channel handling
  • input/* — witness generation, script utilities, and size estimation for taproot/MuSig2 paths
  • funding/* — commitment type negotiation extended for taproot channels
  • channeldb/* — channel state persistence updated
  • peer/* — brontide peer and MuSig2 channel closer updated
  • server.go / rpcserver.go — core server coordination touched

The PR also exceeds both bump thresholds: 44 non-test/non-generated files (>20) and ~1,607 non-test/non-generated lines changed (>500), further confirming CRITICAL classification. The changes implement taproot channel support across the full LN stack, requiring careful review of the MuSig2 nonce handling in lnwallet/musig_session.go, the new witness generation paths in input/witnessgen.go, and the updated HTLC resolvers in contractcourt/.


To override, add a severity-override-{critical,high,medium,low} label.

Roasbeef added 23 commits April 8, 2026 16:28
Before this commit, we'd _always_ set both nonces fields, for both the
staging and the final taproot channels type.

With this commit, we've switched to only setting the new nonce map field
for the final taproot feature bit type.
In this commit, we add the ability to inject a custom random source
for generating JIT (Just-In-Time) signing nonces in MuSig2 sessions.
By default, MuSig2 signing nonces are generated using crypto/rand,
which makes signatures non-deterministic across runs. For test vector
generation, we need fully reproducible signatures from a fixed seed.

A new `customNonceRand` field is threaded through `MusigSession`,
`MusigSessionCfg`, `MusigPairSession`, and exposed via the
`WithCustomSigningRand` channel option. When set, the custom reader
is passed to `musig2.WithCustomRand()` during JIT nonce generation
in `SignCommit`. All existing callers pass `fn.None[io.Reader]()` to
preserve the current behavior of using the system CSPRNG.
In this commit, we add a test vector generator and verifier for
taproot channel constructions. All vectors are derived
deterministically from a single 32-byte seed using SHA256(seed ||
label) for key derivation, ensuring any implementation can reproduce
them independently.

The generator covers two areas:

Script vectors decompose the full tapscript trees for every output
type: funding (MuSig2 aggregated key), to_local (delay + revocation
leaves), to_remote (1-block CSV leaf), anchors (OP_16 OP_CSV),
offered/accepted HTLCs on both local and remote commits, and
second-level HTLC transactions. Each entry captures the raw leaf
scripts, leaf hashes, tapscript root, internal key, output key, and
pkScript.

Transaction vectors produce full serialized commitment transactions
and HTLC resolution transactions for three scenarios: a simple
commitment with no HTLCs, a commitment with five untrimmed HTLCs,
and the same HTLCs at a higher fee rate causing some to be trimmed.

To generate: go test -run TestTaprootVectors ./lnwallet/ -args -generate-taproot-vectors
To verify:   go test -run TestTaprootVectors ./lnwallet/
In this commit, we fix the taproot test vector generator to capture and
emit the real MuSig2 partial signatures and public nonces rather than
the dummy `CommitSig` value which is zeroed out for taproot channels.

Previously, the generator was reading from `CommitSig.ToSignatureBytes()`
which yielded a minimal DER encoding of `(0, 0)` (the 8-byte string
`3006020100020100`). For taproot channels the actual signature lives in
the `PartialSig` field of the `CommitSigs` struct, which carries both
the 32-byte partial sig scalar and the 66-byte compressed public nonce
needed by the verifier to reconstruct the combined signature.

We now unwrap the `PartialSig` from both the local and remote commitment
signatures, extract the nonce and sig bytes, and include `local_nonce`
and `remote_nonce` fields alongside `remote_partial_sig` in the emitted
JSON. This gives other implementations (eclair, CLN, etc.) all the
material they need to independently verify commitment transaction
signatures using their own MuSig2 libraries.
In this commit, we fix two interrelated bugs in the way HTLC signatures
are associated with their corresponding second-level transactions in the
taproot test vector generator.

The first issue was that HtlcSigs are sorted by BIP 69 output index
(matching the commitment transaction's output ordering), but the old
code was assigning signatures using the iteration order of incoming
HTLCs followed by outgoing HTLCs. This meant timeout transaction
signatures were getting paired with success transactions and vice versa
whenever the output ordering didn't happen to match the incoming-first
iteration order. This is the root cause of the invalid HTLC-timeout
signatures that eclair reported when cross-validating.

We now collect all HTLC entries (both incoming and outgoing) into a
single slice, sort them by their commitment output index, then zip them
against the HtlcSigs array so each signature lines up with the correct
second-level transaction.

The second issue was in the HTLC-success preimage extraction path. The
old code read the witness script from index [4] (the control block) and
used a hardcoded byte offset of 69 to locate the payment hash, then
wrote the preimage into index [3] (overwriting the script). The correct
taproot witness layout is [remoteSig, localSig, preimage, script,
controlBlock], so the script lives at [3] and the preimage slot is [2].
We now use `txscript.ScriptTokenizer` to walk the script opcodes and
find OP_HASH160 followed by the 20-byte push data, which is far more
robust than relying on fragile byte offsets that break if the script
template ever changes.
…HTLCs

In this commit, we fix the "commitment tx with some HTLCs trimmed" test
case to actually exercise trimming for taproot's zero-fee HTLC
transactions.

With zero-fee second-level HTLCs, the HTLC output value on the
commitment transaction equals the HTLC amount directly (no fee is
deducted). This means trimming is determined solely by whether the HTLC
amount falls below the dust limit, not by the fee rate. The previous
parameters (fee_per_kw=100000, dust_limit=546) didn't actually trim any
of the test HTLCs because even the smallest test HTLC (1000 sats) was
above the 546 sat dust limit.

We now use fee_per_kw=644 (a reasonable rate) and dust_limit=2500 to
ensure that the three smallest test HTLCs (1000, 2000, 2000 sats) are
properly trimmed, leaving only the 3000 and 4000 sat HTLCs on the
commitment transaction.
In this commit, we add a `signature_verification` sub-test to the
taproot test vector verifier that performs full script execution against
both the commitment transaction and all HTLC resolution transactions.

This uses `txscript.NewEngine` to execute the taproot witness programs
exactly as a Bitcoin node would, providing an independent check that all
signatures in the test vectors are cryptographically valid. For the
commitment transaction, we verify its witness against the funding output
pkScript. For each HTLC resolution transaction, we verify its witness
against the corresponding commitment output it spends.

This catches issues that the structural comparison tests (hex matching)
cannot: for instance, a transaction can have the correct structure but
carry an invalid signature if the sighash was computed over the wrong
prevout or if the wrong key was used for signing. Running the full
script engine also validates the control block, the tap leaf hash, and
the overall taproot spend path.
Regenerate `test_vectors_taproot.json` to reflect the corrected test
vector generator. Changes include actual 32-byte MuSig2 partial
signatures (replacing the dummy 8-byte DER stubs), 66-byte public
nonces for both local and remote parties, corrected HTLC sig-to-
transaction mapping sorted by BIP 69 output index, proper HTLC-success
witness layout with preimage in the correct witness slot, and the
updated trimming test case which now trims 3 of the 5 test HTLCs below
the 2500 sat dust limit (down from 5 HTLC outputs to 2).
In this commit, we add the ability for MusigSession to capture and
expose the raw 97-byte MuSig2 secret nonce generated during JIT signing
nonce creation. This is gated behind the customNonceRand option, so it
only activates in test vector generation mode.

The stashed secret nonce is consumed on read (cleared after access) to
prevent accidental nonce reuse. This enables interop test vectors to
include the raw secret nonces, allowing other implementations to replay
the MuSig2 signing process without needing to match the exact nonce
derivation algorithm used by btcd's musig2 library.
…tors

In this commit, we extend the taproot test vector generator and verifier
to include MuSig2 secret nonces and a full partial signature replay
test.

For the generator, we now capture the correct nonces for each
commitment transaction: local's verification nonce (from LocalSession)
for local's own commitment, and remote's JIT signing nonce (from
RemoteSession) for the same commitment. Previously, the local nonce was
incorrectly captured from the RemoteSession, which corresponds to a
different commitment transaction.

The new musig2_partial_sig_replay test sub-suite verifies three
properties for each test case:

 1. The remote partial sig can be independently reproduced from the
    secret nonce and private key using musig2.Sign().

 2. The local partial sig can be independently produced and verified
    using the local secret nonce.

 3. Both partial sigs combine (via the Session API) into the exact
    Schnorr signature present in the commitment transaction witness.

This enables interop implementations to validate their MuSig2 signing
logic against the test vectors without needing to match nonce derivation
algorithms across different secp256k1 libraries.
Regenerate the test vectors JSON to include local_sec_nonce and
remote_sec_nonce fields alongside the existing public nonces. The local
nonce fields now correctly correspond to local's verification nonce for
their own commitment transaction, matching the commitment tx stored in
the test vector.
btcd's schnorr.Sign defaults to RFC6979 nonce derivation, while
libsecp256k1 (used by eclair, CLN, etc) uses BIP-340's standard
nonce derivation with zero auxrand. Both are deterministic but produce
different signatures for the same key and message, causing HTLC
signature mismatches in interop test vectors.

This commit introduces a bip340Signer wrapper that overrides
SignOutputRaw for taproot script path spends to use
schnorr.CustomNonce([32]byte{}) — matching BIP-340 deterministic
signing behavior. The wrapper is only used in the test vector
generator; production signing paths are unchanged.

Note that MuSig2 commitment signatures were already using BIP-340
nonces internally (via the musig2.Sign path), so only the HTLC
second-level transaction signatures were affected.
Regenerate the test vector JSON with HTLC second-level transaction
signatures that use BIP-340 standard nonce derivation (zero auxrand)
instead of RFC6979. This makes the HTLC signatures reproducible across
different Schnorr implementations. The commitment transaction MuSig2
signatures are unchanged.
Wire channel type through BreachRetribution and the watchtower blob
system to support production taproot channels with final scripts.

The key changes are:

1. Add ChanType field to BreachRetribution so downstream consumers
   (including the watchtower) can determine the script variant.

2. Add FlagTaprootFinalChannel blob type flag and
   TypeAltruistTaprootFinalCommit blob type to distinguish production
   from staging taproot channels in watchtower backups.

3. Add TaprootFinalCommitment to the watchtower's CommitmentType enum
   with appropriate witness type and size mappings.

4. Update taprootJusticeKit to use WithProdScripts() when constructing
   script trees for production taproot channels. The isFinal flag is
   set during construction from BreachRetribution.ChanType and during
   deserialization from the blob's commitment type.

Without this change, the watchtower would construct justice transactions
using staging scripts for production taproot channels, resulting in
invalid witnesses that fail to sweep breached outputs.
…nels

Add SIMPLE_TAPROOT_FINAL to the watchtower revoked close retribution
test matrix. This exercises the new FlagTaprootFinalChannel blob type
and ensures the watchtower correctly constructs justice transactions
using production taproot scripts with OP_CHECKSIGVERIFY.
Fix gci (extra blank lines) and ll (line length > 80 chars) issues
across contractcourt, input, and watchtower packages.
Fix swallowed error in MusigSession.Refresh where AggregateNonces
failure returned nil instead of the actual error.
Add a dedicated backup version (7) for production taproot channels that
use final scripts with OP_CHECKSIGVERIFY. This distinguishes them from
staging taproot channels in the SCB format, ensuring backup
compatibility is explicit about the channel type.
@Roasbeef Roasbeef force-pushed the prod-taproot-chans branch from 128ff10 to b503d06 Compare April 8, 2026 23:28
@github-actions github-actions bot added severity-critical Requires expert review - security/consensus critical and removed severity-critical Requires expert review - security/consensus critical labels Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-critical Requires expert review - security/consensus critical taproot chans taproot

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

6 participants