Skip to content

Commit d975a4e

Browse files
committed
Add more docs
1 parent 0547456 commit d975a4e

File tree

7 files changed

+333
-10
lines changed

7 files changed

+333
-10
lines changed

README.md

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,108 @@
11
# Zanzibar
22

3-
Worldcoin re-mixing system.
3+
Distributed cryptographic protocol.
44

55
## Overview
66

77
## Run
88

9-
To run the tests
10-
```
9+
To run the tests:
10+
```bash
1111
cargo test
1212
```
1313

14-
To run the benchmarks
15-
```
14+
To run the benchmarks:
15+
```bash
1616
cargo bench
1717
```
1818

19-
To generate documentation
19+
To generate documentation:
20+
```bash
21+
cargo doc --open
2022
```
21-
cargo doc
23+
24+
## Demo
25+
26+
To run a demo we need:
27+
- A shared key
28+
- 3 mix nodes
29+
- 1 client
30+
31+
There's a key already configured in `mix-node/config/crypto.json`. However, the `secret_key` will be different for each node, this will be overriden with environment variables later.
32+
33+
### Keys
34+
- Participant 0: `secret_key = "D7W4K_1QPjuoEQNsTeyWT92yHSPp67-sApmGksw9EQo"`
35+
- Participant 1: `secret_key = "XGaup1prWoijF91au13Qv2OgqGjo-uE74szhzGbXUgw"`
36+
- Participant 2: `secret_key = "qRekI7iFdtWeHbdJKc8JMOqNM67nCQTLwQA9BwFxlA4"`
37+
38+
<!-- If you want to generate keys yourself, you can run:
39+
```bash
40+
# 3 participants with 2 minimum threshold
41+
cargo run --bin gen_keys -- 2 3
2242
```
23-
You can find the generated docs on /target/doc/remix/index.hmtl
43+
And then copy the `key_set` into the `crypto.json` config file. -->
44+
45+
<details>
46+
<summary>If you want to generate keys yourself (optional)</summary>
47+
48+
To generate a new set of keys:
49+
```bash
50+
# 3 participants with 2 minimum threshold
51+
cargo run --bin gen_keys -- 2 3
52+
```
53+
Copy the resulting `key_set` into the `crypto.json` config file.
54+
</details>
55+
56+
### Running Mix Nodes
57+
58+
We can spin up the 3 mix nodes like so:
59+
```bash
60+
# For more detailed logs, run these before starting the servers:
61+
# export RUST_LOG=info,mix_node=trace,tower_http=debug
62+
63+
# Participant 0
64+
APP_CRYPTO__SECRET_KEY="D7W4K_1QPjuoEQNsTeyWT92yHSPp67-sApmGksw9EQo" \
65+
APP_CRYPTO__WHOAMI="0" \
66+
APP_APPLICATION__PORT="6000" \
67+
cargo run --bin rest --release
68+
69+
# Participant 1
70+
APP_CRYPTO__SECRET_KEY="XGaup1prWoijF91au13Qv2OgqGjo-uE74szhzGbXUgw" \
71+
APP_CRYPTO__WHOAMI="1" \
72+
APP_APPLICATION__PORT="6001" \
73+
cargo run --bin rest --release
74+
75+
# Participant 2
76+
APP_CRYPTO__SECRET_KEY="qRekI7iFdtWeHbdJKc8JMOqNM67nCQTLwQA9BwFxlA4" \
77+
APP_CRYPTO__WHOAMI="2" \
78+
APP_APPLICATION__PORT="6002" \
79+
cargo run --bin rest --release
80+
```
81+
82+
### Running the Client
83+
84+
Now that the network of mix-nodes is up and running, we can run the client that will request a comparison:
85+
```bash
86+
# two codes of length 10
87+
cargo run --example demo -- 10
88+
89+
# two codes of length 12800
90+
cargo run --example demo -- 12800
91+
```
92+
**Note:** Because the servers are running on the same machine and the operations are very CPU heavy, the performance is not truly representative of a real-world deployment.
93+
94+
### How it Works
95+
96+
The client:
97+
1. Requests the public key to encrypt the codes (instead of hardcoding it)
98+
2. Generates a random code with the given length and clones it (both codes will be identical)
99+
3. Encrypts both codes and sends a request to "http://localhost:6000/hamming"
100+
4. Since the codes are identical, the expected hamming distance is 0
101+
102+
### Testing Threshold Decryption
103+
104+
To test threshold decryption:
105+
1. Shutdown the mix-node on port `6002` and run the client again - it should still compute the hamming distance
106+
2. Shutdown another mix-node (port `6001`) - the network can no longer compute the hamming distance because the number of nodes is below the threshold, returning an error
107+
108+
The code for the client can be found at `mix-node/examples/demo`.

mix-node/examples/demo/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ async fn main() -> anyhow::Result<()> {
1515
.nth(1)
1616
.expect("cli arg 'n_bits' missing")
1717
.parse::<usize>()
18-
.expect("cli arg is not a number");
18+
.expect("cli arg is not a number")
19+
* 2;
1920
let port = 6000;
2021
// Request public key
2122
let client = reqwest::Client::new();

mix-node/src/crypto.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,52 @@
1+
//! Secure cryptographic primitives for threshold ElGamal encryption.
2+
//!
3+
//! This module provides a set of tools for encrypted communication with threshold
4+
//! decryption capabilities using the ElGamal cryptosystem over the Ristretto curve.
5+
//! The implementation supports distributed key generation, encryption, remixing,
6+
//! threshold decryption, and verification of decryption shares.
7+
//!
8+
//! # Key Features
9+
//!
10+
//! * Secure threshold encryption using ElGamal over Ristretto
11+
//! * Efficient parallel processing with Rayon
12+
//! * Verifiable decryption shares
13+
//! * Support for mixing and remixing of ciphertexts
14+
//! * Hamming distance calculation between bit vectors
15+
//!
16+
//! # Examples
17+
//!
18+
//! ```
19+
//! # use anyhow::Result;
20+
//! # use elastic_elgamal::sharing::{Dealer, Params, PublicKeySet, ActiveParticipant};
21+
//! # use elastic_elgamal::group::Ristretto;
22+
//! # use bitvec::prelude::*;
23+
//! # use mix_node::crypto::*;
24+
//! # fn main() -> Result<()> {
25+
//! # let mut rng = rand::thread_rng();
26+
//! # let params = Params::new(3, 2);
27+
//! # let dealer = Dealer::<Ristretto>::new(params, &mut rng);
28+
//! # let (public_poly, poly_proof) = dealer.public_info();
29+
//! # let key_set = PublicKeySet::new(params, public_poly, poly_proof)?;
30+
//! # let participants: Vec<_> = (0..3)
31+
//! # .map(|i| ActiveParticipant::new(key_set.clone(), i,
32+
//! # dealer.secret_share_for_participant(i)).unwrap())
33+
//! # .collect();
34+
//! # let bits = bitvec![1, 0, 1, 0];
35+
//! // Encrypt data
36+
//! let encrypted = encrypt(key_set.shared_key(), &bits);
37+
//!
38+
//! // Generate decryption shares
39+
//! let shares = vec![
40+
//! decryption_share_for(&participants[0], &encrypted),
41+
//! decryption_share_for(&participants[1], &encrypted),
42+
//! ];
43+
//!
44+
//! // Combine shares and decrypt
45+
//! let decrypted = decrypt_shares(&key_set, &encrypted, &shares)?;
46+
//! # Ok(())
47+
//! # }
48+
//! ```
49+
150
use anyhow::Context;
251
use elastic_elgamal::{
352
group::Ristretto,
@@ -9,16 +58,33 @@ use serde::{Deserialize, Serialize};
958
use std::sync::LazyLock;
1059
use thiserror::Error;
1160

61+
/// An ElGamal ciphertext over the Ristretto curve.
1262
pub type Ciphertext = elastic_elgamal::Ciphertext<Ristretto>;
63+
64+
/// A bit vector used for storing binary data.
1365
pub type Bits = bitvec::vec::BitVec;
1466

67+
/// A decryption share from a participant in the threshold encryption scheme.
68+
///
69+
/// Contains the participant's index and their decryption shares
70+
/// for a set of ciphertexts, along with proofs of correctness.
1571
#[derive(Debug, Clone, Serialize, Deserialize)]
1672
pub struct DecryptionShare {
1773
index: usize,
1874
share: Vec<(VerifiableDecryption<Ristretto>, LogEqualityProof<Ristretto>)>,
1975
}
2076

2177
impl DecryptionShare {
78+
/// Creates a new decryption share with the given participant index and shares.
79+
///
80+
/// # Arguments
81+
///
82+
/// * `index` - The participant's index
83+
/// * `share` - A vector of verifiable decryptions with their proofs
84+
///
85+
/// # Returns
86+
///
87+
/// A new `DecryptionShare` instance
2288
pub fn new(
2389
index: usize,
2490
share: Vec<(VerifiableDecryption<Ristretto>, LogEqualityProof<Ristretto>)>,
@@ -27,15 +93,24 @@ impl DecryptionShare {
2793
}
2894
}
2995

96+
/// Discrete logarithm lookup table optimized for binary values.
97+
///
98+
/// This static table is used for efficient decryption of binary values (0 or 1).
3099
pub static LOOKUP_TABLE: LazyLock<DiscreteLogTable<Ristretto>> =
31100
LazyLock::new(|| DiscreteLogTable::<Ristretto>::new(0..=1));
32101

102+
/// Errors that can occur during cryptographic operations.
33103
#[derive(Debug, Error)]
34104
pub enum CryptoError {
105+
/// Error indicating invalid or mismatched lengths in cryptographic operations.
35106
#[error("InvalidLength: {0}")]
36107
InvalidLength(String),
37108
}
38109

110+
/// Remixes two ciphertext vectors using ElGamal homomorphic properties.
111+
///
112+
/// This function performs remixing operations on two encrypted code vectors.
113+
/// Both vectors must have the same length, and the length must be even.
39114
pub fn remix(
40115
x_code: &mut [Ciphertext],
41116
y_code: &mut [Ciphertext],
@@ -51,6 +126,10 @@ pub fn remix(
51126
Ok(())
52127
}
53128

129+
/// Encrypts a bit vector using the provided public key.
130+
///
131+
/// This function encrypts each bit in the input bit vector in parallel
132+
/// using the ElGamal encryption scheme.
54133
pub fn encrypt(pub_key: &PublicKey<Ristretto>, bits: &Bits) -> Vec<Ciphertext> {
55134
bits.as_raw_slice()
56135
.into_par_iter()
@@ -71,6 +150,10 @@ pub fn encrypt(pub_key: &PublicKey<Ristretto>, bits: &Bits) -> Vec<Ciphertext> {
71150
.collect::<Vec<_>>()
72151
}
73152

153+
/// Generates decryption shares for a set of ciphertexts.
154+
///
155+
/// This function allows a participant to generate their decryption share
156+
/// for each ciphertext in the provided array.
74157
pub fn decryption_share_for(
75158
active_participant: &ActiveParticipant<Ristretto>,
76159
ciphertext: &[Ciphertext],
@@ -85,6 +168,10 @@ pub fn decryption_share_for(
85168
DecryptionShare::new(active_participant.index(), share)
86169
}
87170

171+
/// Combines decryption shares to recover the original plaintext.
172+
///
173+
/// This function verifies and combines decryption shares from multiple
174+
/// participants to decrypt the original message.
88175
pub fn decrypt_shares(
89176
key_set: &PublicKeySet<Ristretto>,
90177
enc: &[Ciphertext],
@@ -124,6 +211,10 @@ pub fn decrypt_shares(
124211
.map(Bits::from_iter)
125212
}
126213

214+
/// Calculates the Hamming distance between two bit vectors.
215+
///
216+
/// The Hamming distance is the number of positions at which the corresponding
217+
/// bits are different.
127218
pub fn hamming_distance(x_code: Bits, y_code: Bits) -> usize {
128219
// Q: What if x and y are different sizes?
129220
(x_code ^ y_code).count_ones()

mix-node/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pub mod config;
22
pub mod crypto;
33
pub mod db;
44
pub mod rest;
5-
pub(crate) mod rokio;
5+
pub mod rokio;
66
pub mod test_helpers;
77

88
use config::CryptoConfig;

mix-node/src/rest/error.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
//! Error types for the application.
2+
//!
3+
//! This module defines the error types used throughout the application,
4+
//! particularly for handling HTTP responses and crypto-related errors.
5+
16
use axum::{
27
http::StatusCode,
38
response::{IntoResponse, Response},
@@ -6,14 +11,25 @@ use thiserror::Error;
611

712
use crate::crypto::CryptoError;
813

14+
/// Application-wide error types.
915
#[derive(Debug, Error)]
1016
pub enum Error {
17+
/// Error indicating an invalid length was provided
1118
#[error("InvalidLength: {0}")]
1219
InvalidLength(String),
20+
21+
/// Unexpected errors that don't fit other categories
1322
#[error(transparent)]
1423
Unexpected(#[from] anyhow::Error),
1524
}
1625

26+
/// Implementation of `IntoResponse` for the application's error types.
27+
///
28+
/// Converts application errors into HTTP responses with appropriate status codes:
29+
/// - `InvalidLength` errors return `400 Bad Request`
30+
/// - `Unexpected` errors return `500 Internal Server Error`
31+
///
32+
/// The response body contains the error message as a string.
1733
impl IntoResponse for Error {
1834
fn into_response(self) -> Response {
1935
let status_code = match &self {

0 commit comments

Comments
 (0)