Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@21M4TW@madelinevibes@daywalker90
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@21M4TW@madelinevibes@daywalker90
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@21M4TW@madelinevibes@daywalker90
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@21M4TW@madelinevibes@daywalker90
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

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

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery - #8853

Open
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key
Open

Changed/Added: Bolt12 deterministic path key for offer paths' node IDs recovery#8853
21M4TW wants to merge 2 commits into
ElementsProject:masterfrom
21M4TW:bolt12_deterministic_path_key

Conversation

@21M4TW

Copy link
Copy Markdown
Contributor

This PR is an extension to #8238. In addition to signing invoices that don't have an offer_issuer_id using the key from the last used blinded_path node, this new PR generates the private path key deterministically (see derive_first_path_privkey from common/blindedpath.c) in such situation and it is then used to recover the offer paths' node IDs (unblind_paths from common/blindedpath.c). It also derives the path_pubkey that is then used to tweak the private key to sign the invoice. In contrast, in #8238, path_pubkey was derived using information from the onion message, but the offer paths' node IDs could not be recovered, as the path_privkey information was lost after the offer was generated.

So in addition to generating offers without an issuer_id and signing related invoices using the proper key, this PR recovers the offer paths' node IDs, which will enable the generation of invoices with matching paths (addressing #8041) in a future PR.

In this PR, the first path private key for a given path is generated using
e_0 = HMAC256(\text{"first_path_privkey"}, SHA256(path_id || N_0 || path_index))

where path_id is the secret stored in the offer's encrypted_data_tlv and that is only known by the payee, and where N_0 and path_index are the first_node_id and the 64-bit index of the path, respectively.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.
  • Important All PRs must consider how to reverse any persistent changes for tools/lightning-downgrade

offers: offers that include offer_paths now omit offer_issuer_id and
sign with the blinded path alias' key by default. An optional force_issuer_id
field is also added to the offer command so the offer_issuer_id is
included even when not required to reach the issuer due to the existence
of offer_paths.
@21M4TW
21M4TW requested a review from cdecker as a code ownerJanuary 19, 2026 15:38
…m the offer's `path_id`, `N_0` and the path's index so the offer's paths can be recovered when an invoice is fetched. Offer paths' node IDs and the used path index stored in struct invreq.
@21M4TW
21M4TWforce-pushed the bolt12_deterministic_path_key branch from 2e09992 to 62435d8CompareJanuary 25, 2026 03:30
@madelinevibesmadelinevibes added this to the v26.09 milestone Jun 15, 2026
@madelinevibes

Copy link
Copy Markdown
Collaborator

@21M4TW would love to see the conflicts resolve so we can merge this

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@21M4TW@madelinevibes@daywalker90