I tested the deployed app/bundle. The core local check is useful, but I found two correctness issues and two privacy-hardening items worth fixing:
Mixed-case BOLT11 invoices are accepted.Gc() lowercases the full input before validating it (const t=e.toLowerCase()), so an otherwise-valid invoice with one letter's case changed passes. Bech32 decoders MUST reject mixed case (BIP 173), and BOLT11 uses that encoding. Check before normalization:
const hasLower = /[a-z]/.test(raw)
const hasUpper = /[A-Z]/.test(raw)
if (hasLower && hasUpper) throw new Error("mixed-case bech32")
const normalized = raw.toLowerCase()
“Only the person who actually paid can produce it” is not true. The receiver generated/knows the preimage, and forwarding nodes learn it during settlement. A matching preimage proves knowledge of the settlement secret tied to that invoice; it does not prove the submitter's identity as payer. The valid node signature authenticates the invoice's signer, not the payer. For an amountless invoice, the preimage also cannot prove the amount paid. I would rewrite the hero/export around those exact guarantees.
The sensitive values do appear to stay inside the app, but “no data leaves your machine” is broader than the deployed page: it requests Google Fonts and umami.bilthon.dev. Either self-host both or say specifically “your invoice and preimage are never transmitted.” A restrictive CSP would make that promise independently auditable; the current response has no CSP/HSTS/referrer-policy headers.
payment-proof.txt exports the full invoice and preimage. That is a legitimate receipt, but add a warning before download/share: publishing it reveals payment linkage and invoice metadata. “Treat this file as sensitive proof” would be enough.
The mixed-case bug is the one I'd fix first because it is a standards-compliance error with a tiny patch. If useful, I can turn this into a compact regression-test checklist. And if this review saved you time, a zap on the comment is appreciated ⚡
I tested the deployed app/bundle. The core local check is useful, but I found two correctness issues and two privacy-hardening items worth fixing:
Gc()lowercases the full input before validating it (const t=e.toLowerCase()), so an otherwise-valid invoice with one letter's case changed passes. Bech32 decoders MUST reject mixed case (BIP 173), and BOLT11 uses that encoding. Check before normalization:const hasLower = /[a-z]/.test(raw) const hasUpper = /[A-Z]/.test(raw) if (hasLower && hasUpper) throw new Error("mixed-case bech32") const normalized = raw.toLowerCase()Spec: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#uppercase-lowercase
umami.bilthon.dev. Either self-host both or say specifically “your invoice and preimage are never transmitted.” A restrictive CSP would make that promise independently auditable; the current response has no CSP/HSTS/referrer-policy headers.payment-proof.txtexports the full invoice and preimage. That is a legitimate receipt, but add a warning before download/share: publishing it reveals payment linkage and invoice metadata. “Treat this file as sensitive proof” would be enough.The mixed-case bug is the one I'd fix first because it is a standards-compliance error with a tiny patch. If useful, I can turn this into a compact regression-test checklist. And if this review saved you time, a zap on the comment is appreciated ⚡