Étale
Rust implementation of lattice-based cryptographic primitives for zero-knowledge proofs.
Installation
[dependencies]
etale = { path = "." }
Modules
lattice— Ring arithmetic, trace computation, challenge samplingzk— Fiat-Shamir transform, rejection sampling
Challenge Sampling
Sample a Random Challenge
#![allow(unused)]
fn main() {
use etale::lattice::splitting::sample_challenge;
use rand::thread_rng;
let mut rng = thread_rng();
// n=256, τ=1 (ternary coefficients), ω=60 (Hamming weight)
let challenge = sample_challenge(&mut rng, 256, 1, 60);
println!("L1 norm: {}", challenge.l1_norm());
println!("Weight: {}", challenge.weight());
}
Deterministic Challenge from Seed
#![allow(unused)]
fn main() {
use etale::lattice::splitting::challenge_from_seed;
let seed = b"my-seed-bytes";
let challenge = challenge_from_seed(seed, 256, 1, 60);
}
Challenge Arithmetic
#![allow(unused)]
fn main() {
let c1 = sample_challenge(&mut rng, 256, 1, 60);
let c2 = sample_challenge(&mut rng, 256, 1, 60);
let diff = c1.sub(&c2);
let sum = c1.add(&c2);
}
Build a Challenge Set
#![allow(unused)]
fn main() {
use etale::lattice::splitting::{ChallengeSet, SplittingParams};
let params = SplittingParams::with_computed_splits(256, 1, 60, 8380417);
let challenge_set = ChallengeSet::build(params, 100);
println!("Set size: {}", challenge_set.size());
}
Trace Computation
Create a Ring Element
#![allow(unused)]
fn main() {
use etale::lattice::trace::CyclotomicRingElement;
let coeffs = vec![1, 2, 3, 0, 0, 0, 0, 0];
let x = CyclotomicRingElement::new(coeffs, 8);
}
Define a Galois Subgroup
#![allow(unused)]
fn main() {
use etale::lattice::trace::GaloisSubgroup;
let n = 256;
let k = 1;
let subgroup = GaloisSubgroup::standard_subgroup(n, k);
println!("Order: {}", subgroup.order());
}
Compute the Trace
#![allow(unused)]
fn main() {
use etale::lattice::trace::TraceComputer;
let computer = TraceComputer::new(n);
let subgroup = GaloisSubgroup::standard_subgroup(n, 1);
let trace = computer.compute_trace(&x, &subgroup);
}
Tower Optimization
#![allow(unused)]
fn main() {
// Faster trace computation using tower decomposition
if let Some(tower_gens) = subgroup.tower_generators() {
let trace = computer.trace_tower(&x, &tower_gens);
}
}
Trace Pairing
Recovers inner products from packed ring elements via the trace form on cyclotomic rings. Theory: trace-pairing.pdf.
API
use etale::lattice::pairing::{
TracePairingParams, pack, inner_product_from_trace,
};
use etale::lattice::trace::GaloisSubgroup;
// Setup parameters (Hachi: d=1024, k=4)
let params = TracePairingParams::hachi();
let h = GaloisSubgroup::new(params.d, params.k);
// Pack vectors into ring elements (n = 256 elements each)
let a: Vec<F> = (0..params.n).map(|i| F::from(i as u64)).collect();
let b: Vec<F> = (0..params.n).map(|i| F::from((i + 1) as u64)).collect();
let packed_a = pack(&a, ¶ms);
let packed_b = pack(&b, ¶ms);
// Recover ⟨a, b⟩ via trace pairing
let inner_product = inner_product_from_trace(&packed_a, &packed_b, ¶ms, &h);
Note:
Fis a generic field type implementingark_ff::Field. See tests insrc/lattice/pairing.rsfor concrete examples usingFp64.
Parameters
| Parameter | Description | Typical value |
|---|---|---|
d | Ring dimension (power of 2) | 1024 |
k | Extension degree | 4 |
n | Packing capacity: d/k | 256 |
half_n | Half packing dimension: d/(2k) | 128 |
scale | Inner product scaling factor: d/k | 256 |
Functions
| Function | Description |
|---|---|
pack(a, params) | Maps F^n → R_q using basis {X^i} ∪ {X^{d/2+i}} |
unpack(x, params) | Inverse of pack |
trace_pairing(a, b, h) | Computes Tr_H(a · σ_{-1}(b)) |
inner_product_from_trace(a, b, params, h) | Recovers ⟨a, b⟩ from packed elements |
ring_mul(a, b) | Ring multiplication in R_q = Z_q[X]/(X^d + 1) |
Partially Splitting Rings
Create Splitting Parameters
#![allow(unused)]
fn main() {
use etale::lattice::splitting::SplittingParams;
// Manually specify num_splits
let params = SplittingParams::power_of_two(256, 256, 1, 60, 8380417);
// Or compute num_splits automatically from n and modulus
let params = SplittingParams::with_computed_splits(256, 1, 60, 8380417);
}
Query Parameters
#![allow(unused)]
fn main() {
let size = params.challenge_set_size();
let bits = params.security_bits();
println!("Challenge set size: {}", size);
println!("Security bits: {}", bits);
}
Validate Parameters
#![allow(unused)]
fn main() {
// Check if num_splits matches the computed value
assert!(params.is_valid_num_splits());
// Or panic with details if invalid
params.validate();
}
Compute Number of Splits
#![allow(unused)]
fn main() {
// Compute how X^n + 1 splits mod q
let num_splits = SplittingParams::compute_num_splits(256, 8380417);
}