Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix
  • Loading branch information
nullchinchilla committed Jul 10, 2025
commit e843e838d1c041a2d4f7ab011898dbf1bfe5aabb
41 changes: 41 additions & 0 deletions en/wiki/protocols/lownet-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# LowNet: low-level datagram transport

The lowest-level abstraction in Earendil is **LowNet**, an IP-like, best-effort datagram network.
LowNet hides the physical topology of the Earendil relay graph and gives every node—relay _or_ client—an addressable, flat namespace.

## Address format

LowNet uses addresses that look like the following

```
na-<relay-fingerprint>-<client-id>
```

| Field | Size | Meaning |
|-------|------|---------|
| `relay‐fingerprint` | 32 B (printed as 64 hex chars) | the `RelayFingerprint` (public key hash) of some relay |
| `client-id` | 8 B unsigned integer | `0` identifies the relay itself; any other value is a client slot behind that relay. |


For instance, `na-d4c5b1e6a0ff…ce09` refers to client #42 behind the relay `d4c5b1e6a0ff…ce09`.


## Datagram structure

Every packet that crosses LowNet is a `Datagram`:

```rust
pub struct Datagram {
pub ttl: u8,
pub dest_addr: NodeAddr,
pub payload: Bytes,
}
```

* **TTL** is decremented at every hop; frames with `ttl == 0` are dropped.
* **Payload** is an opaque byte slice—mixing, congestion control and higher-level framing live at upper layers.
---

## Learn more

* 🗂 **Source code**: <https://github.com/mel-project/earendil/tree/new-refactor/libraries/earendil_lownet>
51 changes: 51 additions & 0 deletions en/wiki/protocols/mix-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Mixnet protocol

The **mixnet** allows communication between any two nodes, _as long as one of them is a relay_. The other party is kept anonymous.

## Packet format

The packet format is based on the `InnerPacket` Rust enum:

```rust
/// Represents the actual end-to-end packet that is carried in the payloads. Either an application-level message, or a batch of reply blocks.
pub enum InnerPacket {
/// Normal messages
Message(Message),
/// Reply blocks, used to construct relay->anon messages
ReplyBlocks(Vec<ReplyBlock>),

/// An inner packet message with corresponding UDP port-like source and destinaton docks
pub struct Message {
pub source_dock: Dock,
pub dest_dock: Dock,
pub body: Vec<Bytes>,
}

pub type Dock = u32;
```

An `InnerPacket` is stuffed into the 8192-byte payload by this process:

1. First, we encode the packet using [bincode](https://docs.rs/bincode/latest/bincode/), getting `box_plaintext`.
2. We then box-encrypt using an ephemeral keypair, whose public half is `box_epk`, getting `box_ciphertext`.
3. We sign `box_epk` with our identity secret key `identity_sk`, whose public half is `identity_pk`, producing `identity_signature`
4. We then package everything into a tuple `(identity_pk, identity_signature, box_ciphertext)`, bincode it, and pad it to 8192 bytes.

This picture roughly illustrates the structure of a fully encoded `InnerPacket` that stores a normal `Message`.

![](../../.gitbook/assets/n2r_innerpacket.png)

## Socket abstraction

The typical interface exposed by the mixnet protocol is not raw functions for sending and receiving packets. Instead, we use a _socket_ abstraction inspired by UDP. Each socket represents an `Endpoint`, a _local fingerprint:dock pair,_ that can receive and send messages. More specifically:

- The user constructs a socket by **binding** to an identity and a dock number.
- This identity can either be the long-term identity of the node, or a temporary anonymous identity.
- The socket has a method to **send** a message to a fingerprint and dock number.
- This formats a message with:
- source identity public key and source dock number taken from the identity and dock number of the socket
- destination identity looked up by fingerprint
- If the socket is bound to a temporary identity, reply blocks must be sent to the destination fingerprint as well so that they can talk back to us.
- There's also a **receive** method, which returns a message and a source endpoint, which consists of a fingerprint and a dock number.
- This blocks until there is an incoming message addressed to the identity and dock number that the socket is bound to.
- In the implementation, there must be some sort of demultiplexing done to separate incoming messages addressed to different sockets.