Reply blocks
A particular challenge in packet-based onion routing is when Alice sends a message to Bob, how can Bob reply.
The trivial solution is to attach a header to the message with a "source address". This is okay in the normal case, where anonymity against the counterparty is not desired, but not okay if Alice wants to hide her identity from Bob.
In that case, we use reply blocks. Let's say sends a message to over intermediary relays . To help respond, attaches a reply block containing:
A randomly generated fingerprint: the source ID
The first hop on the return-path, . WLOG we assume there is only one path between the parties, and the reply packet follows the same path but in the other direction.
An onion-encrypted header encoding the path , as well as ephemeral pubkeys for each layer
An ephemeral onion public key
then encrypts and stuffs the response (which is an InnerPacket) into 8192 bytes using and an randomly generated, ephemeral onion public key. He attaches the header verbatim, then sends the packet off to himself, as the first hop in Earendil routes is always the sending node itself. In 's attempt to peel an layer of the onion while forwarding the packet, the mssage body is garbled (that is, ChaCha20-encrypted, because encryption and decryption are identical for stream ciphers), making this reply packet indistinguishable from forward packets to the next hop, .
The message will eventually pass back to , garbled by the intermediary nodes' attempts to "decrypt" the message with layers of stream-ciphers. But can reconstruct the keystream that was XOR'ed against the payload, and thus reconstruct the ciphertext that sent.
is then able to decrypt the response from .
How this is exposed in the API
The recv_from
function will return a packet and source. This source is either
a direct address: a fingerprint of a relay or client
a reply address: a randomly-generated fingerprint that serves as the source ID
When send_to
processes a reply address, it looks up an unused reply block corresponding to the source ID and routes the message through it.
When a node calls send_to
using an anonymous source address, we send a packet of 8 reply blocks in addition to the message, so that the destination can use them to respond to us.
DoS resistance
Only the last N reply blocks received from a source, within the last T seconds are kept around. Memory usage can be tightly bound with something like a weighted moka
cache.
Source IDs that are too inactive become dead.
Last updated