---
title: LIBP2P-MIX
name:  Libp2p Mix Protocol
status: raw
category: Standards Track
tags:
editor: Akshaya Mani <akshaya@status.im>
contributors:
---

## Abstract

This document specifies the Mix protocol, a custom protocol within the
[libp2p](https://libp2p.io) framework designed to enable anonymous communication
in peer-to-peer networks. The Mix protocol allows libp2p nodes to send messages
without revealing the sender's identity to intermediary nodes or the recipient.
It achieves this by using the [Sphinx packet format](https://www.researchgate.net/publication/220713667_Sphinx_A_Compact_and_Provably_Secure_Mix_Format),
which encrypts and routes messages through a series of nodes (mix nodes)
before reaching the recipient.

Key features of the protocol include:

i. Path selection for choosing a random route through the network via multiple
mix nodes.\
ii. Sphinx packet construction and processing, providing cryptographic
guarantees of anonymity and security.\
iii. Pluggable spam protection mechanism to prevent abuse of the mix network.\
iv. Delayed message forwarding to thwart timing analysis attacks.

**Protocol identifier:** `"/mix/1.0.0"`

Note: The Mix Protocol is designed to work alongside existing libp2p protocols,
allowing for seamless integration with current libp2p applications while
providing enhanced privacy features. For example, it can encapsulate messages
from protocols like [GossipSub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.2.md)
to ensure sender anonymity.

## Background

libp2p protocols do not inherently protect sender identities.

The Mix protocol enhances anonymity in libp2p by implementing a mix network,
where messages are anonymized through multiple relay nodes before reaching the
intended recipient. The Sphinx packet format is a well-researched component which
this specification leverages to offer strong anonymity properties by concealing
sender and recipient information at each relay.

Using this approach, even the nodes relaying messages cannot determine the
sender or final recipient, within a robust adversarial model. This decentralized
solution distributes trust among participants, eliminating single points of
failure and enhancing overall network resilience. Additionally, pluggable
spam protection mechanism and delayed forwarding address common attacks on
anonymity networks, such as spam and timing analysis.

The Mix protocol is designed with flexibility in mind, allowing for
pluggable components such as spam protection, peer discovery, and
incentivization mechanisms. This design choice enables the protocol to evolve
and adapt to different network requirements and constraints. This also leaves
room for future enhancements such as cover traffic generation.

By incorporating these features, the Mix protocol aims to provide a robust
anonymity layer within the libp2p ecosystem, enabling developers to easily
incorporate privacy features into their applications.

## Specification

### 1. Protocol Identifier

The Mix protocol is identified by the string `"/mix/1.0.0"`.

### 2. Custom Mix Protocol

The Mix protocol is designed as a standalone protocol,
identified by the protocol identifier `"/mix/1.0.0"`.
This approach allows the Mix protocol to operate independently,
decoupled from specific applications,
providing greater flexibility and reusability across various libp2p protocols.
By doing so, the Mix protocol can evolve independently,
focusing on its core functionality without being tied to the development
and maintenance cycles of other protocols.

#### 2.1 Mix Nodes Roles

All nodes participating in the Mix protocol are considered as mix nodes. They
have the capability to create/process and forward Sphinx packets. Mix nodes can
play different roles depending on their position in a particular message path:

- **Sender Node**
  - A mix node that initiates the anonymous message publishing process.
  - Responsible for:
    - Path selection.
    - Sphinx packet creation.
    - Initiating the message routing through the mix network.
  - Must run both the Mix protocol instance and the instance of the libp2p
    protocol for the message being published (_e.g.,_ GossipSub, Ping, etc.).
- **Intermediary Mix Node**
  - A mix node that is neither the sender nor the exit node in a message path.
  - Responsible for:
    - Receiving Sphinx packets.
    - Processing (decrypting and re-encrypting) Sphinx packets.
    - Forwarding processed packets to the next node in the path.
  - Only needs to run the Mix protocol instance.
- **Exit Node**
  - The final mix node in a message path.
  - Responsible for:
    - Receiving and processing the final Sphinx packet.
    - Extracting the original message.
    - Disseminating the decrypted message using the appropriate libp2p protocol.
  - Must run both the Mix protocol instance and the instance of the libp2p
    protocol for the message being published.

#### 2.2 Roles Flexibility

A single mix node can play different roles in different paths:

- It can be a sender node for messages it initiates.
- It can be an intermediary node for messages it is forwarding.
- It can be an exit node for messages it is disseminating.

#### 2.3 Incentives

To publish an anonymous libp2p message (_e.g.,_ GossipSub, Ping, etc.), nodes
MUST run a mix node instance. This requirement serves as an incentive for nodes
to participate in the mix network, as it allows them to benefit from the
anonymity features while also contributing to the network's overall anonymity
and robustness.

#### 2.4 Node Discovery

All mix nodes participate in the discovery process and maintain a list of
discovered nodes.

- **Bootstrap Nodes**

  i. The network has a set of well-known bootstrap nodes that new mix nodes
  can connect to when joining the network.\
  ii. The bootstrap nodes help new mix nodes discover other active mix nodes in
  the network.

- **Discovery**
  
  i. All mix nodes publish their Ethereum Node Records (ENRs) containing:

  ```json
  {
    "id": "v4",
    "multiaddr": "/ip4/192.0.2.1/udp/9000/quic",
    "ed25519": "0x5a6fcd3e9d6a5e4d5f71e7e5b4cfa9b7b73d9f5f7e9a8b9c5d7f9e8d5a6f7c9e",
    "mix": "/mix/1.0.0",
    "supported_protocols": ["ping", "gossipsub"]
  }
  ```

  **Field Explanations**

  - `id`: Indicates the ENR format version (_e.g.,_ `"v4"`).
  - `multiaddr`: The node's multiaddress, including the transport protocol
    (_e.g.,_ QUIC) and IP address/port.
  - `ed25519`: The node's Ed25519 public key, used for Sphinx encryption.
  - `mix`: Indicates the supported Mix protocol version (_e.g.,_ `"/mix/1.0.0"`).
  - `supported_protocols`: A list of other libp2p protocols supported by the
    node (_e.g.,_ Ping, GossipSub, etc.).
  - Additional fields may be included based on the node's requirements.
  
  ii. The mix nodes use a peer discovery protocol like [WAKU](https://waku.org)/[Discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md):
  
  - Connect to a set of bootstrap nodes when joining the network.
  - Regularly update their list of known peers.
  - Obtain a random sample of nodes that is representative of the network.

- **Path Selection (Message Senders Only)**

  To send an anonymous message, a mix node performs the following actions:
  
  i. Choose a random exit node that supports the required libp2p protocol for
  the message.\
  ii. Select remaining L-1 unique mix nodes randomly without replacement from
  the list of discovered nodes.

- **Forwarding To Next Hop (Intermediary Nodes Only)**

  When a mix node receives an incoming Sphinx packet, it performs the following
  actions:

  i. Decrypts the packet to obtain the next hop multiaddress\
  ii. Checks if the next hop is in the list of discovered nodes.\
  iii. If not, performs discovery for that specific node.\
  iv. Forwards the Sphinx packet to the next hop.

#### 2.5 Protocol Registration

The protocol is registered with the libp2p host using the `"/mix/1.0.0"`
identifier. This identifier is used to establish connections and negotiate the
protocol between libp2p peers.

#### 2.6 Transport Layer

The Mix protocol uses secure transport protocols to ensure confidentiality and
integrity of communications. The recommended transport protocols are
[QUIC](https://datatracker.ietf.org/doc/rfc9000/) or TLS (preferably QUIC
due to its performance benefits and built-in features
such as low latency and efficient multiplexing).

#### 2.7 Connection Establishment

- The sender initiates a secure connection (TLS or QUIC) to the first mix node
  using the libp2p transport.
- The sender uses the `"/mix/1.0.0"` protocol identifier to convey that the
  connection is for the Mix protocol.
- Once the connection is established, the sender can forward Sphinx packets
  using the Mix protocol.
- Subsequent mix nodes in the path follow the same process when forwarding
  messages to other mix nodes.

### 3. Cryptographic Primitives and Security Parameter

- **Security Parameter:** $\kappa = 128$ bits provides a balance between
  security and efficiency.
- **Cryptographic Primitives**
  - **Group G**: Curve25519 elliptic curve offers 128-bit security with small
    (32-byte) group elements, efficient for both encryption and key exchange.
  - **Hash function H**: SHA-256.
  - **KDF:** SHA-256 (truncated to 128 bits).
  - **AES-CTR:** AES-128 in counter mode.
    - **Inputs:** Key `k` (16 bytes), Initialization Vector `iv` (16 bytes),
      Plaintext `p`
    - **Initialization Vector (IV)**: 16 bytes, chosen randomly for each
      encryption.
    - **Plaintext**: Data to be encrypted (_e.g.,_ routing information, message
      payload).
    - **Output**: Ciphertext `c` (same size as plaintext `p`).
    - **Operation**: AES-CTR mode uses key and the counter (`iv`) to produce a
      keystream, which is XORed with the plaintext to produce the ciphertext.
  - **HMAC-SHA-256:** 256-bit MAC (truncated to 128 bits).
    - **Inputs:** Key `k` (16 bytes), Message `m`
    - **Message**: Data to be authenticated (_e.g.,_ $$\beta$$ component).
    - **Output**: MAC `mac` (truncated to 128 bits).
    - **Operation**: HMAC-SHA-256 uses the key and the message to produce a
      hash-based message authentication code.

### 4. Sphinx Packet Format

#### 4.1 Packet Components and Sizes

1. **Alpha ($α$)**: 32 bytes

   - Represents a Curve25519 group element (x-coordinate in GF(2^255 - 19)).
   - Used by mix nodes to extract shared session key using their private key.

2. **Beta ($$\beta$$)**: $((t+1)r + 1)\kappa$ bytes typically, where $r$ is the maximum
   path length.

   - Contains the encrypted routing information.
   - We recommend a reasonable maximum path length of $r=5$, considering
    latency/anonymity trade-offs.
   - This gives a reasonable size of $336$ bytes, when $t = 3$ (refer
     Section 5.2.10 for the choice of $t$).
   - We extend $$\beta$$ to accommodate next hop address and delay below.

3. **Gamma ($γ$)**: $\kappa$ bytes (16 bytes)

   - Output of HMAC-SHA-256, truncated to 128 bits.
   - Ensures the integrity of the header information.

4. **Delta ($$\delta$$)**: The encrypted payload, which can be of variable size.
   - According to the [MixMatch](https://petsymposium.org/popets/2024/popets-2024-0050.pdf)
    paper, the Nym network uses Sphinx packets of a fixed
    size (2413 bytes).
   - Considering this, the maximum $$\delta$$ size can be chosen as 2413 bytes minus
    the header length (which will be derived below).

#### 4.2 Address Format and Delay Specification

In the original
[Sphinx](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf) paper, the
authors use node IDs of size $\kappa$ ($16$ bytes) to represent the next hop
addresses. To accommodate larger addresses, we'll use a combined size of
$t\kappa$ bytes for the address and delay, where $t$ is small (_e.g.,_ $t = 2$
or $3$).

- **Delay**: 2 bytes
  Allows delays up to 65,535 milliseconds ≈ 65 seconds.
- **Address**: $t\kappa-2$ bytes
  This flexible format can accommodate various address types, including:
  - libp2p multiaddress (variable length, typically 32-64 bytes).
  - Custom format with:
    - IP address (IPv4 or IPv6, 4 or 16 bytes)
    - TCP/UDP port number (2 bytes)
    - QUIC/TLS protocol identifier flag (1 byte)
    - Peer ID (32 bytes for Ed25519 or Secp256k1).

The entire Sphinx packet header ($α$, $$\beta$$, and $γ$) can fit within a fixed size
of $32 + (r(t+1)+1)\kappa + 16 = 384$ bytes, leaving ample room for a large $$\delta$$ of
up to $2413 - 384 = 2029$ bytes.

#### 4.3 Message Format

The Mix protocol uses the Sphinx packet format to encapsulate messages and
routing information.

```proto
message SphinxPacket {
  bytes alpha = 1; // 32 bytes
  bytes beta = 2; // 304 - 384 bytes
  bytes gamma = 3; // 16 bytes
  bytes delta = 4; // variable size, max 2029 bytes
}
```

### 5. Handler Function

The [handler function](https://docs.libp2p.io/concepts/fundamentals/protocols/#handler-functions)
is responsible for processing connections and messages for
the Mix protocol. It operates according to the mix node roles (_i.e.,_ sender,
intermediary mix node, or exit node) defined in
[Section 2.1](#21-mix-nodes-roles). This function is crucial for implementing
the core functionality of the mixnet protocol within the libp2p framework.

When a node receives a new stream for the `"/mix/1.0.0"` protocol, the handler
function is invoked. It performs different operations based on the node's role
in the current message path:

- **Role Determination**

  The handler first determines the node's role for the incoming message. This
  is typically done by examining the packet structure and the node's position
  in the network.

- **Packet Processing**

  Depending on the role, the handler processes the Sphinx packet differently:

  - For senders, it creates and sends new Sphinx packets.
  - For intermediary nodes, it processes and forwards existing packets.
  - For exit nodes, it decrypts the final layer and disseminates the original message.

- **Error Handling**

  It manages any errors that occur during packet processing, such as invalid
  MACs or decryption failures.

- **Logging and Metrics**

  The handler is also be responsible for logging important events and
  collecting metrics for network analysis and debugging.

The specific implementation of the handler function for each role (_i.e.,_
sender, intermediary, and exit node) is detailed in the following subsections.

#### 5.1 Sender

1. **Convert the libp2p Message to Bytes**

   Serialize the libp2p message to bytes and store the result in
   `libp2p_message`. This can be done using Protocol Buffers or another
   serialization method.

2. **Apply Spam Protection**

   Apply the chosen spam protection mechanism to the `libp2p_message`.
   This could be Proof of Work (PoW), Verifiable Delay Function (VDF),
   Rate Limiting Nullifier (RLN), or other suitable approaches.

   Refer to [Appendix A](#appendix-a-example-spam-protection-using-proof-of-work)
   for details on the current implementation using PoW.

3. **Prepare the Message**

   Prepare the `message` by combining the `libp2p_message` with any necessary data
   from the spam protection mechanism. The exact format of `message` will depend
   on the chosen spam protection method.

   Note: The spam protection mechanism is designed as a pluggable interface,
   allowing for different methods to be implemented based on network requirements.
   This flexibility extends to other components such as peer discovery and incentivization,
   which are not specified in detail to allow for future optimizations and adaptations.

4. **Perform Path Selection** (refer [Section 2.4](#24-node-discovery))

   - Let the Ed25519 public keys of the mix nodes in the path be
    $y_0,\ y_1,\ \ldots,\ y_{L-1}$.

5. **Wrap Final Message in Sphinx Packet**
   Perform the following steps to wrap `message` in a Sphinx packet:

   a. **Compute** **Alphas ($α_i$**, **$i=0$** to **$L-1$)**

   - Select a random exponent $x$ from $\mathbb{Z}_q^*$.
   - Compute initial alpha $α_0$, shared secret $s_0$, and blinding factor $b_0$:
     - $α_0 = g^x$ using Curve25519 scalar multiplication.
     - $s_0 = y_0^x$, where $y_0$ is the public key of the first hop.
     - $b_0 = H(α_0\ |\ s_0)$, where $H$ is the SHA-256 hash function (refer
      _[Section 3](#3-cryptographic-primitives-and-security-parameter)_ for details).
   - For each node $i$ (from $1$ to $L-1$):
     - $α_i = α_{i-1}^{b_{i-1}}$ using Curve25519 scalar multiplication.
     - $s_i = y_{i}^{x\prod_{\text{j=0}}^{\text{i-1}} b_{j}}$, where $y_{i}$ is
      the public key of the i-th hop.
     - $b_i = H(α_i\ |\ s_i)$, where $H$ is the SHA-256 hash function.

   Note that $\alpha_i$ and $s_i$ are group elements, each 32 bytes long.

   b. **Compute** **Filler Strings ($\phi_i$**, **$i=0$** to **$L-1$)**

   - Initialize $\phi_0$ as an empty string.
   - For each $i$ (from $1$ to $L-1$):

     - Derive the AES key and IV:

       $`\phi_{\mathrm{aes\_key}_{i-1}} = \mathrm{KDF}("aes\_key" \mid s_{i-1})`$

       $`\phi_{\mathrm{iv}_{i-1}} = \mathrm{H}("iv" \mid s_{i-1})`$ (truncated to 128 bits)

     - Compute the filler string $\phi_i$ using $\text{AES-CTR}^\prime_i$,
       which is AES-CTR encryption with the keystream starting from
       index $((t+1)(r-i)+t+2)\kappa$ :

       $`\phi_i = \mathrm{AES\text{-}CTR}'_i(\phi_{\mathrm{aes\_key}_{i-1}},
        \phi_{\mathrm{iv}_{i-1}}, \phi_{i-1} \mid 0_{(t+1)\kappa})`$,
       where $0_{(t+1)\kappa}$ is the string of $0$ bits of length $(t+1)\kappa$.

   Note that the length of $\phi_i$ is $(t+1)i\kappa$.

   c. **Compute** **Betas and Gammas ($$\beta_i$$**, $\gamma_i$, **$i=0$** to **$L-1$)**

   For each $i$ (from $L-1$ to $0$):

   - Derive the AES key, MAC key, and IV:

     $`\beta_{\mathrm{aes\_key}_i} = \mathrm{KDF}("aes\_key" \mid s_i)`$

     $`\text{mac\_key}_{i} = KDF(\text{"mac\_key"}\ |\ s_{i})`$

     $`\beta_{\mathrm{iv}_i} = \mathrm{H}("iv" \mid s_i)`$ (truncated to 128 bits)

   - Generate random $`\text{delay\_i}`$, a 16-bit unsigned integer (0-65535 milliseconds).

     Note that top-level applications can use other probability distributions,
     such as an exponential distribution, where shorter delays are more likely
     than longer delays. This can mimic real-world traffic patterns and provide
     robust anonymity against traffic analysis. The trade-off lies in balancing
     the need for flexible delay handling with the risk of exposing
     application-specific traffic patterns.

   - If $i = L-1$ (_i.e.,_ exit node):

     $`\beta_i = \mathrm{AES\text{-}CTR}(\beta_{\mathrm{aes\_key}_i}, \beta_{\mathrm{iv}_i},
      0_{((t+1)(r-L)+t+2)\kappa}) \mid \phi_{L-1}`$

   - Otherwise (_i.e.,_ intermediary node):

     $`\beta_i = \mathrm{AES\text{-}CTR}\bigl(\beta_{\mathrm{aes\_key}_i},
     \beta_{\mathrm{iv}_i}, \mathrm{addr}_{i+1} \mid \mathrm{delay}_{i+1}
     \mid \gamma_{i+1} \mid \beta_{i+1 \, [0 \ldots (r(t+1) - t)\kappa - 1]} \bigr)`$

     Note that the length of $$\beta_i$$ is $(r(t+1)+1)\kappa$, $0 \leq i \leq L-1$,
     where $t$ is the combined length of next hop address and delay.

   - $`\gamma_i = \mathrm{HMAC\text{-}SHA\text{-}256}(\mathrm{mac\_key}_i, \beta_i)`$\

     Note that the length of $\gamma_i$ is $\kappa$.

   d. **Compute** **Deltas (**$\delta_i$, **$i=0$** to **$L-1$)**

   For each $i$ (from $L-1$ to $0$):

   - Derive the AES key and IV:

    $`\delta_{\mathrm{aes\_key}_i} = \mathrm{KDF}("\delta\_aes\_key" \mid s_i)`$

    $`\delta_{\mathrm{iv}_i} = \mathrm{H}("\delta\_iv" \mid s_i)`$ (truncated to 128 bits)

   - If $i = L-1$ (_i.e.,_ exit node):

     $`\delta_i = \mathrm{AES\text{-}CTR}(\delta_{\mathrm{aes\_key}_i},
     \delta_{\mathrm{iv}_i}, 0_{\kappa} \mid m)`$
      , where $m$ is the `message`.

   - Otherwise (_i.e.,_ intermediary node):

     $`\delta_i = \mathrm{AES\text{-}CTR}(\delta_{\mathrm{aes\_key}_i}, \delta_{\mathrm{iv}_i}, \delta_{i+1})`$

     Note that the length of $\delta$ is $|m| + \kappa$.

     Given that the derived size of $\delta$ is $2029$ bytes, this allows
     `message` to be of length $2029-16 = 2013$ bytes. This means smaller
     messages may need to be padded up to $2013$ bytes (e.g., using PKCS#7
     padding).

   e. **Construct Final Sphinx Packet**

   - Initialize header

     ```pseudocode
     alpha = alpha_0 // 32 bytes
     beta = beta_0 // $(r(t+1)+1)\kappa$ bytes
     gamma = gamma_0 // 16 bytes
     ```

     As discussed earlier, for a maximum path length of $r = 5$, and combined
     length of address and delay $t = 3\kappa = 48$ bytes, the header size is
     just $384$ bytes.
   - Initialize payload

     `delta = delta_0 // variable size, max 2029 bytes`

     For a fixed Sphinx packet size of $2413$ bytes and given the header length
     of $384$ bytes, `delta` size is $2029$ bytes.

6. **Serialize the Sphinx Packet** using Protocol Buffers.

7. **Send the Serialized Packet** to the first mix node using the
   `"/mix/1.0.0"` protocol.

#### 5.2 Intermediary Mix Node

Let $`x_i \in \mathbb{Z}_q^*`$ be the intermediary node’s private key
corresponding to the public key $y_i \in G^*$. It performs the following steps
to relay a message:

1. **Receive and Deserialize** the Sphinx packet using Protocol Buffers.
2. **Compute Shared Secret $s = \alpha^{x_{i}}$**.

3. **Check If Previously Seen**

   a. Compute tag $H(s)$, where $H$ is the SHA-256 hash function.

   b. If tag is in the previously seen list of tags, discard the message.

   c. This list can be reset whenever the node rotates its private key

4. **Compute MAC**

   a. Derive MAC key

   $`\text{mac\_key} = KDF(\text{"mac\_key"}\ |\ s)`$

   b. Check if $`\gamma = \mathrm{HMAC\text{-}SHA\text{-}256}(\mathrm{mac\_key}, \beta)`$.
   If not, discard the message.

   c. Otherwise, store tag $H(s)$ in the list of seen message tags.

5. **Decrypt One Layer**

   a. Derive the AES key, MAC key, and IV:

   $`\beta_{\mathrm{aes\_key}} = \mathrm{KDF}("aes\_key" \mid s)`$

   $`\beta_{\mathrm{iv}} = \mathrm{H}("iv" \mid s)`$ (truncated to 128 bits)

   b. Compute
   $`B = \mathrm{AES\text{-}CTR}(\beta_{\mathrm{aes\_key}}, \beta_{\mathrm{iv}}, \beta \mid 0_{(t+1)k})`$.

   c. Uniquely parse prefix of $B$

   If $B$ has a prefix of **$0_{((t+1)(r-L)+t+2)\kappa}$,** the current node is the
   exit node (refer exit node operations below).

   Otherwise, it is an intermediary node and it performs the followings steps
   to relay the message.

   d. **Extract Routing Information**

   $`\text{next\_hop} = B_{[0\ldots(t\kappa-17)]}`$ (first $t\kappa-2$ bytes).

   e. **Extract Delay**

   $`\text{delay} = B_{[(t\kappa-16)\ldots(t\kappa-1)]}`$ (following $2$ bytes).

   f. **Extract Gamma**

   $`{\gamma}' = B_{[t\kappa\ldots(t\kappa+\kappa-1)]}`$ (following $\kappa$ bytes).

   g. **Extract Beta**

   $`\beta' = B_{[(t\kappa+\kappa) \ldots (r(t+1)+t+2)\kappa - 1]}`$ (following
   $((t+1)r + 1)\kappa$ bytes).

   h. **Compute Alpha**

   - Compute blinding factor $b = H(α\ |\ s)$, where $H$ is the SHA-256 hash
    function.
   - Compute $α^′ = α^b$.

   i. **Compute Delta**

   - Derive the AES key and IV:
     $`\delta_{\mathrm{aes\_key}} = \mathrm{KDF}("\delta\_aes\_key" \mid s)`$
     $`\delta_{\mathrm{iv}} = \mathrm{H}("\delta\_iv" \mid s)`$ (truncated to 128 bits)
   - Compute $`\delta' = \mathrm{AES\text{-}CTR}(\delta_{\mathrm{aes\_key}}, \delta_{\mathrm{iv}}, \delta)`$

6. **Construct Final Sphinx Packet**

   a. Initialize header

   ```pseudocode
    alpha = alpha' // 32 bytes
    beta = beta' // $((t+1)r + 1)\kappa$ bytes
    gamma = gamma' // 16 bytes
   ```

   b. Initialize payload

   `delta = delta' // variable size, max 2029 bytes`

7. **Serialize the Sphinx Packet** using Protocol Buffers.
8. **Introduce A Delay** of $`\text{delay}`$ milliseconds.
9. **Send the Serialized Packet** to $`\text{next\_hop}`$ using the
    `"/mix/1.0.0"` protocol.

#### 5.3 Exit Node

1. **Perform _Steps i. to v. b._ Above**, similar to an intermediary node. If
   $B$ has a prefix of $0_{((t+1)(r-L)+t+2)\kappa}$ (in _step 5. c._ above), the
   current node is the exit node. It performs the following steps to
   disseminate the message via the respective libp2p protocol.

2. **Compute Delta**

   - Derive the AES key and IV:

     $`\delta_{\mathrm{aes\_key}} = \mathrm{KDF}("\delta\_aes\_key" \mid s)`$

     $`\delta_{\mathrm{iv}} = \mathrm{H}("\delta\_iv" \mid s)`$ (truncated to 128 bits)

   - Compute $`\delta' = \mathrm{AES\text{-}CTR}(\delta_{\mathrm{aes\_key}}, \delta_{\mathrm{iv}}, \delta)`$.

3. **Extract Message**

   $m = \delta'_{[\kappa\ldots]}$ (remove first $\kappa$ bytes).

4. **Remove Any Padding** from $m$ to obtain the `message` including any
   necessary spam protection data.

5. **Verify Spam Protection**

   Verify the spam protection mechanism applied to the `message`.
   If the verification fails, discard the `message`.
   Refer to [Appendix A](#appendix-a-example-spam-protection-using-proof-of-work)
   for details on the current implementation using PoW.

6. **Deserialize the extracted message** using the respective libp2p protocol's
   definition.

7. **Disseminate the message** via the respective libp2p protocol (_e.g.,_
   GossipSub).

## Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

## References

### Normative

[Handler function](https://docs.libp2p.io/concepts/fundamentals/protocols/#handler-functions)
[libp2p](https://libp2p.io)\
[Sphinx](https://cypherpunks.ca/~iang/pubs/Sphinx_Oakland09.pdf)

### Informative

[PoW](https://bitcoin.org/bitcoin.pdf)\
[Sphinx packet size](https://petsymposium.org/popets/2024/popets-2024-0050.pdf)

## Appendix A. Example Spam Protection using Proof of Work

The current implementation uses a Proof of Work mechanism for spam protection.
This section details the specific steps for attaching and verifying the PoW.

### Structure

The sender appends the PoW to the serialized libp2p message, `libp2p_message`,
in a structured format, making it easy to parse and verify by the exit node.
The sender includes the PoW as follows:

 `message = <libp2p_message_bytes | timestamp | nonce>`

where:

 `<libp2p_message_bytes>`: Serialized libp2p message (variable length).

`<timestamp>`: The current Unix timestamp in seconds (4 bytes).

`<nonce>`: The nonce that satisfies the PoW difficulty criterion (4 bytes).

**Nonce Size:** The nonce size should be large enough to ensure a sufficiently large
search space. It should be chosen so that the range of possible nonce values
allows for the difficulty target to be met. However, larger nonce sizes can increase
the time and computational effort required to find a valid nonce. We use
a 4-byte nonce to ensure sufficiently large search space with reasonable
computational effort.

**Difficulty Level:** The difficulty level is usually expressed as the number of
leading zeros required in the hash output. It is chosen such that the
computational effort required is significant but not prohibitive.
We recommend a reasonable difficulty level that requires around
16-18 leading zeros in the SHA-256 hash. This would roughly take
0.65 to 2.62 seconds to compute in a low-grade CPU,
capable of computing 100,000 hashes per second.

### Calculate Proof of Work (PoW)

The sender performs the following steps to compute the PoW challenge and response:

i. **Create Challenge**

Retrieves the current Unix timestamp, `timestamp`, in seconds (4 bytes).

ii. **Find A Valid Nonce**

- Initializes the `nonce` to a 4-byte value (initially zero).
  
- Increments the `nonce` until the SHA-256 hash of
  `<libp2p_message_bytes | timestamp | nonce>` has at least
  18 leading zeros.

- The final value of the `nonce` is considered valid.

### Attach the PoW to the libp2p Message

  Append the 4-byte `timestamp` and the valid `nonce` to
  the `libp2p_message_bytes` to form the `message`.

  `message = <libp2p_message_bytes | timestamp | nonce>`

### Verify PoW
  
i. **Extract Timestamp and Nonce**

  Split `message` into 4-byte `nonce` (last 4 bytes), 4-byte `timestamp`
  (the 4 bytes before the nonce), and the serialized libp2p message
  to be published, `libp2p_message_bytes` (the remaining bytes).

ii. **Verify Timestamp**
  
- Check the `timestamp` is within the last 5 minutes.
  
- If the timestamp is outside the acceptable window, the exit node
  discards the message.

iii. **Verify Response**

- Compute the SHA-256 hash of the `message` and check if the hash
  meets the difficulty requirement, _i.e._, has at least 18 leading zeros.
  
- If the hash is not valid, the exit node discards the message. Otherwise,
  it follows the steps to publish the message.
