Hi everyone,
I’m a postgraduate student trying to understand how anonymous replies work in the Nym mixnet client. I’ve been reading the reply/SURB code and would like to check whether my understanding is correct.
What I think happens when the replier sends a reply
1. Building the inner payload (NymPayloadBuilder in common/nymsphinx/src/preparer/payload.rs)
From prepare_reply_chunk_for_sending, the replier calls build_reply with reply_surb.encryption_key() (the random SurbEncryptionKey, which I’ll call k).
As far as I can tell:
-
The SURB-ACK block is not encrypted with
k. -
The key digest
H(k)is also not encrypted withk. -
Only the reply fragment is encrypted with
k.
So the inner layout seems to be:
[ SURB-ACK prefix ] || [ H(k) ] || [ Enc_k(fragment) ]
2. Wrapping with the SURB (apply_surb in common/nymsphinx/anonymous-replies/src/reply_surb.rs)
Then apply_surb passes the full NymPayload into SURB::use_surb, which uses Payload::encapsulate_message with the per-hop payload key material from the SURB (the keys derived from the SURB route and mix nodes — Ki, not with k.
So my understanding is:
-
The ack prefix and
H(k)are not encrypted underk. -
The whole padded inner blob (ack + digest + encrypted fragment) is layer-encrypted with the SURB’s mix-route payload keys K1,…,KL.
-
Mix nodes peel those layers; at the recipient’s gateway the plaintext is recovered and the SURB-ACK prefix can be split off.
What the replier receives with each reply SURB
When the original sender attaches reply SURBs, ReplySurb::to_bytes() appears to send:
SurbEncryptionKey (k) || SURB_bytes
where SURB_bytes includes the precomputed header, first-hop address, and payload key seeds/keys for every hop on that SURB’s route.
Question: For a route with e.g. 5 mix hops, does each ReplySurb the replier receives therefore include:
-
one random
k(SurbEncryptionKey) for encrypting the reply fragment, and -
the full per-hop payload key material for that SURB’s route (all Kiseeds or keys — one per mix hop)?
Summary — is this correct?
-
kencrypts only the fragment, not the SURB-ACK bytes and notH(k). -
apply_surbthen encrypts the entire inner payload (including the unencrypted ack block and digest) with the SURB per-hop payload keys from the mix route. -
The gateway can split the ack after Sphinx unwrap because that prefix was never under
k. -
Each reply SURB given to the replier includes
kplus all route payload key material for that specific SURB path (not keys for the whole network).
If any part of this is wrong or I’m mixing up header keys vs payload keys, I’d really appreciate corrections or pointers to docs/code I should read next.
Thanks!