fix(payjoin): correct malformed receive BIP21 URI - #2259
Conversation
The receive payment request was rebuilt through dart:core Uri, which percent-encoded the pj endpoint's :// (HTTPS%3A%2F%2F...) and dropped the pjos parameter, producing an out-of-spec URI that other wallets could not parse for sending. Append amount/message verbatim to the PDK-produced URI instead, keeping the pj endpoint byte-identical and last (BIP-77 SHOULD). Advertise pjos=0 when the PDK omits it, matching our receiver which never substitutes outputs. Add unit tests for the generated URI and a decode round-trip.
|
Claude finished @ethicnology's task in 2m 10s —— View job Review: payjoin receive URI fix
Solid, well-scoped fix. Building the URI by string-appending to the PDK output instead of round-tripping through 🟡 1. Small amounts render in scientific notation → out-of-spec BIP21
if (amountBtc > 0) 'amount=$amountBtc',
So a receive request for under 100 sats produces Note this is not a regression — the existing non-payjoin path has the identical bug at ✅ Things I checked that are correct
🔎 Minor — please confirmThe Overall: correct and a clear improvement. The only actionable item is the small-amount formatting (low severity — sub-100-sat receives are uncommon — but cheap to fix while you're here). |
double.toString() emits scientific notation below 1e-6 (e.g. amount=1e-8 for 1 sat), which is not a valid BIP21 amount. Add formatBtcAmount (8 decimals, trailing zeros trimmed) and apply it to the payjoin, bitcoin and liquid receive paths. Add tests covering sub-100-sat amounts.
|
Good catch @claude on the amount formatting. Added Re: the base address — intended. The payjoin receiver is created with Is it good enough now @claude ? |
|
Claude finished @ethicnology's task in 1m 19s —— View job Re-review: amount formatting fix
Yes — this resolves the issue. 👍
So the pre-existing non-payjoin bug is fixed in the same pass, as you said. The implementation is sound. Tests look good. 1/10/100-sat cases assert plain decimals, and the trailing-zero trim is covered ( Base-address question — confirmed resolved. Your explanation matches: the receiver is created via One housekeeping note (not blocking): this PR still targets Note: I couldn't execute Nice work — LGTM. ✅ |
|
FYI there shouldn't be any wire format changes from 0.23 to 1.0.0-rc3 or cross version interop problems other than the URI. Technically 0.23 is out of spec but 1.0.0-rc3 iirc does accept the old URI for compatibility's sake for the time being so we don't kill clients' whose newest versions still aren't up to 0.25+. This will not be forever.
There's no cross-version interop issues between 0.23,0.24,0.25, 1.0.0-rc3 afaiu beyond this issue if you consider it a wire format issue |
"Backwards-compatible receivers MUST disable output substitution by setting pjos=0 to prevent modification by a malicious directory" this is NOT about BBMobile substituting outputs, actually pjos=0 + output substitution is OK for BIP 77. it's for the BIP 78 backwards compatibility, where a malicious directory/proxy in the middle could actually STEAL FUNDS by malleating the plaintext if this isn't set properly |
Payjoin receive URI — fix + current limitation
What was wrong
Our receive payment request was rebuilt through
dart:core Uri, whichpercent-encoded the pj endpoint's
://(HTTPS%3A%2F%2F…) and droppedpjos. The result was an out-of-spec, malformed BIP21 URI.The fix
We now take the PDK-produced URI (already valid) and append
amount/messageverbatim instead of re-encoding it. The pj endpoint stays byte-identical and
last (BIP-77 SHOULD), and we advertise
pjos=0when the PDK omits it — whichmatches our receiver, since it never substitutes outputs. Covered by unit
tests: old-behavior characterization, new generator, and a decode round-trip.
pjoswere our bug (BULL generation), notthe
bip21_uripackage — the package's encoder keeps://clean.-→+). That'sa separate, known issue, documented as skipped tests.
We have not changed the payjoin engine. 0.23.0 uses the old
+fragmentseparator; final BIP-77 uses
-. So:not fix cross-version interop.
bip21_uridecoder's-→+rewrite is currently load-bearing: it'swhat lets us accept a final-BIP-77 (
-) URI on the send path against the 0.23PDK. Removing it before upgrading would break sending.
land together — not before.