Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Étale

Rust implementation of lattice-based cryptographic primitives for zero-knowledge proofs.

Installation

[dependencies]
etale = { path = "." }

Modules

  • lattice — Ring arithmetic, trace computation, challenge sampling
  • zk — 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, &params);
let packed_b = pack(&b, &params);

// Recover ⟨a, b⟩ via trace pairing
let inner_product = inner_product_from_trace(&packed_a, &packed_b, &params, &h);

Note: F is a generic field type implementing ark_ff::Field. See tests in src/lattice/pairing.rs for concrete examples using Fp64.

Parameters

ParameterDescriptionTypical value
dRing dimension (power of 2)1024
kExtension degree4
nPacking capacity: d/k256
half_nHalf packing dimension: d/(2k)128
scaleInner product scaling factor: d/k256

Functions

FunctionDescription
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);
}