Skip to content

Use reply key for replyable errors to v2 senders#981

Merged
arminsabouri merged 4 commits intopayjoin:masterfrom
nothingmuch:use-reply-key-for-replyable-errors
Aug 29, 2025
Merged

Use reply key for replyable errors to v2 senders#981
arminsabouri merged 4 commits intopayjoin:masterfrom
nothingmuch:use-reply-key-for-replyable-errors

Conversation

@nothingmuch
Copy link
Copy Markdown
Contributor

@nothingmuch nothingmuch commented Aug 20, 2025

Before some relatively recent spec changes, two messages were written to the same mailbox. The error path was not updated to reflect this, so the sender will not receive it (as it is polling a different mailbox), and the receiver further relied on the directory allowing mailbox contents to be overwritable (or at least accepting the POST request).

With this change a replyable error will be sent to the reply key, assuming one was obtained from the first message. If no key can be recovered from the message then there is no point in sending a reply as the peer is not adhering to the protocol.

Pull Request Checklist

Please confirm the following before requesting review:

  • A human has reviewed every single line of this code before opening the PR (no auto-generated, unreviewed LLM/robot submissions).
  • I have read CONTRIBUTING.md and rebased my branch to produce hygienic commits.

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from fbcd995 to 1d288a9 Compare August 20, 2025 23:41
Comment thread payjoin/src/core/receive/v2/mod.rs Outdated
Copy link
Copy Markdown
Collaborator

@arminsabouri arminsabouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from 1d288a9 to 6ef1772 Compare August 22, 2025 14:30
@nothingmuch nothingmuch marked this pull request as ready for review August 22, 2025 14:31
@nothingmuch nothingmuch changed the title [WIP] Use reply key for replyable errors Use reply key for replyable errors to v2 senders Aug 22, 2025
@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Aug 22, 2025

Pull Request Test Coverage Report for Build 17306832046

Details

  • 221 of 222 (99.55%) changed or added relevant lines in 7 files are covered.
  • 1 unchanged line in 1 file lost coverage.
  • Overall coverage increased (+0.2%) to 85.967%

Changes Missing Coverage Covered Lines Changed/Added Lines %
payjoin-cli/src/app/v2/mod.rs 2 3 66.67%
Files with Coverage Reduction New Missed Lines %
payjoin/src/core/receive/v2/session.rs 1 97.35%
Totals Coverage Status
Change from base Build 17305633599: 0.2%
Covered Lines: 8166
Relevant Lines: 9499

💛 - Coveralls

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from 6ef1772 to 7b18d7b Compare August 22, 2025 14:34
Comment thread payjoin/src/core/receive/v2/mod.rs Outdated
Comment on lines +92 to +96
/// The mailbox ID where the receiver expects the sender's Original PSBT
/// payjoin proposal.
pub(crate) fn proposal_mailbox_id(&self) -> ShortId {
short_id_from_pubkey(self.receiver_key.public_key())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Original PSBT payjoin proposal" uses both names for the Original PSBT and the Payjoin Proposal. Which one is it? IF from sender, Original, if from receiver, Proposal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i hate that but in the code the typestate is UncheckedProposal so i felt like i had to put that in there to be less confusing...

i guess this should rename UncheckedProposal to UncheckedOriginalPSBT?

also do you mind if Original is renamed to OriginalPSBT for consistency with the spec? Original on its own seems harder to digest even if it's more accurate and less redundant

@nothingmuch
Copy link
Copy Markdown
Contributor Author

there's a big FIXME comment re session_history.extract_err_request, but because optionality of the reply key in the session is also used to distinguish v1/v2 requests internally and lifting reply_key to the Receiver typestate structs themselves is very boilerplate heavy, i left it as is for now even though i was hoping to make some progress on #929 i gave up on that for this PR

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch 2 times, most recently from 51b838e to beeb471 Compare August 22, 2025 17:05
Copy link
Copy Markdown
Collaborator

@arminsabouri arminsabouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach/utACK
Core logic for updating error request mailbox lgmt. The rest of the refactors and naming updates all seem sane as well. Thanks 👍

Comment thread payjoin/src/core/receive/v2/mod.rs Outdated
Comment on lines +134 to +137
// FIXME ideally this should be more like a method of
// Receiver<UncheckedProposal> and subsequent states instead of the
// history as a whole since it doesn't make sense to call it before,
// reaching that state.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most ergonomic approach imo is to have v2 receiver errors to be convertable to json replies iff they are fatal errors. That way you can send the error reply after you fail to process a typestate or send it after you replay the SEL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, that sounds right to me, but i don't see how this would specifically avoid this method of the context being callable even in states where it doesn't make sense... i guess the mailbox ID would be derived as part of that conversion? would it be captured into the fatal error?

either way do you mind opening an issue with a bit more detail than this comment? i would prefer to do it as a followup

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. In fact we have this issues. But your comment about the different mailbox id is new context that we haven't considered yet. #793

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch 2 times, most recently from d4b0295 to 373a802 Compare August 28, 2025 18:00
@nothingmuch nothingmuch requested a review from DanGould August 28, 2025 18:03
@nothingmuch
Copy link
Copy Markdown
Contributor Author

nothingmuch commented Aug 28, 2025

@DanGould i've renamed as per your suggestions in DM, but having second thoughts about updating the spec since part of our rationale for "Original PSBT" is that it exactly matched BIP 78's terminology, confusing though it may be. i do think it's appropriate in the code since the state is not just the psbt but the psbt, the params and the reply key (although the OriginalPayload struct does not include the reply key as that is v2 specific).

@nothingmuch
Copy link
Copy Markdown
Contributor Author

i guess ship has sailed on calling them "sender's proposal" and "receiver's proposal" lol

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from 373a802 to 99b6b91 Compare August 28, 2025 18:10
@nothingmuch
Copy link
Copy Markdown
Contributor Author

hmm, this now introduces mutant failures but i'm not convinced they're worth addressing, if i understand Armin's suggestions then I think that would eliminate them, the only caller of session_context is extract_err_request so if we refactor that i think that method could go away so i am inclined to ignore these mutants

@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from 99b6b91 to 36580dd Compare August 28, 2025 19:29
@arminsabouri
Copy link
Copy Markdown
Collaborator

hmm, this now introduces mutant failures but i'm not convinced they're worth addressing, if i understand Armin's suggestions then I think that would eliminate them, the only caller of session_context is extract_err_request so if we refactor that i think that method could go away so i am inclined to ignore these mutants

Makes sense. No need to write tests rn that we would have to remove soon

@benalleng
Copy link
Copy Markdown
Collaborator

benalleng commented Aug 28, 2025

If I'm not mistaken these are the same mutants covered by these tests #915 (comment), right?

@arminsabouri
Copy link
Copy Markdown
Collaborator

arminsabouri commented Aug 28, 2025

Please push the mutations ignore and I will review and drop a re-ack Or migrate the tests mentioned here #981 (comment)

Comment thread payjoin/src/core/receive/v2/mod.rs Outdated
@nothingmuch
Copy link
Copy Markdown
Contributor Author

If I'm not mistaken these are the same mutants covered by these tests #915 (comment), right?

oh right i had mentally filed that into the next PR to rebase forgetting that it's based on this one... i will add them to this one thanks again for that!

Before some relatively recent spec changes, two messages were written to
the same mailbox. The error path was not updated to reflect this, so the
sender will not receive it (as it is polling a different mailbox), and
the receiver further relied on the directory allowing mailbox contents
to be overwritable (or at least accepting the POST request).

With this change a replyable error to a v2 sender will be sent to the
reply key, assuming one was obtained from the first message. If no key
can be recovered from the message then there is no point in sending a
reply as the peer is not adhering to the protocol.
@nothingmuch nothingmuch force-pushed the use-reply-key-for-replyable-errors branch from 36580dd to 68608bf Compare August 28, 2025 20:12
Copy link
Copy Markdown
Collaborator

@arminsabouri arminsabouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK

session_context: SHARED_CONTEXT.clone(),
original: OriginalPayload { psbt: PARSED_ORIGINAL_PSBT.clone(), params },
session_context: SessionContext {
reply_key: Some(HpkeKeyPair::gen_keypair().public_key().clone()),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHARED_CONTEXT should have a fresh keypair. Why would we create a new one here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub(crate) static SHARED_CONTEXT: Lazy<SessionContext> = Lazy::new(|| SessionContext {
        address: Address::from_str("tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4")
            .expect("valid address")
            .assume_checked(),
        directory: EXAMPLE_URL.clone(),
        mailbox: None,
        ohttp_keys: OhttpKeys(
            ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"),
        ),
        expiry: SystemTime::now() + Duration::from_secs(60),
        receiver_key: HpkeKeyPair::gen_keypair(),
        reply_key: None,
        amount: None,
    });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHARED_CONTEXT is used in both uninitialized and uncheckedpayload tests, it's not correct to have a reply key set in the uninitialized state

@arminsabouri arminsabouri merged commit 676f2a5 into payjoin:master Aug 29, 2025
14 checks passed
@nothingmuch nothingmuch deleted the use-reply-key-for-replyable-errors branch September 25, 2025 20:04
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.

5 participants