Skip to content

feat: Node::export_channel_attestation (RFC staging)#1

Draft
rsafier wants to merge 3 commits into
mainfrom
rfc/export-channel-attestation
Draft

feat: Node::export_channel_attestation (RFC staging)#1
rsafier wants to merge 3 commits into
mainfrom
rfc/export-channel-attestation

Conversation

@rsafier
Copy link
Copy Markdown
Owner

@rsafier rsafier commented Apr 19, 2026

Status: review-staging draft on the rsafier fork. See rsafier/rust-lightning#1 for the one-accessor PR this depends on.

Summary

Adds a new public read-only API on `Node`:

```rust
pub fn export_channel_attestation(
&self, channel_id: &ChannelId,
) -> Result<attestation::ChannelAttestation, Error>;

pub fn list_channel_attestations(&self) -> Vecattestation::ChannelAttestation;
```

Returns, for the current state of each channel:

  • Unsigned holder commitment transaction (what we'd broadcast on force-close)
  • Counterparty's ECDSA signature over it (BIP-143, SIGHASH_ALL)
  • Both funding pubkeys, distinguishably (`holder_funding_pubkey`, `counterparty_funding_pubkey`)
  • Balances, pending HTLCs, capacity, `is_outbound`

No secret material exposed. The counterparty's signature is cryptographically attributable to them regardless of who holds it.

Implementation (new module `src/attestation.rs`, ~180 LOC)

  1. `ChainMonitor::get_monitor` → `LockedChannelMonitor` (existing public API).
  2. New in paired rust-lightning PR: `ChannelMonitor::channel_keys_id()`.
  3. `SignerProvider::derive_channel_signer(channel_keys_id)` (existing trait method) → `signer.pubkeys(&secp).funding_pubkey` = holder pubkey.
  4. `ChannelMonitor::unsafe_get_latest_holder_commitment_txn` (existing, `unsafe_revoked_tx_signing` feature enabled on our lightning dep) → signed holder commit tx.
  5. Parse funding-input witness `[empty, sig_A, sig_B, 2-of-2 redeem script]` to pull both pubkeys + sigs.
  6. Match the holder pubkey to one of them → know which sig is the counterparty's.
  7. Strip witness → unsigned commit tx.

Proof

Regtest end-to-end in the lightning-attestor harness: two `ldk-server` nodes built from this branch, six channel scenarios (initial / outbound-pay / bidirectional / htlc-inflight / htlc-settled / multi-channel). Each snapshot's extracted bundle passes an independent BIP-143 counterparty-sig verification against the counterparty's funding pubkey.

Non-goals

  • Revocation secrets, history beyond current unrevoked state — neither is available from LDK (by design, it's what makes Lightning safe) and we don't ask for them.
  • `commitment_number` returned as `0` for now; can be de-obscured from locktime+sequence using both parties' `payment_basepoint` if needed, but the cryptographic BIP-143 verification doesn't depend on it.

Ask

Feedback on the method signature, the module placement, and whether `ChannelAttestation` should live in `ldk-node` or move up into rust-lightning (we went with ldk-node since the witness-parsing + signer-rederivation is LDK-node-level plumbing, not a rust-lightning primitive).

rsafier added 3 commits April 19, 2026 15:49
Adds a read-only accessor on `Node` that returns a cryptographically
verifiable snapshot of the current state of a channel, shaped for use
by external custodial-attestation tooling (see `lightning-attestor` /
the upstream RFC "expose channel attestation data").

The bundle contains, per channel:
 - The unsigned commitment transaction we would broadcast on force-close
 - The counterparty's ECDSA signature over it (BIP-143, SIGHASH_ALL)
 - Both funding pubkeys, DISTINGUISHABLY (holder vs counterparty)
 - Balances, pending HTLCs, capacity, is_outbound

Exposes no secret material — the counterparty's signature is
cryptographically attributable to them regardless of who holds it.

Implementation:

 - Reaches the `ChannelMonitor` via `chain_monitor.get_monitor()`.
 - Calls `unsafe_get_latest_holder_commitment_txn()` to get the signed
   holder commit tx (requires the `unsafe_revoked_tx_signing` feature on
   the `lightning` dep, enabled here; despite the name the method is a
   pure read with no broadcast side-effect).
 - Parses the funding-input witness to extract both funding pubkeys and
   both signatures.
 - Uses `ChannelMonitor::channel_keys_id` (exposed in the paired
   rust-lightning fork) + `SignerProvider::derive_channel_signer` to
   rederive the holder signer and obtain the holder funding pubkey,
   disambiguating the lex-sorted pair returned by the witness.
 - Strips the witness to produce the unsigned commit tx.

Cargo.toml: adds a `[patch]` section pointing lightning-* git deps at
the rsafier/rust-lightning fork branch `rfc/channel-keys-id-accessor`,
and adds the `unsafe_revoked_tx_signing` feature to the `lightning` dep.

Consumers:
 - `lightningdevkit/ldk-server` gains a `GetChannelAttestations` RPC
   wrapping this call (RFC: "include commitment-bundle data on
   channel-state events").
 - Downstream attestor binaries (Go / Rust) consume the bundle and emit
   operator-signed artifacts verifiable by any party holding the
   operator's Ed25519 public key.
OP_2 lives at byte 69 and OP_CHECKMULTISIG at byte 70 in the 71-byte
BOLT-3 funding redeem script. Previous offsets were off by one (and the
operator-precedence goof compared a 2-byte slice to a 1-byte array).
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