Skip to content

Commit

Permalink
Merge branch 'release/1.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Sep 29, 2024
2 parents fa2accf + 5cdd347 commit dad9929
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ on:
branches: [main, develop]
pull_request:
types: [opened, synchronize]
merge_group: {}

jobs:
matrix:
name: Build and Test
name: Build and Test Matrix
timeout-minutes: 15
runs-on: ubuntu-latest

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fiber-sphinx"
version = "1.0.0"
version = "1.0.1"
edition = "2021"
license-file = "COPYING.md"
description = "A Rust implementation of the Sphinx mix network."
Expand Down
27 changes: 14 additions & 13 deletions docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ Fiber Sphinx implements [Sphinx][Sphinx] using the same configuration as Bitcoin
[Sphinx]: http://ieeexplore.ieee.org/document/5207650/
[BOLT4]: https://github.com/lightning/bolts/blob/master/04-onion-routing.md

Fiber uses Sphinx to facilitate the transmission of messages from an initial node to a destination node by utilizing a sequence of forwarding nodes. These nodes create a singly linked list, and the message transmitted between consecutive nodes is referred to as a hop.
Fiber uses Sphinx to facilitate the transmission of messages from an initial node to a destination node by utilizing a sequence of forwarding nodes. These nodes form a linked list, and hops refer to the links between the nodes to their successors.

Intermediate nodes can verify the packet's integrity and determine the next hop for forwarding. However, they have limited knowledge of the route. They only know their predecessor and successor nodes, and cannot learn about other nodes or the route's length. Additionally, the packet is obfuscated at each hop, preventing network-level attackers from associating packets on the same route.
Intermediate nodes can verify the packet's integrity and determine the next hop for forwarding. However, they have limited knowledge of the route. They only know their predecessor and successor nodes, and cannot learn about other nodes or the route's length. Additionally, each hop obfuscates the packet, preventing network-level attackers from associating packets on the same route.

The route is established by the origin node, which possesses the public keys of all intermediate nodes as well as the final node. With knowledge of these public keys, the origin node is able to generate a shared secret for each intermediate node and the final node using ECDH. This shared secret is utilized to create a pseudo-random stream of bytes, which is employed to obfuscate the packet. Additionally, the shared secret is used to generate multiple keys that are used for payload encryption and HMAC computation. These HMACs are then used to ensure packet integrity at each hop in the route.
The origin node, which possesses the public keys of all intermediate nodes and the final node, establishes the route. With knowledge of these public keys, the origin node can generate a shared secret for each intermediate node and the final node using ECDH. This shared secret is used to create a pseudo-random stream of bytes, which is employed to obfuscate the packet. The shared secret is used to generate multiple keys that are used for payload encryption and HMAC computation. These HMACs are then used to ensure packet integrity at each hop in the route.

## Definition

Assume that the origin node wants to send the message $m_{v-1}$ to node $n_{v-1}$ and it has found a path $(n_0, n_1, \cdots, n_{v-1})$ where $n_0, n_1, \cdots, n_{v-2}$ are forwarding nodes. The data $m_0, m_1, \cdots, m_{v-2}$ are control messages sent to forwarding nodes where $m_i$ is the control message for the node $n_i$. The origin node can optionally attach the associated data $A$ that all the nodes can verify the integrity of the associated data $A$.
Assume that the origin node wants to send the message $m_{v-1}$ to the target node $n_{v-1}$ and it has found a path $(n_0, n_1, \cdots, n_{v-1})$ where $n_0, n_1, \cdots, n_{v-2}$ are forwarding nodes. The data $m_0, m_1, \cdots, m_{v-2}$ are control messages sent to forwarding nodes where $m_i$ is the message for the target node $n_i$. The origin node can optionally attach the associated data $A$ that all the nodes can verify the integrity of the associated data $A$.

Each node $n_i$ has a secp256k1 private key $x_i$. The origin node knows the corresponding public keys $y_i = g^{x_i}$ for each node.

Expand Down Expand Up @@ -63,23 +63,24 @@ There are there hash function used in the equations: $h_b$, $h_\mu$, and $h_\rho

### Filler Generation

Filler is used to fill the blanks when a hop removes its message from the packet. It is generated by using the $\mu_i$ from $\mu_0$ to $\mu_{v-2}$ incrementally.
Filler is used to fill the blanks when a hop removes its message from the packet. The origin node generates the filler string from $n_0$ to $\n_{v-2}$ incrementally.

Let $\phi_0$ be an empty string. For $0 \lt i \lt v$:

$$
\phi_i = \mathsf{Chacha20}(\rho_{i-1})[(L-\lvert \phi_{i-1} \rvert)..(L + \lvert m_{i-1} \rvert)] \oplus \\{\phi_{i-1} \Vert 0_{\lvert m_{i-1} \rvert} \\}
$$

Where
In the formula above:

- $0_b$ means the string of 0 bits of length $b$ bytes.
- $\mathsf{Chacha20}(\rho_{i-1})$ is a Chacha20 cipher stream with the key $\rho_{i-1}$ and iv $0_{12}$.
- $\mathsf{Chacha20}(\rho_{i-1})[a..b]$ means the substring of is the string of $\mathsf{Chacha20}$ consisting of bytes a (inclusively) through b (exclusively). The substring has the length $b - a$. The string index starts from 0.
- $\Vert$ denotes concatenation
- $\lvert s \rvert$ is the length of string $s$
- $a \oplus b$ applies XOR on strings $a$ and $b$.

This step is illustrated in Figure 1.
Figure 1 illustrates this step.

![Figure 1](Fiber%20Sphinx%20Specification%20-%20Filler%20Generation%20v2.excalidraw.svg)

Expand Down Expand Up @@ -123,7 +124,7 @@ $$

The forward message is the tuple $(\alpha_0, \beta_0, \gamma_0)$, and should be sent to $n_0$.

This step is illustrated in Figure 2.
Refer to the illustration in the Figure 2.

![Figure 2](Fiber%20Sphinx%20Specification%20-%20Construction.excalidraw.svg)

Expand Down Expand Up @@ -154,25 +155,25 @@ $$

Compute the HMAC of $\beta_i$ using the key $\mu_i$ and verify whether it is $\gamma_i$. If they does not match, discard the message. Otherwise, decrypt $\beta_i$ by XORing it with the output of $\mathsf{Chacha20}(\rho_i)[0..L]$.

Attention that, the decrypted content starts with $m_i$ and $\gamma_{i+1}$ but how to know the length of $m_i$ is not a part of Fiber Sphinx Specification. The applications must add their own mechanisms to get the length. For example, the message can have a fixed length or has embed the length in itself. Using the length, the node can extracted $m_i$ and $\gamma_{i+1}$ from the decrypted content. The node $n_i$ is the final node if $\gamma_{i+1}$ is $0_{32}$, otherwise it should create the forwarding message $(\alpha_{i+1}, \beta_{i+1}, \gamma_{i+1})$ for $n_{i+1}$:
Attention that, the decrypted content starts with $m_i$ and $\gamma_{i+1}$ but how to know the length of $m_i$ is not a part of Fiber Sphinx Specification. The applications must add their own mechanisms to get the length. For example, the message can have a fixed length or have embedded the length in itself. Using the length, the node can extract $m_i$ and $\gamma_{i+1}$ from the decrypted content. The node $n_i$ is the final node if $\gamma_{i+1}$ is $0_{32}$, otherwise it should create the forwarding message $(\alpha_{i+1}, \beta_{i+1}, \gamma_{i+1})$ for $n_{i+1}$:

- $\alpha_{i+1}$ can be derived from $\alpha_i$ and $b_i$ since $g^{x \prod_{k=1}^{i-1}{b_k} } = {g^{x \prod_{k=1}^{i-2}{b_k}}}^{b_i} = \alpha_i^{b_i}$
- Delete $m_i$ and $\gamma_{i+1}$ from the decrypted content, and append $\lvert m_i \rvert + 32$ bytes of zeros in the end. XOR the new appended $\lvert m_i \rvert + 32$ bytes with $\mathsf{Chacha20}(\rho)[L..(L+\lvert m_i \rvert+32)]$. This step will recreate the content of $\beta_{i+1}$.
- $\gamma_{i+1}$ is in the decrypted $\beta_i$.

This step is illustrated in Figure 3
Figure 3 is the illustration of this step.

![Figure%203](Fiber%20Sphinx%20Specification%20-%20Peeling.excalidraw.svg)

The Fiber Sphinx dose not define how $n_i$ knows the address of $n_{i+1}$ to send the forwarding message. Usually such information can be obtained from $m_i$.
The Fiber Sphinx does not define how $n_i$ knows the address of $n_{i+1}$ to send the forwarding message. Usually, such information can be got from $m_i$.

## Returning Errors

The Sphinx protocol allows encrypted error messages to be returned to the origin node from any hop, including the final node.

The forwarding nodes and the final node must store the shared secret from the forward path and reuse it to obfuscate any corresponding return packet. In addition, each node locally stores data regarding its own sending peer in the route, so it knows where to forward the error packets.

The forwarding and final nodes both store the shared secret from the forward path. They reuse this secret to obfuscate any return packet. Additionally, each node keeps data about its own sending peer in the route, enabling it to know where to forward the error-returning packet.
The forwarding and final nodes both store the shared secret from the forward path. They reuse this secret to obfuscate any return packet. Each node keeps data about its own sending peer in the route, enabling it to know where to forward the error-returning packet.

The node generating the error (erring node) creates a return packet that includes two parts:

Expand All @@ -193,7 +194,7 @@ Where
- $h_\bar{\mu}: (s_i) \to \bar{\mu}_i$ computes HMAC-SHA256 on $s_i$ by using 2 bytes `0x756d` (utf8 encoding of the text "um") as the key.
- $h_\bar{\gamma}: (s_i) \to \bar{\gamma}_i$ computes HMAC-SHA256 on $s_i$ by using 3 bytes `0x616d6d6167` (utf8 encoding of the text "ammag") as the key.

Finally, the erring node computes the HMAC and encrypt the packet:
Finally, the erring node computes the HMAC and encrypts the packet:

- Computes the HMAC of `payload` to get `hmac`: $\mathsf{HMAC\textrm{-}SHA256}(\bar{\mu} _{i}, \textrm{payload})$
- Concatenate `hmac` and `payload`, then XOR it with the Chacha20 stream: $\\{\textrm{hmac} \Vert \textrm{payload}\\} \oplus \mathsf{Chacha20}(\bar{\gamma}_i)$
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pub struct OnionPacket {
/// let session_key = SecretKey::from_slice(&[0x41; 32]).expect("32 bytes, within curve order");
/// let hops_ss = OnionSharedSecretIter::new(hops_path.iter(), session_key, &secp).collect::<Vec<_>>();
///
/// // The node [0x21; 32] generates the error
/// // The node 0324653...0ab1c generates the error
/// let shared_secret = hops_ss[1];
/// let error_packet = OnionErrorPacket::create(&shared_secret, b"error message".to_vec());
/// ```
Expand Down

0 comments on commit dad9929

Please sign in to comment.