Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all \x3Cpre>\x3Ccode> blocks (function() { function addCopyButtons() { document.querySelectorAll('pre code').forEach(function(codeBlock) { if (codeBlock.parentElement.hasAttribute('data-copy-added')) return; codeBlock.parentElement.setAttribute('data-copy-added', 'true'); var btn = document.createElement('button'); btn.textContent = 'Copy'; btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;'; btn.onmouseover = function() { this.style.opacity = '1'; }; btn.onmouseout = function() { this.style.opacity = '0.7'; }; btn.onclick = function() { navigator.clipboard.writeText(codeBlock.textContent).then(function() { btn.textContent = 'Copied!'; setTimeout(function() { btn.textContent = 'Copy'; }, 1500); }); }; codeBlock.parentElement.style.position = 'relative'; codeBlock.parentElement.appendChild(btn); }); } addCopyButtons(); // Re-run on dynamic content var observer = new MutationObserver(addCopyButtons); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); js: exclude payer-proof data-range TLVs from invoice reconstruction by vincenzopalazzo · Pull Request #33 · rustyrussell/bolt12 · GitHub
Skip to content

js: exclude payer-proof data-range TLVs from invoice reconstruction - #33

Merged
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f
Jun 26, 2026
Merged

js: exclude payer-proof data-range TLVs from invoice reconstruction#33
vincenzopalazzo merged 1 commit into
rustyrussell:masterfrom
vincenzopalazzo:claude/practical-black-439c0f

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator

Summary

  • The TS payer-proof reader treated any non-signature-range TLV as an included invoice field, so an unknown TLV in the payer-proof data range (1001..=999_999_999) was mis-counted as an invoice merkle leaf and the proof was wrongly rejected on the proof_leaf_hashes count check.
  • Fix: a record is a genuine invoice field only when type < 240 (normal) or type >= 1_000_000_000 (experimental). This mirrors the rust-lightning reference (tlv_stream_iter), which strips both the signature range (240..=1000) and the data range (1001..=999_999_999) when reconstructing the invoice merkle root.
  • Effect: the reader now tolerates ignorable proof extensions (an unknown odd data-range TLV the writer signed over) instead of rejecting them — matching the reference impl in [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297.

Review Notes

  • Production-safety review passed (2 rounds). The only types whose classification changed are [1006, 999_999_999]; no valid invoice field can live there per the invoice writer rule, and all 5 valid spec vectors + 23 invalid spec vectors still pass.
  • Not a forgery vector: proof_signature commits to everything outside 240..=1000 (data-range TLVs included), so an injected data-range TLV invalidates proof_signature unless the payer signed it. The invoice signature is reconstructed over invoice fields only, matching the issuer's original root.
  • Adds a regression test that signs a proof carrying an unknown odd data-range TLV (1007) and asserts it still verifies. It fails before the fix (leaf_hashes count (11) must match included non-signature TLV count (12)).

Decision Log

Hardest decision: whether to silently exclude unknown data-range TLVs (as done here, matching rust tlv_stream_iter) versus rejecting them. I went with exclude-and-tolerate because the reference does, and because proof_signature already commits to them so toleration is safe. The alternative — full BOLT TLV even/odd "it's ok to be odd" enforcement on the proof stream — is a broader, library-wide change (js/src/tlv.ts) that this repo doesn't do anywhere yet, so I kept it out of scope.

Alternatives rejected:

  • Reject any unknown data-range TLV: diverges from the rust reference and breaks forward-compat for ignorable extensions.
  • Hardcode the data-range bounds inline at the call site: less readable; extracted isIncludedInvoiceType instead to document the rule once.

Least confident about: the even/odd TLV semantics gap. An unknown even data-range TLV is silently excluded here but rust's typed TLV stream would reject it as an unknown mandatory field. That is a pre-existing, library-wide TLV-handling difference (the TS lib never enforces even/odd mandatory rules), not specific to payer proofs, so I left it for a follow-up. Happy to revisit if you want it enforced here.

Test plan

  • npx tsc --noEmit clean
  • npm test — offers 53, payer-proof 35 (was 34), generated 22, verify 3
  • Regression test fails without the fix, passes with it

@vincenzopalazzovincenzopalazzo left a comment

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Self-review: I found one spec-compliance issue in payer-proof TLV parsing. The inline comment captures the specific concern.

Comment threadjs/src/payer_proof.ts
The reader treated any non-signature-range TLV as an included invoice
field, so a TLV in the payer-proof data range (1001..=999_999_999) was
mis-counted as an invoice merkle leaf and the proof was wrongly rejected
on the leaf-hash count.
Classify a record as a genuine invoice field only when its type is < 240
(normal) or >= 1_000_000_000 (experimental), mirroring rust-lightning's
tlv_stream_iter. Within the data range the known proof fields 1001..=1005
are matched explicitly; any other type is handled per the BOLT "it's ok
to be odd" rule -- even types rejected at parse, odd types ignored (and
still committed by proof_signature).
Adds regression tests: an unknown odd data-range TLV (1007) signed into a
proof still verifies, while an unknown even one (1006) is rejected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzoforce-pushed the claude/practical-black-439c0f branch from f01d52c to f14c01dCompareJune 26, 2026 12:27
@vincenzopalazzo
vincenzopalazzo merged commit 8c90caf into rustyrussell:masterJun 26, 2026
4 checks passed
@vincenzopalazzovincenzopalazzo mentioned this pull request Jun 26, 2026
3 tasks
vincenzopalazzo added a commit that referenced this pull request Jun 26, 2026
Bump version 0.1.1 -> 0.1.2 and add a changelog. This release ships the
payer-proof data-range TLV handling fix (PR #33): the reader excludes the
payer-proof data range (1001..=999_999_999) from invoice merkle
reconstruction and rejects unknown even TLVs in that range per the BOLT
"it's ok to be odd" rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto 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

@vincenzopalazzo