rust-payjoin icon indicating copy to clipboard operation
rust-payjoin copied to clipboard

Backward compatible receiver spec compliance

Open benalleng opened this issue 4 months ago • 5 comments

In adding some test coverage on the v2 receiver I created a test that modifies the pjos parameter of a backwards-compatible v2 receiver to enable output substitution to test the logic that handles the session when this happens. The spec states that this shouldn't be allowed https://github.com/bitcoin/bips/blob/master/bip-0077.md#payjoin-uri. Our current implementation simply reverts this param back to disabled after the receiver receives a response from the sender.

The unique part about this is that v2 receivers CANNOT at any point create a session that has output substitution enabled in a normal flow, so to get to this state something must have already gone wrong or been modified.

The spec is not entirely clear as to whether we should deny a backwards-compatible receiver that accepts output substitution and end early, or simply correct the param to disable output substitution and continue.

Thanks to @spacebear21 for pointing this out in our implementation https://github.com/payjoin/rust-payjoin/pull/928#discussion_r2259548275

The question we have from this is, Should we continue to allow backwards-compatible v2 receivers that have somehow created a session that allows output substitution to continue with just a modification to their pjos param or should we end early and force a new session to start hopefully with the correct params?

benalleng avatar Aug 11 '25 18:08 benalleng

I thought about this more: even if our v1 sender implementation does the "right" thing and enforces pjos=0, the v2 receiver is still technically not backwards-compatible because we can't guarantee the same behavior for other v1 sender implementations. My hunch is that a v2 receiver with pjos=1 is effectively signaling they are not backwards-compatible, and should reject v1 senders (unless the sender enforces disable_output_substitution as our implementation does). @DanGould / @nothingmuch thoughts?

spacebear21 avatar Aug 13 '25 15:08 spacebear21

v2 receiver is still technically not backwards-compatible because we can't guarantee the same behavior for other v1 sender implementations

Can you expand on this? How would you expect of other v1 sender implementations to differ?

a v2 receiver with pjos=1 is effectively signaling they are not backwards-compatible, and should reject v1 senders

This is a clever solution but I'm concerned about a side effect. Wouldn't a v1 sender then still potentially send a plaintext payload, exposing a cluster of their inputs to the directory without any chance of upside (unlike when they're given the opportunity to payjoin)? A silly hack would be to use pjos=2 which should (? needs verification) cause an error for v1 senders. I'd want to check that behavior with implementations in the wild to see if they'd still post a payload and leave themselves vulnerable if not.

DanGould avatar Aug 13 '25 17:08 DanGould

v2 receiver is still technically not backwards-compatible because we can't guarantee the same behavior for other v1 sender implementations

Can you expand on this? How would you expect of other v1 sender implementations to differ?

Disregard, I was under the impression our v1 sender forced the parameter back to disabled but it's the v2 receiver that does that.

spacebear21 avatar Aug 13 '25 17:08 spacebear21

Another way to make v1 senders fail v2 urls would be to display an http:// url by default, since authenticated encryption is done using HPKE and rather than TLS and bip78 says:

The sender must ensure that the URL refers to a scheme or protocol using authenticated encryption, for example TLS with certificate validation, or a .onion link to a hidden service whose public key identifier has already been communicated via a TLS connection. Senders SHOULD NOT accept a URL representing an unencrypted or unauthenticated connection.

DanGould avatar Aug 17 '25 17:08 DanGould

for bip (3)21 URIs to convey bip 77 only and not bip 78 is supported, my preferred approach is to add a new key like &pjv2=<mailbox endpoint url> instead of &pj=<mailbox endpoint url> that otherwise has exactly the same meaning. this requires a very minor spec change that we have previously discussed.

the scheme in the URI is technically ignored by the relay which assumes https. this is because if the relay doesn't use https as a client or as a server then the same payload is visible to its AS and the user's ISP, even when the relay is honest that makes makes it possible for the AS to link specific directory requests to metadata if it colludes with the directory. however, this seems like kind of a footgun that relies on conservative implementation of bip 78 and may have undesirable side effects.

overloading the pjos field also isn't entirely obvious since that field has to do with output substitution, and again depends on strict implementation of bip 78

nothingmuch avatar Aug 20 '25 23:08 nothingmuch