BOLT 12: first draft of payer proofs#1295
Conversation
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
jkczyz
left a comment
There was a problem hiding this comment.
Some minor comments from a first pass. Should have more feedback once we attempt to implement this.
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
vincenzopalazzo
left a comment
There was a problem hiding this comment.
Thanks for this spec! I am finishing to implement it in ldk and have some feedback from implementation experience:
- The nonce hash notation needs clarification (see inline comment)
- Test vectors would be extremely valuable for cross-implementation compatibility. Wondering if you already had some draft implementation where we can compare the tests vectors?
Happy to provide my implementation's test vectors once the ambiguities are resolved.
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix
Implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. This allows proving that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature, and a payer signature. Key additions: - Extend merkle.rs with selective disclosure primitives for creating and reconstructing merkle trees with partial TLV disclosure - Add payer_proof.rs with PayerProof, PayerProofBuilder, and UnsignedPayerProof types for building and verifying payer proofs - Support bech32 encoding with "lnp" prefix
Add a Rust CLI tool that generates and verifies test vectors for BOLT 12 payer proofs as specified in lightning/bolts#1295. The tool uses the rust-lightning implementation from lightningdevkit/rust-lightning#4297. Features: - Generate deterministic test vectors with configurable seed - Verify test vectors from JSON files - Support for basic proofs, proofs with notes, and invalid test cases - Uses refund flow for explicit payer key control Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A PayerProof cryptographically proves that a BOLT 12 invoice was paid by demonstrating possession of the payment preimage, a valid invoice signature over a merkle root (via selective disclosure), and a payer signature proving who authorized the payment. Key components: - `PayerProofBuilder`: constructs proofs with selective disclosure, supporting both direct signing and derived key signing from `ExpandedKey` + `Nonce` - `PayerProof`: verifiable proof with bech32 encoding (lnp HRP) - Selective disclosure via merkle tree: omitted TLV fields are represented by minimized markers and missing hashes, allowing verification of the invoice signature without revealing all fields - `compute_selective_disclosure` / `reconstruct_merkle_root`: build and verify merkle proofs using n-node in-place tree traversal
Implements payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer.
Implements payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer.
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I created a tool for the test vectors https://github.com/vincenzopalazzo/payer-proof-test-vectors, and this is the most recent one https://github.com/vincenzopalazzo/payer-proof-test-vectors/blob/main/test_vectors.json |
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement payer proofs for BOLT 12 invoices as specified in lightning/bolts#1295. A payer proof cryptographically demonstrates that a BOLT 12 invoice was paid using selective disclosure of invoice fields, the payment preimage, and signatures from both the invoice issuer and the payer. The selective disclosure mechanism uses a merkle tree over the invoice's TLV fields, allowing the payer to reveal only chosen fields while proving the full invoice was signed by the issuer. Privacy-preserving omitted markers hide the actual TLV type numbers of undisclosed fields. PayerProofBuilder provides two signing modes: an explicit signing function for callers with direct key access, and automatic key re-derivation from ExpandedKey + Nonce + PaymentId for the common case where invoice requests used deriving_signing_pubkey. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Using the described algorithm for creating and using `missing_hashes`, they don't always end up in ascending order. Interestingly, the rust implementation bent over backwards to implement the spec, and is far more complex because of it. Also, the rust version didn't include `omitted_tlvs` when there were no tlvs omitted: this seems reasonable. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Add the payer proof types, selective disclosure merkle support, parsing, and tests for constructing and validating BOLT 12 payer proofs from invoices. This implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. Missing hashes in a proof are emitted in the DFS traversal order defined by the spec. The BOLT 12 payer proof spec test vectors from bolt12/payer-proof-test.json (full disclosure, minimal disclosure, with payer note, and left-subtree omitted) validate the end-to-end output. The parser rejects unknown even TLVs in every sub-stream range (offer, invoice request, invoice, payer-proof/signature, and the three experimental ranges) via the `tlv_stream!` macro's unknown-even fallback, and rejects types in the unused gap between the signature range and the experimental ranges via the all-bytes-consumed check in `ParsedMessage::try_from`. Co-Authored-By: Rusty Russell <rusty@rustcorp.com.au> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the payer proof types, selective disclosure merkle support, parsing, and tests for constructing and validating BOLT 12 payer proofs from invoices. This implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. Missing hashes in a proof are emitted in the DFS traversal order defined by the spec. The BOLT 12 payer proof spec test vectors from bolt12/payer-proof-test.json (full disclosure, minimal disclosure, with payer note, and left-subtree omitted) validate the end-to-end output. The parser rejects unknown even TLVs in every sub-stream range (offer, invoice request, invoice, payer-proof/signature, and the three experimental ranges) via the `tlv_stream!` macro's unknown-even fallback, and rejects types in the unused gap between the signature range and the experimental ranges via the all-bytes-consumed check in `ParsedMessage::try_from`. Co-Authored-By: Rusty Russell <rusty@rustcorp.com.au> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to them. Haven't fixed up my implementation or redone test vectors yet, but this moves the proof fields out of the signature range. Also combined the two separate (!) requirements sections. Suggested-by: @t-bast
Implement the BOLT 12 payer-proof authentication change discussed at: lightning/bolts#1295 (comment) # What changes The current spec (BOLT 12 PR 1295 line 1052) signs only SHA256(payer_signature.note || invoice_merkle_root) with `invreq_payer_id`. Tamper-resistance for the remaining payer-proof TLVs (`preimage`, `omitted_tlvs`, `missing_hashes`, `leaf_hashes`) holds only transitively. Any future payer-side TLV outside the "either part of the already-signed invoice or has an out-of-band binding" set silently loses authentication. See module docs for the concrete `payer_timestamp` example. This commit implements t-bast's proposed alternative: 1. The `payer_signature` is computed as a tagged signature over the merkle root of all payer-proof TLVs except the `payer_signature` TLV being signed (i.e. the standard BOLT 12 signing scheme, applied to the payer-proof TLV stream itself). 2. `note` becomes its own TLV (`PAYER_PROOF_PAYER_NOTE_TYPE`, placeholder type 252) and is a regular merkle leaf instead of being bundled inside the `payer_signature` TLV. 3. `payer_signature` becomes a plain `bip340sig` like every other signature TLV in BOLT 12. # Code-level changes `lightning/src/offers/merkle.rs` - Refactor `root_hash` into `root_hash_filtered(tlv_stream, include)` so callers can supply a custom inclusion predicate. The original `root_hash` keeps its semantics (excludes `SIGNATURE_TYPES`). - Add `TaggedHash::from_tlv_stream_bytes_filtered(tag, bytes, include)` for the payer-proof signing path. `lightning/src/offers/payer_proof.rs` - Replace the `PayerSignatureWithNote { sig, note }` bundle with a plain `Signature` + a separate `payer_note: Option<String>` field on both `PayerProofContents` and `PayerProofTlvStream`. - Add `PAYER_PROOF_PAYER_NOTE_TYPE = 252` (placeholder, see TODO below). - Replace `payer_signature_hash(note, invoice_merkle_root)` with `payer_proof_signature_hash(bytes)`, which computes the tagged hash over the proof's own merkle root, excluding only the `payer_signature` TLV. - `serialize_payer_proof` now takes `Option<&Signature>`. The builder calls it twice: first with `None` to get the bytes used to compute the signing hash, then with `Some(sig)` to produce the final proof bytes. Both produce the same merkle root because the filter excludes type 250 in either case. - Verifier flow becomes the two signature checks promised by the spec proposal: payer signature against the payer-proof merkle root, then issuer signature against the reconstructed invoice merkle root. Preimage check is unchanged. - Update the manually-built `UnsignedPayerProof` test fixtures to follow the same two-pass build (placeholder hash, then real hash after `serialize_payer_proof(None)`). - Update `test_round_trip_..._signature_range_tlv` to assert the new (stricter) semantics: under full-tree signing, even unknown odd TLVs in the signature range break the payer signature. - Update `test_parsing_rejects_unknown_even_tlvs_in_every_range` to probe type 254 instead of 252 (252 is the new `payer_note` type and is therefore "known"). - Mark `check_against_spec_vectors` as `#[ignore]`: the spec test vectors in `bolt12/payer-proof-test.json` are pinned to the previous signing scheme and need to be regenerated once the BOLT 12 spec change lands. Every other `payer_proof::*` test passes on the new scheme. # What still depends on the spec - **Final TLV layout.** Today the data-bearing payer-proof TLVs (`preimage` 242, `omitted_tlvs` 244, `missing_hashes` 246, `leaf_hashes` 248) and the proposed `payer_note` (252) all sit in the `SIGNATURE_TYPES` range (240..=1000). The standard BOLT 12 merkle root excludes that whole range; this commit therefore uses a payer-proof-specific filter that excludes only TLV 250. If the spec reallocates these TLVs to a new dedicated range outside `SIGNATURE_TYPES`, swap `payer_proof_signature_hash` to use the standard `from_valid_tlv_stream_bytes` helper and drop the custom filter. The wire format stays the same; only the chosen TLV type numbers shift. - **Final type number for `payer_note`.** Held at 252 here. Update `PAYER_PROOF_PAYER_NOTE_TYPE` and regenerate the spec test vectors when the spec assigns one. # Verification - `cargo +1.75.0 check --tests -p lightning`: clean (no errors, no new warnings). - `cargo +1.75.0 fmt --all -- --check`: clean. - `cargo +1.75.0 test -p lightning --lib`: 1358 passed, 10 ignored, 0 failed. The single ignored payer-proof test is `check_against_spec_vectors` (pinned to the old scheme). Tracking issue for the spec discussion: lightning/bolts#1332 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the payer proof types, selective disclosure merkle support, parsing, and tests for constructing and validating BOLT 12 payer proofs from invoices. This implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. Missing hashes in a proof are emitted in the DFS traversal order defined by the spec. The BOLT 12 payer proof spec test vectors from bolt12/payer-proof-test.json (full disclosure, minimal disclosure, with payer note, and left-subtree omitted) validate the end-to-end output. The parser rejects unknown even TLVs in every sub-stream range (offer, invoice request, invoice, payer-proof/signature, and the three experimental ranges) via the `tlv_stream!` macro's unknown-even fallback, and rejects types in the unused gap between the signature range and the experimental ranges via the all-bytes-consumed check in `ParsedMessage::try_from`. Co-Authored-By: Rusty Russell <rusty@rustcorp.com.au> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment was marked as off-topic.
This comment was marked as off-topic.
As specified in lightning/bolts#1295
As specified in lightning/bolts#1295
d6dbb9d to
1553230
Compare
Test vectors, fixed and expanded to cover more tests:
Successful cases:
full_disclosure
minimal_disclosure
with_note
left_subtree_omitted
empty_proof_omitted_tlvs_explicit
Failing cases:
missing_invreq_payer_id
missing_invoice_payment_hash
missing_invoice_node_id
missing_signature
missing_proof_preimage
missing_proof_missing_hashes
missing_proof_leaf_hashes
missing_proof_signature
wrong_proof_preimage
proof_omitted_tlvs_not_ascending
proof_omitted_tlvs_contains_zero
proof_omitted_tlvs_contains_signature_field
proof_omitted_tlvs_contains_proof_field
proof_omitted_tlvs_contains_high_field
proof_omitted_tlvs_contains_included_tlv_field
proof_omitted_tlvs_not_sequential
proof_leaf_hashes_too_few
proof_leaf_hashes_too_many
proof_missing_hashes_too_few
proof_missing_hashes_too_many
wrong_invoice_signature
wrong_proof_signature
contains_invreq_metadata
1553230 to
93c7f98
Compare
I've buffed up the test vectors more, updated with latest names etc. How do they look? |
| { | ||
| "type": 1005, | ||
| "len": 0, | ||
| "hex": "" | ||
| }, |
There was a problem hiding this comment.
All valid test vectors include an empty note (but with the TLV field provided, which means it should be taken into account in the merkle tree covered by the proof_signature). Is that expected? It may be worth having one of those test vectors completely omit this TLV for completeness?
| "invoice_merkle_root": "cb9e0c81bb39fc244f9f523c748ab4de0e09f1a5fef74359c2e1f7cc7cdc7447", | ||
| "invoice_sighash": "538aab18f82285032db132cecf7275ba2cbb462ef1f4d151588f836d0ce47aae", | ||
| "invoice_signature": "fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577", | ||
| "proof_merkle_root": "cb9e0c81bb39fc244f9f523c748ab4de0e09f1a5fef74359c2e1f7cc7cdc7447", |
There was a problem hiding this comment.
I'm unable to reproduce the test vectors in eclair, and I suspect that this is the culprit: in all test vectors, you have proof_merkle_root == invoice_merkle_root. I thought that proof_merkle_root would contain the merkle root of the tree created from the proof TLVs (result.proof_fields with proof_signature filtered out), which is different from the tree created from the invoice TLVs. I then expected that this would be signed using the payer_id as detailed in the "Signature calculation" section. Is my understanding incorrect? Or is there an unrelated issue (maybe with the way I create the merkle tree of the proof TLVs), in which case it would be helpful for debugging if the test vector could contain the merkle root of the proof TLV tree?
Another related question: when building the merkle tree of the proof fields, should the leftmost leaf be a 0x0000 empty invreq_metadata, or is it omitted from the tree entirely (which IMO would be the preferred option since it isn't in the proof_fields)?
There was a problem hiding this comment.
I think I had the same consideration in #1295 (comment), posting in there to avoid missing inside the github interface
There was a problem hiding this comment.
So what exactly does proof_signature sign? A) invoice root only; B) proof TLV root only; C) some combined tree/root containing both invoice reconstruction and proof fields?
The rationale says it should be B but the vectors currently look like A.
As specified in lightning/bolts#1295
| 7. All-zero offer_id == gratuitous payment. | ||
| 8. Streaming invoices? | ||
| 9. Re-add recurrence. | ||
| 10. Re-add `invreq_refund_for` to support proofs. |
There was a problem hiding this comment.
Q: should this line be dropped?
| - Otherwise, if `proof_omitted_tlvs` is empty: | ||
| - The *marker number* is 1. | ||
| - Otherwise: | ||
| - The *marker number* is one greater than the last `proof_omitted_tlvs` entry. |
There was a problem hiding this comment.
should we mention marker number needs to be in the range? Based on the requirements here we can generate a marker number that's 240, which will be rejected by the reader.
| "invoice_merkle_root": "cb9e0c81bb39fc244f9f523c748ab4de0e09f1a5fef74359c2e1f7cc7cdc7447", | ||
| "invoice_sighash": "538aab18f82285032db132cecf7275ba2cbb462ef1f4d151588f836d0ce47aae", | ||
| "invoice_signature": "fbb932e6a9d5b4d88ca0ddc9cf9f8cc880ef41e3ec9574da89f624db898ab3e9d3ed6caa8744633b855167da009119d9834ae71f7b06f02732dc4c1debab0577", | ||
| "proof_merkle_root": "cb9e0c81bb39fc244f9f523c748ab4de0e09f1a5fef74359c2e1f7cc7cdc7447", |
There was a problem hiding this comment.
So what exactly does proof_signature sign? A) invoice root only; B) proof TLV root only; C) some combined tree/root containing both invoice reconstruction and proof fields?
The rationale says it should be B but the vectors currently look like A.
| - `proof_omitted_tlvs` is not one greater than: | ||
| - an included TLV number, or | ||
| - the previous `proof_omitted_tlvs` or 0 if it is the first number. | ||
| - `proof_leaf_hashes` does not contain exactly one hash for each non-signature TLV field. |
There was a problem hiding this comment.
IIUC, i think this should be,
| - `proof_leaf_hashes` does not contain exactly one hash for each non-signature TLV field. | |
| - `proof_leaf_hashes` does not contain exactly one nonce hash for each included invoice TLV field, excluding signature TLV fields and proof-only TLV fields. |
or maybe this,
- the number of nonce hashes in `proof_leaf_hashes` is not equal to the number of included invoice TLV fields:
- excluding signature TLV fields,
- excluding proof-only TLV fields,
- excluding omitted invoice TLV fields, which are covered by `proof_missing_hashes`.
Add the payer proof types, selective disclosure merkle support, parsing, and tests for constructing and validating BOLT 12 payer proofs from invoices. This implements the payer proof extension to BOLT 12 as specified in lightning/bolts#1295. Missing hashes in a proof are emitted in the DFS traversal order defined by the spec. The BOLT 12 payer proof spec test vectors from bolt12/payer-proof-test.json (full disclosure, minimal disclosure, with payer note, and left-subtree omitted) validate the end-to-end output. The parser rejects unknown even TLVs in every sub-stream range (offer, invoice request, invoice, payer-proof/signature, and the three experimental ranges) via the `tlv_stream!` macro's unknown-even fallback, and rejects types in the unused gap between the signature range and the experimental ranges via the all-bytes-consumed check in `ParsedMessage::try_from`. Co-Authored-By: Rusty Russell <rusty@rustcorp.com.au> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Needs: