Skip to content

Implement option_htlcs_claim_tx#1

Open
darosior wants to merge 4 commits into
bip448:0.3from
darosior:templatehash_antipinning
Open

Implement option_htlcs_claim_tx#1
darosior wants to merge 4 commits into
bip448:0.3from
darosior:templatehash_antipinning

Conversation

@darosior

@darosior darosior commented Jun 30, 2026

Copy link
Copy Markdown

This is an unreviewed vibecoded implementation of bip448/bolts#2 against rust-lightning's latest release branch. An end-to-end integration test based on this branch is added to LDK-node here: bip448/ldk-node#1.

@darosior darosior force-pushed the templatehash_antipinning branch 2 times, most recently from 97c81b9 to d8a6a03 Compare July 1, 2026 19:24
darosior and others added 4 commits July 1, 2026 16:56
Implements the experimental `option_htlcs_claim_tx` channel type (feature
bits 110/111), which builds on `option_zero_fee_commitments` to close the
last on-chain pinning gap. Offered HTLC outputs become P2TR outputs whose
preimage (`htlc_success`) spend path commits, via `OP_TEMPLATEHASH`, to a
fixed v3 "HTLC claim transaction".

 - Add the `OptionHTLCsClaimTx` feature, its `ChannelTypeFeatures`
   accessors, channel-type negotiation, and the `negotiate_htlcs_claim_tx`
   config flag (depends on `negotiate_anchor_zero_fee_commitments`).
 - Build the offered HTLC commitment output as a P2TR when the feature
   applies.
 - The offered-HTLC `htlc_success` leaf uses the real `OP_TEMPLATEHASH`
  (0xce). As it pushes the computed hash, the leaf now ends with
  `<template hash> OP_TEMPLATEHASH OP_EQUAL`.
 - Resolve such outputs on-chain by broadcasting the fixed claim
   transaction as an untractable, non-aggregated package, reusing the
   zero-fee-commitment broadcast path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `option_htlcs_claim_tx` HTLC claim transaction is template-committed
(a single input and a single output) and pays zero fees, so it cannot be
modified and can only be relayed and confirmed alongside a fee-paying
child (a TRUC 1-parent-1-child package). Wire this up, modelled on how
zero-fee commitment transactions are fee-bumped:

- Add `EcdsaChannelSigner::sign_htlcs_claim_transaction_input`, which
  signs the child's input spending the claim transaction's output (a
  P2WPKH to our payment point). `InMemorySigner` reuses the existing
  `sign_counterparty_payment_input` path.
- `OnchainTxHandler` now reports templated offered HTLC outputs as
  requiring external funding and yields a new `ClaimEvent::BumpHTLCsClaimTx`
  carrying the signed claim transaction, surfaced to the user as a new
  `BumpTransactionEvent::HTLCsClaimTxResolution`.
- The default `BumpTransactionEventHandler` constructs a version 3 child
  spending the claim output plus coin-selected inputs, signs it, and
  broadcasts `[claim_tx, child]` as a package.

Also fix `verify_channel_type_features` to accept `htlcs_claim_tx` as a
supported channel type feature, without which a `ChannelMonitor` for such
a channel failed to round-trip through serialization.

Add a handler unit test and an end-to-end functional test exercising the
counterparty-offered-HTLC on-chain resolution path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Test vectors from bolt03/htlcs-claim-tx-test.json, verifying the full
option_htlcs_claim_tx output structure:

  1. Commitment tx with a single offered HTLC (P2TR output): checks
     the signed commitment tx, htlc_timeout_script, htlc_success_script
     (with embedded OP_TEMPLATEHASH digest), template_hash, unsigned
     HTLC claim tx, and the claim tx with the tapscript witness.
  2. Same but with millisatoshi truncation (25000821 msat → 25000 sat
     in the claim tx output); template_hash is identical to case 1
     because OP_TEMPLATEHASH commits to the satoshi output value.
  3. Mixed commitment: one offered (P2TR) + one received (P2WSH, unchanged).

Run with `RUSTFLAGS="--cfg ldk_test_vectors" cargo test htlcs_claim_tx_test_vectors`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The `option_htlcs_claim_tx` offered-HTLC preimage spend is a P2TR
script-path spend whose witness is `[preimage, htlc_success_script,
control_block]`, unlike the legacy P2WSH HTLC witnesses that
`HTLCClaim::from_witness` knew how to parse. As a result the offering
(or forwarding) node's `ChannelMonitor` failed to classify the claim
transaction and never extracted the preimage, so the corresponding
outbound HTLC could not be resolved on-chain.

Teach `HTLCClaim::from_witness` to recognize the templated witness as an
`OfferedPreimage` claim (via a control-block size modular check plus a
leaf script ending in `OP_TEMPLATEHASH OP_EQUAL`), and read the preimage
from the correct witness element during monitor claim processing.

This work was done with the assistance of Claude Code (Claude Opus 4.8).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@darosior darosior force-pushed the templatehash_antipinning branch from d8a6a03 to 2b4711a Compare July 1, 2026 21:05
@darosior darosior changed the base branch from main to 0.3 July 1, 2026 21:05
@darosior darosior changed the title WIP: Use OP_TEMPLATEHASH to close last pinning vector in today's Lightning channels Implement option_htlcs_claim_tx Jul 1, 2026
@darosior darosior marked this pull request as ready for review July 1, 2026 21:55
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.

1 participant