Skip to content

Zero-fee taproot channels using v3 transactions (feature 82/83)#1330

Draft
t-bast wants to merge 20 commits intolightning:masterfrom
t-bast:zero-fee-taproot-commitments
Draft

Zero-fee taproot channels using v3 transactions (feature 82/83)#1330
t-bast wants to merge 20 commits intolightning:masterfrom
t-bast:zero-fee-taproot-commitments

Conversation

@t-bast
Copy link
Copy Markdown
Collaborator

@t-bast t-bast commented Apr 2, 2026

We introduce a new channel_type that leverages v3 (TRUC) transactions, taproot, pay-to-anchor outputs and ephemeral dust. This channel_type unifies zero_fee_commitments (which are defined for segwit v0) and option_simple_taproot (which is defined for nVersion = 2), by building on top of #995 and #1228.

Combining those two channel types results in a cleaner transaction architecture:

  • by using a single pay-to-anchor instead of keyed segwit v0 anchors, we don't need to reveal funding keys when spending output to allow late spending of the anchor outputs: this lets us use the revocation key as internal key everywhere, which gets rid of the NUMS point introduced by option_simple_taproot
  • by using TRUC replacement rules, we don't need the CSV 1 on outputs and the main remote output can directly be a key-path spend, which can be used to CPFP the commitment transaction
  • similarly, HTLC outputs can be used to CPFP the commitment transaction
  • it also allows an extension that provides better mobile wallet safety against malicious LSP, described here: https://delvingbitcoin.org/t/zero-fee-commitments-for-mobile-wallets/1453

It should be preferred to all existing channel types, once enough bitcoin nodes correctly relay v3 transactions.

TODO:

  • Add test vectors

Roasbeef and others added 20 commits June 30, 2025 18:06
In this extension BOLT, we specify the initial flavor of taproot
channels to be deployed. This channel type uses musig2 aggregated keys
and signatures for the funding output, making it a normal single
signature key path spend. All outputs are then updated to use P2T2
(segwit v1) outputs. The coop close process has been simplified to
always terminate, and the co-op close transaction now also flags RBF to
make way for future schemes that enable the process to be restarted
which enables co-op close fee bumping.

A top-level key spend output is used to the revocation of HTLC outputs.
The revocation for the local output uses a script path to ensure that
information needed to sweep the anchors by 3rd parties is always
revealed on chain.
In this commit, we make a few changes to the local nonces map as
defined:
  * Drop the length prefix, just encode the nonces concatenated to each
    other. The number of nonces to decode can be computed from the size
    of the nonces and a txid.
  * Using the funding txid in the nonce map instead of the empty hash.
We must use the `next_local_nonces` TLV instead of a single nonce TLV
in the following messages to be future-proof with splicing:

- `channel_reestablish`
- `revoke_and_ack`

We must include one nonce for each commitment transaction that can be
published: when there is a pending splice, that means we need one nonce
for the current funding transaction, another nonce for the commitment
transaction that spends the splice transaction, and additional nonces
for each RBF attempt of that splice transaction (if any).

All of those commitment transactions use the same `per_commitment_point`
and are all revoked with the same secret: even though we send multiple
`commit_sig` messages while splices are pending, we send always send a
single `revoke_and_ack` message. This is why it must contain the map of
all next local nonces.
If we want to be future-proof with splicing, we must include the
`funding_txid` in the shachain root, which allows having distinct
shachains for each splice transaction to deterministically derive
local commitment nonces.
The specification for `option_simple_close` was inconsistent with
regards to closee and closer nonce. We fix the incorrect or unclear
requirements.
While `lnd` uses the legacy `closing_signed` mechanism for early
taproot channels support, this shouldn't be in the BOLT: we should
require `option_simple_close` whenever using taproot channels, which
was explicitly designed for taproot.
Updates to simple taproot channels for cross-compatibility
Remove the redundant nSequence paragraph from the cooperative close
section. The `option_simple_close` spec in BOLT #2 already specifies
that `nSequence` MUST be exactly `0xFFFFFFFD`, so restating it here
with the weaker "less-than-or-equal" phrasing was both redundant and
inconsistent.

Also fix "next closee nonce" to "closer nonce" in the JIT nonce
description for `closing_complete`, matching the terminology used
throughout the rest of the spec.
…ions

Update the embedded JSON test vectors to match the corrected lnd test
vector generator output. The key changes are:

The `remote_partial_sig` fields now contain actual 32-byte MuSig2
partial signature scalars instead of the dummy 8-byte DER stub
`3006020100020100` that was leaking through from the zeroed `CommitSig`
field. Each test case also now includes `local_nonce` and `remote_nonce`
fields (66-byte compressed public nonces) so that verifiers can
reconstruct the combined MuSig2 signature independently.

The HTLC resolution transactions have been corrected in two ways: the
`remote_partial_sig_hex` values are now correctly mapped to their
transactions using BIP 69 output index ordering (fixing the invalid
HTLC-timeout signatures that eclair reported), and the HTLC-success
witness stacks now include the preimage and redeem script in the correct
witness slots.

The "commitment tx with some HTLCs trimmed" test case now uses
dust_limit=2500 instead of 546, which properly exercises zero-fee HTLC
trimming by pushing the three smallest test HTLCs (1000, 2000, 2000
sats) below the dust threshold, reducing the HTLC count from 5 to 2.
Add detailed explanatory sections to the test vectors appendix so that
implementers can reproduce the vectors without needing to reverse-
engineer the generator code. The new documentation covers:

The complete set of key derivation labels used with the
`SHA256(seed || label)` pattern, so implementations can derive all
private keys from the seed independently.

MuSig2 nonce generation: the deterministic signing randomness labels
(`"local-signing-rand"` / `"remote-signing-rand"`) that produce
reproducible nonces. Each test case now includes `local_nonce` and
`remote_nonce` fields so implementations can cross-check their nonce
derivation before attempting signature verification.

The distinction between commitment transaction signatures (32-byte
MuSig2 partial sig scalars using a keyspend path) and HTLC resolution
signatures (64-byte Schnorr signatures using a tapscript spend path).

The BIP 69 output index ordering convention for `htlc_descs` entries,
matching the order that `htlc_signature` messages are exchanged during
the commitment protocol.

The full taproot witness stack layout for both HTLC-success (5 elements
including preimage) and HTLC-timeout (4 elements) transactions.

The zero-fee HTLC trimming semantics, explaining why trimming depends
solely on the dust limit rather than the fee rate.
In this commit, we expand the MuSig2 Nonce Generation section of the
test vector documentation to describe the newly added secret nonce
fields (local_sec_nonce and remote_sec_nonce). Different MuSig2
implementations may produce different nonces from the same randomness
input, so providing raw 97-byte secret nonces allows interop
implementations to inject them directly rather than re-derive them.

The documentation now clarifies that all nonces in a test case
correspond to the same commitment transaction (the local party's
commitment), explains the distinction between the local verification
nonce and the remote JIT signing nonce, and provides step-by-step
instructions for replaying MuSig2 signing from the test vectors.
Update the embedded JSON test vectors to include the new
local_sec_nonce and remote_sec_nonce fields, and correct the
local_nonce values. The local_nonce now reflects the local party's
verification nonce for their own commitment transaction (from
LocalSession), rather than the JIT signing nonce for the remote
party's commitment that was previously (incorrectly) used.
… form

The HTLC-timeout second-level transaction script was using the non-prod
form (OP_CHECKSIG + OP_CSV OP_DROP) while the HTLC-success script was
already using the prod form (OP_CHECKSIGVERIFY + OP_CSV). Both scripts
should be identical in structure since they share the same tapscript
tree construction. This aligns the spec with the implementation which
uses WithProdScripts() for all taproot channel scripts.
Update the embedded test vectors with HTLC signatures that use BIP-340
standard nonce derivation (zero auxrand) instead of RFC6979. This
ensures HTLC second-level transaction signatures are reproducible
across different Schnorr implementations (libsecp256k1, btcd, etc).

Also document that implementations must use BIP-340 deterministic
signing with zero auxrand to reproduce the HTLC signatures in the
test vectors.
We introduce a new `channel_type` that leverages v3 (TRUC) transactions,
taproot, pay-to-anchor outputs and ephemeral dust. This `channel_type`
unifies `zero_fee_commitments` (which are defined for segwit v0) and
`option_simple_taproot` (which is defined for `nVersion = 2`).

Combining those two channel types results in a cleaner transaction
architecture:

- by using a single pay-to-anchor instead of keyed segwit v0 anchors,
  we don't need to reveal funding keys when spending output to allow
  late spending of the anchor outputs: this lets us use the revocation
  key as internal key *everywhere*, which gets rid of the NUMS point
  introduced by `option_simple_taproot`
- by using TRUC replacement rules, we don't need the `CSV 1` on outputs
  and the main remote output can directly be a key-path spend, which
  can be used to CPFP the commitment transaction
- similarly, HTLC outputs can be used to CPFP the commitment transaction

It should be preferred to all existing channel types, once enough
bitcoin nodes correctly relay v3 transactions.
t-bast added a commit to ACINQ/eclair that referenced this pull request Apr 3, 2026
We add support for the zero-fee commitment format specified in
lightning/bolts#1330.

Channels using this commitment format benefit from better protection
against pinning attacks (thanks to TRUC/v3 transactions), don't need
the `update_fee` mechanism, have less dust exposure risk, and use an
overall simpler state machine, while benefiting from the privacy
improvements of taproot channels.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants