Async Payjoin #33684

issue achow101 openend this issue on October 22, 2025
  1. achow101 commented at 11:06 pm on October 22, 2025: member

    This issue is for the tracking and brainstorming of the implementation of BIP 77 Async Payjoin into the wallet.



    Async Payjoin utilizes an external Directory server to allow the Sender and Receiver to communicate with each other without needing to operate any infrastructure. The servers are reached using RFC 9458 Oblivious HTTP (OHTTP) relays to avoid revealing the IPs of the Sender and Receiver to the Directory. All communication between the Sender or Recevier to the OHTTP Relay is encrypted, as described in the OHTTP RFC. Additionally, the payload data itself is encrypted with methodology similar to OHTTP to prevent the Directory from being able to read the transaction data.

    The cryptography uses things that we already have in the project. Specifically, all keys used are EC keys on secp256k1, keys are exchanged using DH, and encryption is done with ChaCha20Poly1305. Pubkeys included directly in the payload (ephemeral keys used to do additional key exchanges for responses) are encoded with ElligatorSwift. All of these algorithms are already included in the project to support BIP 324, so it should not be difficult to reuse them for Async Payjoin.

    OHTTP itself is encrypted using the aforementioned cryptography. It uses plain HTTP, not HTTPS, so there is no need to include anything related to TLS.


    The general operation of Async Payjoin is:

    1. The Receiver produces a Bitcoin URI containing a parameter which includes the URL of a mailbox endpoint on a Directory server (i.e. some URL for the Sender to POST to). The URL additionally contains the public key of the Directory itself, and an ephemeral(ish)public key of the Receiver.
    2. The Receiver provides the URI to the Sender out of band.
    3. The Sender generates an ephemeral public key, performs a key exchange with the Receiver’s ephemeral key to compute a shared secret. This shared secret is used with ChaCha20-Poly1305 to encrypt:
      • A new ephemeral pubkey for the key exchange for the reply (Reply Key)
      • The Original PSBT which pays the receiver
    4. The Sender connects to the specified Directory server via a OHTTP Relay, performing a DH key exchange with the Directory public key, and encrypting the ElligatorSwift encoding of their Reply Key and the encrypted payload produced in the previous step. The payload is POSTed to the specified mailbox endpoint.
    5. The Receiver polls the Directory server at the mailbox endpoint until it receives the Sender’s payload.
    6. The Receiver performs DHKE with the Sender’s ephemeral key, then decrypts the encrypted payload.
    7. The Receiver produces a Proposal PSBT which is the Sender’s Original PSBT modified with the Receiver’s inputs and outputs, as well as any necessary signatures and witnesses.
    8. The Receiver generates a new ephemeral key to perform DHKE with the Sender’s Reply Key, and uses the shared secret to encrypt their Proposal PSBT
    9. The Receiver connects to the previously specified Directory via a OHTTP Relay, and provides an encapsulated payload of the ElligatorSwift encoding of their ephemeral key. The payload is POSTed to a new mailbox endpoint derived from the Sender’s Reply Key.
    10. The Sender polls the Directory server at the mailbox endpoint derived from their Reply Key until it receives the Receiver’s payload.
    11. The Sender performs DHKE with the Receiver’s new ephemeral key, decrypts the Proposal PSBT, validates and signs it. The Sender broadcasts the final transaction.

    Additionally, the original Bitcoin URI contains an expiration time. This is used by both the Sender and Receiver to have a timeout on polling the Directory. The Receiver can also broadcast the Original PSBT and the Sender should stop polling if it sees that be broadcast.


    There are a few questions that need to be answered for implementing Async Payjoin

    • If we are the Receiver, how do we get the pubkey of the Directory. Some suggestions are:
      • The user gets it out of band somehow
      • OHTTP specifies a way to do key discovery by querying the server directly without OHTTP. But this both requires TLS, and reveals our IP unless some other IP hiding method is used.
      • A set of keys are hard coded into the software. (ew)
    • How do we choose which OHTTP relay to use?
    • What is the polling interval?
    • What shows in the GUI when we are waiting for and polling the Directory, for both Receivers and Senders?
    • We should not be automatically signing transactions for our users:
      • How will Senders using the GUI be notified and prompted when a reply is received so that the PSBT can be signed?
      • How will Senders using the CLI know when a reply was received and that a PSBT needs to be signed?
  2. DanGould commented at 11:51 pm on October 22, 2025: none

    There are a few questions that need to be answered for implementing Async Payjoin

    • If we are the Receiver, how do we get the pubkey of the Directory. Some suggestions are:
      • The user gets it out of band somehow

    I think getting the keys from another receiver’s URI was the simple out of band bootstrap mechanism @nothingmuch and I had in mind. The Payjoin URI shared by a receiver contains the OHTTP Key Config (“pubkey of the directory.”) Cache that key and allow receiving to that directory from then on until the expiration. Before expiration, attempt to fetch updated keys close to the expiration period using OHTTP requests to the .well-known/ohttp-gateway endpoint after that first fetch.

    • How do we choose which OHTTP relay to use?

    Probably hard code some options like is done for DNS seeds, and then allow configuration. You can see how this is done as of today in our reference implementation’s RelayManager.

    • What is the polling interval?

    The reference uses long polling, so the directory won’t return a result for the timeout and polling does not wait at all. The default is 30 seconds (https://github.com/payjoin/rust-payjoin/blob/3e30f4ed37d91daa2add446bf8b7eaeadbb71d3f/payjoin-directory/src/config.rs#L83). I can make an edit to BIP 77 to include this recommendation since a typical forced timeout for http requests with many clients is 1 minute.

    • What shows in the GUI when we are waiting for and polling the Directory, for both Receivers and Senders?

    “Pending” + The expiration time + the ability to unilaterally broadcast the fallback. our reference payjoin-cli history subcommand shows these as well as historic error states.

    • We should not be automatically signing transactions for our users:

    My thinking here is that authorization for a given transaction within a timeframe with fee rate is what’s done on the first signing for a sender, and another prompt is not strictly necessary as long as appropriate checks pass, but otherwise perhaps a prompt to action does need to be shown.

    • How will Senders using the GUI be notified and prompted when a reply is received so that the PSBT can be signed?

    TBD

    • How will Senders using the CLI know when a reply was received and that a PSBT needs to be signed?

    Other than looking at payjoin-cli history for inspiration TBD. Perhaps then a resume command could be issued from the CLI.

  3. bitcoin deleted a comment on Oct 24, 2025
  4. glozow added the label Feature on Oct 29, 2025
  5. glozow added the label Wallet on Oct 29, 2025
  6. glozow added the label Tracking Issue on Oct 29, 2025
  7. fanquake commented at 10:04 am on October 30, 2025: member
    Given that this is adding new features to the GUI. Is the plan to add them to the “legacy” GUI, the new QML GUI, or implement Payjoin into both? cc @hebasto

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-10-31 03:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me