Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Session Initialization Implicit #364

Merged
merged 12 commits into from
Oct 22, 2024

Conversation

DanGould
Copy link
Contributor

@DanGould DanGould commented Oct 11, 2024

Close #363

A session is now initialized by generating keys and sharing them out
of band.

A POST request pushes data to a subdirectory, by convention defined as the receiver's public key. GET requests attempt to fetch data. For the sender, this means they poll GET requests instead of POST requests so bandwidth use is reduced.

Payjoin v1 backwards compatibility is supported by adding v1 aware handlers, one for POST requests directly at the relay, since v1 is unaware of OHTTP, and a PUT handler on the subdirectory's OHTTP endpoint that saves the updated Payjoin PSBT to another Redis column. The v1 POST handler is waiting on an update to that column to repond to the v1 sender before the request times out.

Lastly, Message A is now encrypted with an ephemeral "encapsulation key" so that the sender's "reply key," used to identify the sender subdirectory where Message B is stored is encrypted inside Message A's ciphertext in order to prevent the Payjoin Directory from finding it. This prevents the Payjoin Directory from being able to relate the sender and receiver subdirectories by searching for the subdirectory identifying "reply key" in Message A's associated data. All they'll find is the ephemeral key that has no relation to the subdirectory id.

@DanGould DanGould force-pushed the implicit-sessions branch 2 times, most recently from 8f5aca5 to a6c2a8a Compare October 14, 2024 21:48
@DanGould DanGould force-pushed the implicit-sessions branch 2 times, most recently from b307fa2 to f050e77 Compare October 15, 2024 15:23
Copy link
Contributor

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

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

just some nits. still familiarizing myself with the code, so not yet comfortable expressing stronger opinions

payjoin/src/receive/v2/mod.rs Outdated Show resolved Hide resolved
payjoin/src/send/mod.rs Outdated Show resolved Hide resolved
payjoin/src/send/mod.rs Outdated Show resolved Hide resolved
@nothingmuch
Copy link
Contributor

oh, one more thing: maybe consider renaming ActiveSession to Session because the distinction is now meaningless

@DanGould
Copy link
Contributor Author

oh, one more thing: maybe consider renaming ActiveSession to Session because the distinction is now meaningless

Perhaps even just Receiver to map against the Sender

@DanGould DanGould force-pushed the implicit-sessions branch 3 times, most recently from 60e41fb to c2eafff Compare October 15, 2024 17:38
@DanGould DanGould changed the title WIP: Make session initialization implicit Make Session Initialization Implicit Oct 15, 2024
@DanGould
Copy link
Contributor Author

The tests appear to be flakier than they were before, getting "Ohttp relay is long running" from the ohttp_relay shut down before the test completes on occasion 🤔

@DanGould DanGould marked this pull request as ready for review October 15, 2024 17:51
Copy link
Collaborator

@0xBEEFCAF3 0xBEEFCAF3 left a comment

Choose a reason for hiding this comment

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

This is a rather large PR. Probably going to review in chunks. ApproachACK. Leaving Nits in a few places. Please feel free to ignore or tell me why i'm wrong about certain comments. This is the first time im looking at a PR for this repo. Thanks

payjoin-cli/src/app/v2.rs Show resolved Hide resolved
payjoin-cli/src/app/v2.rs Show resolved Hide resolved
@@ -4,8 +4,8 @@ use futures::StreamExt;
use redis::{AsyncCommands, Client, ErrorKind, RedisError, RedisResult};
use tracing::debug;

const RES_COLUMN: &str = "res";
const REQ_COLUMN: &str = "req";
const DEFAULT_COLUMN: &str = "";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Something descriptive maybe useful here when debugging in prod. even "default-col"

payjoin/src/hpke.rs Outdated Show resolved Hide resolved
payjoin/src/hpke.rs Outdated Show resolved Hide resolved
payjoin/src/hpke.rs Show resolved Hide resolved
payjoin/src/hpke.rs Outdated Show resolved Hide resolved
payjoin/src/hpke.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

Loving this protocol upgrade.

I'm confused by the sender polling in long_poll_post - it looks like they're polling POST requests but based on my understanding of the BIP77 updates and PR description shouldn't they be polling GETs?

payjoin-directory/src/lib.rs Outdated Show resolved Hide resolved
payjoin-directory/src/lib.rs Outdated Show resolved Hide resolved
payjoin/tests/integration.rs Outdated Show resolved Hide resolved
loop {
let (req, ohttp_ctx) = v2_ctx.extract_req(self.config.ohttp_relay.clone())?;
let response = http
.post(req.url)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the sender be polling GET requests here?

Copy link
Contributor Author

@DanGould DanGould Oct 17, 2024

Choose a reason for hiding this comment

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

ohttp requests are always POSTs so that they're indistinguishable. Only when the OHTTP encapsulation is removed does the target server see the inner GET request.

see https://github.com/payjoin/rust-payjoin/pull/364/files#diff-e1a2a0997d5241c211b358d2ea2be04fc1a4aa680ac543480188d7b4ab63ff72R448

I wonder if there's a better way to represent this in the state machine than POST/GET to avoid this confusion

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see now the GET request is constructed in extract_req, makes sense. It might help if the OHTTP post was extracted into a make_ohttp_request(req) function or something along those lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea I plan to write for both v2.rs and integration.rs in a follow up

Copy link
Contributor

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

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

messages a and b are distinguishable due to the use of Auth mode in message a

payjoin/src/hpke.rs Outdated Show resolved Hide resolved
@DanGould
Copy link
Contributor Author

After 9f5a170 this should close #371 in implementation. We'll have to add the details to BIP77 but I think assuming this passes review it should be ready to merge.

A session is now initialized by generating keys and sharing them out
of band. The semantics of the protocol are otherwise unchanged.

Because sessions are implicit the typestate is now called `Receiver`.
@DanGould
Copy link
Contributor Author

rebased

DanGould and others added 10 commits October 21, 2024 13:41
Encrypting Message A with an ephemeral "encapsulation key" allows the sender
"reply key" corresponding to its subdirectory to be hidden from the directory.
Since the encapsulation keypair was ephemeral and not known to the
receiver, but used in the Auth pattern it was included as authenticated
associated data in the payload.

This means that encrypt_message_a and encrypt_message_b had
distinguishable bit patterns, the former starting with two uncompressed
curve points (one for the DHKEM and one for this auth key), whereas the
latter only had one (the DHKEM point).

Since the sender's first message establishes a reply key, that key could
be used in a second Auth HPKE setup after the Base setup, in order to
prove that the sender can decrypt the receiver's reply.  However,
incentives are for the sender to provide a valid point, and the reply
key is included in AEAD ciphertext, so this would add complexity without
meaningful improving security or incentive compatibility.
The constants PADDED_PLAINTEXT_{A,B}_LENGTH now represent the maximum
payload size for the input, whereas before the message A constant
included the reply key size. This makes the PayloadTooLarge error
represent a maximum and actual size that correspond to the inputs to
encrypt_message_a and encrypt_message_b.
Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

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

ACK 96c0eb6. I haven't looked very closely at the hpke changes.

Copy link
Contributor

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

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

utACK 96c0eb6

I coudln't review everything in detail since I'm still familiarizing myself with the codebase, and as such I can't really call my fiddling "tested". However the parts I was able to scrutinize I'm comfortable with.

Perhaps consider adding a randomized test for the encrypted payloads' bit uniformity:

  1. generate 8 each of messages a and b, and 16 each of only a, and only b in separate runs
  2. for each of the 256 pairwise combinations, ensure their lengths are equal, XOR the two messages together, and OR this into an accumulator that starts as all 0x00s
  3. if at the end any of the bytes of this accumulator are still 0x00, fail the test

Such a test will generate a false negative with negligible probability if all encrypted messages share an identical byte at a given position by chance. It should fail deterministically before the ellswift changes due to the first byte being 0x01 in all messages in all runs (a only, b only, a+b). It should fail deterministically for a only messages because of the second 0x01 byte at position 65 (the previously aad'd reply key).

@DanGould DanGould merged commit 5f4fe76 into payjoin:master Oct 22, 2024
4 checks passed
DanGould added a commit that referenced this pull request Oct 24, 2024
close #371 

This randomized test will generate a false negative with negligible
probabilityif all encrypted messages share an identical byte at a
given position by chance.
It should fail deterministically if any bit position has a fixed value.

re
#364 (review)
from @nothingmuch

I did check that this test would indeed fail before the ellswift changes
by cherry-picking the test on 9c4880c
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.

Make Session Initialization implicit from the Directory's Perspective
4 participants