Skip to content

Lightning-paid wireguard config delivery and payment hash mismatch fixes#3

Open
libertas-primordium wants to merge 2 commits into
btcjt:mainfrom
libertas-primordium:lnpay-wireguard-config-delivery
Open

Lightning-paid wireguard config delivery and payment hash mismatch fixes#3
libertas-primordium wants to merge 2 commits into
btcjt:mainfrom
libertas-primordium:lnpay-wireguard-config-delivery

Conversation

@libertas-primordium

Copy link
Copy Markdown

Summary

This PR fixes phoenixd-backed Lightning purchases so they can complete the full Europa purchase flow: payment reconciliation, session activation, and purchaser-only WireGuard config delivery.

The previous phoenixd path had two related problems:

  1. Europa generated a local preimage/payment hash and stored that local payment hash in the pending session row, but phoenixd created the actual payable invoice with its own payment hash. Settlement polling then checked phoenixd for a hash phoenixd did not recognize, so paid sessions could remain pending.

  2. Europa encrypted the LNURL successAction payload with the locally generated preimage, but wallets receive the actual payment preimage from the invoice they paid. With phoenixd-backed invoices, those preimages did not match, so the wallet could not decrypt the returned WireGuard config.

This branch fixes both issues by treating the backend-created phoenixd invoice as the source of truth for both the payment hash and the payment preimage.

The previous smaller PR that only fixed the payment-hash mismatch can be closed in favor of this branch because this branch includes that fix plus the complete WireGuard config delivery fix.

Code changes

Use the backend invoice payment hash for settlement checks

When creating a pending LNURL purchase session, Europa now stores the paymentHash returned by the Lightning backend-created invoice instead of always storing the locally generated hash.

This preserves the existing fallback behavior while using the actual payable invoice hash when the backend supplies one.

Before, phoenixd-backed payments could reach phoenixd successfully while Europa still left the session pending, because Europa was polling phoenixd for the locally generated hash rather than the hash of the invoice phoenixd actually created.

Fetch and return the real phoenixd invoice preimage

The phoenixd backend now creates the invoice, then immediately queries phoenixd’s incoming-payment record by the returned paymentHash.

Phoenixd exposes the real invoice preimage before payment through its incoming-payment record. Europa can therefore use that real preimage for the LNURL AES successAction.

This keeps the LNURL flow secure and purchaser-gated: the wallet can only decrypt the success payload after paying the invoice and receiving the matching payment preimage.

Encrypt LNURL successAction with the backend preimage

The LNURL callback now encrypts the WireGuard config delivery payload using the preimage returned by the Lightning backend.

For phoenixd, this is the actual phoenixd invoice preimage. That makes the encrypted successAction decryptable by the paying wallet.

Update LNURL AES encryption to LUD-10-compatible AES-CBC

The successAction AES encryption was updated to use the LNURL LUD-10-compatible AES-CBC flow rather than AES-GCM.

This was required for wallet compatibility. In live testing, Phoenix Wallet stopped crashing when viewing the receipt after the AES mode was corrected.

Add a browser-compatible config viewer route

Phoenix Wallet can decrypt the successAction, but it presents the decrypted payload as an “open link” action. A raw WireGuard config is not a URL, and a data: URL caused Phoenix to crash when tapped.

This branch adds a /config-viewer route and changes the decrypted success payload to a normal HTTPS URL:

https://<public_host>/config-viewer#<base64url-wireguard-config>

The WireGuard config is placed in the URL fragment after #. Browser URL fragments are handled client-side and are not sent to the server in the HTTP request. The /config-viewer page decodes the fragment locally and displays a copyable WireGuard config.

This keeps the config delivery flow wallet-compatible while avoiding sending the decrypted config back to the server through the request path.

Security properties

The WireGuard config delivery remains purchaser-gated:

  • The LNURL successAction payload is encrypted with the actual Lightning payment preimage.
  • The paying wallet only receives that preimage after successfully paying the invoice.
  • Unpaid users cannot decrypt the successAction.
  • The config viewer receives the WireGuard config through the URL fragment, which is decoded locally in the browser.
  • The server receives only GET /config-viewer; the fragment containing the config is not included in the HTTP request.
  • The generated config does not include the purchaser’s WireGuard private key. It contains a placeholder, and the purchaser must insert the private key matching the public key submitted during purchase.

The operator system necessarily creates the session config, and phoenixd necessarily knows the invoice preimage because it created the invoice. The security goal here is that third parties and unpaid users cannot decrypt or retrieve the config, while the purchaser can access it after payment.

Testing and verification

This branch was validated on a live phoenixd-backed Europa node.

Payment hash mismatch validation

Before the fix:

  • Phoenixd received a 100 sat Lightning payment normally.
  • Phoenixd balance increased.
  • fee_credit_sat stayed at 0, confirming the payment was no longer just fee credit.
  • Europa still left the session pending.
  • No WireGuard peer was automatically added.

After storing the backend-created invoice payment hash:

  • A fresh 100 sat Lightning purchase increased phoenixd balance.
  • The Europa session changed from pending to active automatically.
  • The WireGuard peer was added automatically.
  • No manual database update or manual wg set command was required.

Phoenixd preimage validation

A direct phoenixd API test confirmed that after creating an invoice, phoenixd’s incoming-payment record for the returned paymentHash exposes a valid 64-character preimage before payment.

That made it possible for Europa to fetch the actual phoenixd invoice preimage and use it for LNURL AES encryption without modifying phoenixd.

Wallet receipt/config delivery validation

The config delivery flow was tested with Phoenix Wallet:

  • Payment succeeds.
  • Phoenix Wallet can open the payment receipt.
  • Phoenix Wallet can decrypt the LNURL AES successAction.
  • The attached message link opens in the browser.
  • The config viewer page displays the WireGuard config.
  • The config can be copied into WireGuard.
  • The session activates automatically on the server.
  • The WireGuard peer is added automatically.

Build and test validation

The code was validated with:

  • pnpm build
  • pnpm test

During development, a test expectation for the canonical SHA-256 hash of "abc" was corrected after the AES encryption test updates. The final expected value matches the standard SHA-256 hash:

ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

Notes

This branch supersedes the earlier PR that only fixed the payment-hash mismatch. That smaller fix restored automatic activation, but it did not solve the wallet receipt/config delivery problem.

This PR includes both pieces needed for the full phoenixd-backed Lightning purchase flow:

  1. Correct settlement reconciliation using the backend invoice payment hash.
  2. Correct LNURL config delivery using the actual phoenixd invoice preimage and a wallet-compatible config viewer URL.

Store the payment hash returned by the Lightning backend when creating a pending LNURL purchase session instead of always storing the locally generated preimage hash.

This fixes phoenixd-backed payment reconciliation because phoenixd creates invoices with its own payment hash while Europa previously stored the generated preimage hash and then polled phoenixd for a hash it did not recognize.

Validated with pnpm install --frozen-lockfile, pnpm build, and pnpm test. Also validated on a live phoenixd-backed Europa node: after this change, a 100 sat Lightning purchase increased phoenixd balance, the session changed from pending to active automatically, and the WireGuard peer was added without manual database or wg intervention.
Use phoenixd's backend-created invoice payment hash and preimage for Lightning purchases so settlement polling and LNURL AES successAction encryption both reference the actual payable invoice.

Phoenixd creates invoices with its own payment hash and preimage, while Europa previously encrypted the WireGuard config with a locally generated preimage and stored the locally generated payment hash. That caused phoenixd-backed sessions to remain pending before the payment-hash fix, and caused wallets to be unable to decrypt the returned successAction payload.

Update the phoenixd backend to fetch the incoming payment record after invoice creation and return the real phoenixd preimage. Update LNURL config delivery to encrypt the successAction with that backend preimage and store the backend invoice payment hash for settlement checks.

Also update LNURL AES encryption to use the LUD-10-compatible AES-CBC mode, then wrap the decrypted WireGuard config in a browser-compatible HTTPS config-viewer URL whose fragment carries the config locally without sending it back to the server in the HTTP request.

Validated on the live libertas-ams-1 phoenixd-backed Europa node: Phoenix Wallet payment succeeds, the receipt opens, the attached message link opens the config viewer, the WireGuard config is displayed for the purchaser, the session activates automatically, and the WireGuard peer is added without manual database or wg intervention.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant