diff --git a/crates/garble-core/benches/evaluate.rs b/crates/garble-core/benches/evaluate.rs index 4b28a03b..25a17b75 100644 --- a/crates/garble-core/benches/evaluate.rs +++ b/crates/garble-core/benches/evaluate.rs @@ -20,7 +20,6 @@ fn bench_evaluate(c: &mut Criterion) { let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); - let seed: [u8; 16] = rng.random(); // Prepare inputs let inputs: Vec = (0..256).map(|_| rng.random()).collect(); @@ -38,12 +37,10 @@ fn bench_evaluate(c: &mut Criterion) { let actual_gates = iterations as u64 * gates_per_circuit; // Pre-generate garbled circuits (single gates) - let mut gb = Garbler::new(seed, delta); - let setup = gb.setup().unwrap(); + let mut gb = Garbler::default(); let all_gates: Vec> = (0..iterations) .map(|_| { - let worker = gb.alloc_worker(circuit.and_count()).unwrap(); - let mut iter = worker.generate(circuit, &inputs).unwrap(); + let mut iter = gb.generate(circuit, delta, &inputs).unwrap(); let gates: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); gates @@ -55,11 +52,9 @@ fn bench_evaluate(c: &mut Criterion) { // Iterator-based (one gate at a time) group.bench_function(BenchmarkId::new("iter", name), |b| { let mut ev = Evaluator::default(); - ev.setup(setup.clone()).unwrap(); b.iter(|| { for gates in &all_gates { - let worker = ev.alloc_worker(circuit.and_count()).unwrap(); - let mut consumer = worker.evaluate(circuit, &eval_inputs).unwrap(); + let mut consumer = ev.evaluate(circuit, &eval_inputs).unwrap(); for gate in gates { consumer.next(*gate); } @@ -73,19 +68,15 @@ fn bench_evaluate(c: &mut Criterion) { // iteration group.bench_function(BenchmarkId::new("batched", name), |b| { let mut ev = Evaluator::default(); - ev.setup(setup.clone()).unwrap(); - let mut gb = Garbler::new(seed, delta); - let _ = gb.setup().unwrap(); + let mut gb = Garbler::default(); b.iter(|| { for _ in 0..iterations { // Regenerate batches (not timed separately, but included in measurement) - let worker = gb.alloc_worker(circuit.and_count()).unwrap(); - let mut iter = worker.generate_batched(circuit, &inputs).unwrap(); + let mut iter = gb.generate_batched(circuit, delta, &inputs).unwrap(); let batches: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); - let ev_worker = ev.alloc_worker(circuit.and_count()).unwrap(); - let mut consumer = ev_worker.evaluate_batched(circuit, &eval_inputs).unwrap(); + let mut consumer = ev.evaluate_batched(circuit, &eval_inputs).unwrap(); for batch in batches { consumer.next(batch); } @@ -105,7 +96,6 @@ fn bench_evaluate_parallel(c: &mut Criterion) { let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); - let seed: [u8; 16] = rng.random(); // Prepare inputs let inputs: Vec = (0..256).map(|_| rng.random()).collect(); @@ -123,12 +113,10 @@ fn bench_evaluate_parallel(c: &mut Criterion) { let actual_gates = circuit_count as u64 * gates_per_circuit; // Pre-garble circuits - let mut gb = Garbler::new(seed, delta); - let setup = gb.setup().unwrap(); + let mut gb = Garbler::default(); let garbled_circuits: Vec = (0..circuit_count) .map(|_| { - let worker = gb.alloc_worker(circuit.and_count()).unwrap(); - let mut iter = worker.generate(&circuit, &inputs).unwrap(); + let mut iter = gb.generate(&circuit, delta, &inputs).unwrap(); let gates: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); GarbledCircuit { gates } @@ -139,17 +127,12 @@ fn bench_evaluate_parallel(c: &mut Criterion) { // Parallel evaluation using evaluate_garbled_circuits (uses rayon par_iter) group.bench_function(BenchmarkId::new("rayon", name), |b| { - let mut ev = Evaluator::default(); - ev.setup(setup.clone()).unwrap(); b.iter(|| { let circs: Vec<_> = garbled_circuits .iter() .map(|gc| (circuit.clone(), eval_inputs.clone(), gc.clone())) .collect(); - let workers: Vec<_> = (0..circuit_count) - .map(|_| ev.alloc_worker(circuit.and_count()).unwrap()) - .collect(); - black_box(evaluate_garbled_circuits(circs, workers).unwrap()) + black_box(evaluate_garbled_circuits(circs).unwrap()) }) }); } diff --git a/crates/garble-core/benches/garble.rs b/crates/garble-core/benches/garble.rs index 37949ffc..e610ab43 100644 --- a/crates/garble-core/benches/garble.rs +++ b/crates/garble-core/benches/garble.rs @@ -4,7 +4,7 @@ use criterion::{BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main}; use mpz_circuits::AES128; -use mpz_garble_core::{Evaluator, Garbler, Key}; +use mpz_garble_core::{Garbler, Key}; use mpz_memory_core::correlated::Delta; use rand::{Rng, SeedableRng, rngs::StdRng}; @@ -19,7 +19,6 @@ fn bench_garble(c: &mut Criterion) { let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); let inputs: Vec = (0..256).map(|_| rng.random()).collect(); - let seed: [u8; 16] = rng.random(); let gates_per_circuit = circuit.and_count() as u64; @@ -31,11 +30,10 @@ fn bench_garble(c: &mut Criterion) { // Iterator-based (one gate at a time) group.bench_function(BenchmarkId::new("iter", name), |b| { + let mut gb = Garbler::default(); b.iter(|| { for _ in 0..iterations { - let mut gb = Garbler::new(seed, delta); - let _ = gb.setup().unwrap(); - let mut iter = gb.generate(circuit, &inputs).unwrap(); + let mut iter = gb.generate(circuit, delta, &inputs).unwrap(); let _: Vec<_> = iter.by_ref().collect(); black_box(iter.finish().unwrap()); } @@ -44,11 +42,10 @@ fn bench_garble(c: &mut Criterion) { // Batched (multiple gates at a time) group.bench_function(BenchmarkId::new("batched", name), |b| { + let mut gb = Garbler::default(); b.iter(|| { for _ in 0..iterations { - let mut gb = Garbler::new(seed, delta); - let _ = gb.setup().unwrap(); - let mut iter = gb.generate_batched(circuit, &inputs).unwrap(); + let mut iter = gb.generate_batched(circuit, delta, &inputs).unwrap(); let _: Vec<_> = iter.by_ref().collect(); black_box(iter.finish().unwrap()); } @@ -57,38 +54,6 @@ fn bench_garble(c: &mut Criterion) { } group.finish(); - - // Evaluator benchmarks - let mut ev_group = c.benchmark_group("evaluate"); - - ev_group.bench_function("aes128", |b| { - let mut gb = Garbler::new(seed, delta); - let setup = gb.setup().unwrap(); - let mut gb_iter = gb.generate(&AES128, &inputs).unwrap(); - let gates: Vec<_> = gb_iter.by_ref().collect(); - - let choices: Vec = (0..256).map(|_| rng.random()).collect(); - let inputs: Vec<_> = inputs - .iter() - .zip(choices) - .map(|(input, choice)| input.auth(choice, &delta)) - .collect(); - - b.iter(|| { - let setup = setup.clone(); - let mut ev = Evaluator::default(); - ev.setup(setup).unwrap(); - let mut ev_consumer = ev.evaluate(&AES128, &inputs).unwrap(); - - for gate in &gates { - ev_consumer.next(*gate); - } - - black_box(ev_consumer.finish().unwrap()); - }) - }); - - ev_group.finish(); } criterion_group!(benches, bench_garble); diff --git a/crates/garble-core/src/evaluator.rs b/crates/garble-core/src/evaluator.rs index 7478a141..3cd1f608 100644 --- a/crates/garble-core/src/evaluator.rs +++ b/crates/garble-core/src/evaluator.rs @@ -1,14 +1,15 @@ use core::fmt; -use std::{marker::PhantomData, ops::Range, sync::Arc}; +use std::{ops::Range, sync::Arc}; use cfg_if::cfg_if; use mpz_memory_core::correlated::Mac; -use crate::{ - DEFAULT_BATCH_SIZE, EncryptedGateBatch, GarbledCircuit, SetupMsg, circuit::EncryptedGate, -}; +use crate::{DEFAULT_BATCH_SIZE, EncryptedGateBatch, GarbledCircuit, circuit::EncryptedGate}; use mpz_circuits::{Circuit, Gate}; -use mpz_core::{Block, aes::FixedKeyAes}; +use mpz_core::{ + Block, + aes::{FIXED_KEY_AES, FixedKeyAes}, +}; /// Errors that can occur during garbled circuit evaluation. #[derive(Debug, thiserror::Error)] @@ -18,28 +19,22 @@ pub enum EvaluatorError { InputLength { expected: usize, actual: usize }, #[error("evaluator not finished")] NotFinished, - #[error("attempted to set up evaluator twice")] - AlreadySetup, - #[error("evaluator was not in set up state as expected")] - NotSetup, - #[error("AND gate count mismatch: expected no more than {expected}, got {actual}")] - GateCountMismatch { expected: usize, actual: usize }, } -/// Evaluates half-gate garbled AND gate. +/// Evaluates half-gate garbled AND gate #[inline] pub(crate) fn and_gate( cipher: &FixedKeyAes, x: &Block, y: &Block, encrypted_gate: &EncryptedGate, - gid: u128, + gid: usize, ) -> Block { let s_a = x.lsb(); let s_b = y.lsb(); - let j = Block::new(gid.to_be_bytes()); - let k = Block::new((gid + 1).to_be_bytes()); + let j = Block::new((gid as u128).to_be_bytes()); + let k = Block::new(((gid + 1) as u128).to_be_bytes()); let mut h = [*x, *y]; cipher.tccr_many(&[j, k], &mut h); @@ -60,62 +55,20 @@ pub struct EvaluatorOutput { } /// Garbled circuit evaluator. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Evaluator { - state: State, -} - -impl Default for Evaluator { - fn default() -> Self { - Self { - state: State::Initialized, - } - } + /// Buffer for the active labels. + buffer: Vec, } impl Evaluator { - /// Creates a new evaluator. - pub fn new() -> Self { + /// Creates a new evaluator with a buffer of the given capacity. + pub fn with_capacity(capacity: usize) -> Self { Self { - state: State::Initialized, + buffer: Vec::with_capacity(capacity), } } - /// Sets up the evaluator with the given `setup` message. - pub fn setup(&mut self, setup: SetupMsg) -> Result<(), EvaluatorError> { - if !matches!(self.state, State::Initialized) { - return Err(EvaluatorError::AlreadySetup); - } - - let SetupMsg { initial_gid, key } = setup; - - self.state = State::Setup(Setup { - current_gid: initial_gid, - key, - }); - - Ok(()) - } - - /// Allocates a worker for the given `count` of AND gates. - pub fn alloc_worker(&mut self, count: usize) -> Result { - let mut state = if let State::Setup(state) = self.state.take() { - state - } else { - return Err(EvaluatorError::NotSetup); - }; - - let worker = EvaluatorWorker { - initial_id: state.alloc(count), - count, - key: state.key, - }; - - self.state = State::Setup(state); - - Ok(worker) - } - /// Returns a consumer over the encrypted gates of a circuit. /// /// # Arguments @@ -126,86 +79,6 @@ impl Evaluator { &'a mut self, circ: &'a Circuit, inputs: &[Mac], - ) -> Result>, EvaluatorError> { - self.alloc_worker(circ.and_count())?.evaluate(circ, inputs) - } - - /// Returns a consumer over batched encrypted gates of a circuit. - /// - /// # Arguments - /// - /// * `circ` - The circuit to evaluate. - /// * `inputs` - The input labels to the circuit. - pub fn evaluate_batched<'a>( - &'a mut self, - circ: &'a Circuit, - inputs: &[Mac], - ) -> Result>, EvaluatorError> { - self.evaluate(circ, inputs).map(EncryptedGateBatchConsumer) - } - - /// Returns whether evaluator was set up. - pub fn is_setup(&self) -> bool { - matches!(self.state, State::Setup(_)) - } -} - -#[derive(Debug)] -pub(crate) enum State { - Initialized, - Setup(Setup), - Error, -} - -impl State { - pub(crate) fn take(&mut self) -> State { - std::mem::replace(self, State::Error) - } -} - -#[derive(Debug)] -pub(crate) struct Setup { - /// The id to be assigned to the next evaluated AND gate. - current_gid: u128, - /// Key for the cipher used to encrypt the gates. - key: [u8; 16], -} - -impl Setup { - /// Allocates ids for the given `count` of AND gates, returning the - /// current id. - fn alloc(&mut self, count: usize) -> u128 { - let old = self.current_gid; - // Each AND gates consumes 2 ids. - self.current_gid += (count * 2) as u128; - old - } -} - -/// A worker responsible for evaluating a single circuit. -/// -/// Multiple workers can be run in paraller to evaluate multiple circuits. -pub struct EvaluatorWorker { - /// Initial AND gate id of the circuit. - initial_id: u128, - /// AND gate count to be evaluated. - count: usize, - /// Key for the cipher used to encrypt the gates. - key: [u8; 16], -} - -impl EvaluatorWorker { - /// Returns a consumer over the encrypted gates of a circuit, consuming - /// the worker. - /// - /// # Arguments - /// - /// * `circ` - The circuit to evaluate. - /// * `inputs` - The input labels to the circuit. - pub fn evaluate<'a>( - self, - circ: &'a Circuit, - inputs: &[Mac], ) -> Result>, EvaluatorError> { if inputs.len() != circ.inputs().len() { return Err(EvaluatorError::InputLength { @@ -214,35 +87,29 @@ impl EvaluatorWorker { }); } - if circ.and_count() > self.count { - return Err(EvaluatorError::GateCountMismatch { - expected: self.count, - actual: circ.and_count(), - }); + // Expand the buffer to fit the circuit + if circ.feed_count() > self.buffer.len() { + self.buffer.resize(circ.feed_count(), Default::default()); } - let mut buffer = vec![Default::default(); circ.feed_count()]; - buffer[..inputs.len()].copy_from_slice(Mac::as_blocks(inputs)); + self.buffer[..inputs.len()].copy_from_slice(Mac::as_blocks(inputs)); Ok(EncryptedGateConsumer::new( circ.gates().iter(), - buffer, + &mut self.buffer, circ.and_count(), circ.outputs(), - self.initial_id, - FixedKeyAes::new(self.key), )) } - /// Returns a consumer over batched encrypted gates of a circuit, consuming - /// the worker. + /// Returns a consumer over batched encrypted gates of a circuit. /// /// # Arguments /// /// * `circ` - The circuit to evaluate. /// * `inputs` - The input labels to the circuit. pub fn evaluate_batched<'a>( - self, + &'a mut self, circ: &'a Circuit, inputs: &[Mac], ) -> Result>, EvaluatorError> { @@ -253,13 +120,13 @@ impl EvaluatorWorker { /// Consumer over the encrypted gates of a circuit. pub struct EncryptedGateConsumer<'a, I: Iterator> { /// Cipher to use to encrypt the gates. - cipher: FixedKeyAes, + cipher: &'static FixedKeyAes, /// Buffer for the active labels. - labels: Vec, + labels: &'a mut [Block], /// Iterator over the gates. gates: I, - /// Current AND gate id. - gid: u128, + /// Current gate id. + gid: usize, /// Number of AND gates evaluated. counter: usize, /// Total number of AND gates in the circuit. @@ -268,7 +135,6 @@ pub struct EncryptedGateConsumer<'a, I: Iterator> { outputs: Range, /// Whether the entire circuit has been garbled. complete: bool, - pd: PhantomData<&'a ()>, } impl fmt::Debug for EncryptedGateConsumer<'_, I> { @@ -281,24 +147,16 @@ impl<'a, I> EncryptedGateConsumer<'a, I> where I: Iterator, { - fn new( - gates: I, - labels: Vec, - and_count: usize, - outputs: Range, - gid: u128, - cipher: FixedKeyAes, - ) -> Self { + fn new(gates: I, labels: &'a mut [Block], and_count: usize, outputs: Range) -> Self { Self { - cipher, + cipher: &(*FIXED_KEY_AES), gates, labels, - gid, + gid: 1, counter: 0, and_count, outputs, complete: false, - pd: PhantomData, } } @@ -329,7 +187,7 @@ where } => { let x = self.labels[node_x.id()]; let y = self.labels[node_y.id()]; - let z = and_gate(&self.cipher, &x, &y, &encrypted_gate, self.gid); + let z = and_gate(self.cipher, &x, &y, &encrypted_gate, self.gid); self.labels[node_z.id()] = z; self.gid += 2; @@ -415,24 +273,24 @@ where /// Evaluates multiple garbled circuits in bulk. pub fn evaluate_garbled_circuits( circs: Vec<(Arc, Vec, GarbledCircuit)>, - workers: Vec, ) -> Result, EvaluatorError> { - debug_assert!(circs.len() == workers.len()); cfg_if! { if #[cfg(feature = "rayon")] { use rayon::prelude::*; - circs.into_par_iter().zip(workers.into_par_iter()).map(|((circ, inputs, garbled_circuit), wrk)| { - let mut consumer = wrk.evaluate(&circ, &inputs)?; + circs.into_par_iter().map(|(circ, inputs, garbled_circuit)| { + let mut ev = Evaluator::with_capacity(circ.feed_count()); + let mut consumer = ev.evaluate(&circ, &inputs)?; for gate in garbled_circuit.gates { consumer.next(gate); } consumer.finish() }).collect::, _>>() } else { + let mut ev = Evaluator::default(); let mut outputs = Vec::with_capacity(circs.len()); - for ((circ, inputs, garbled_circuit), wrk) in circs.into_iter().zip(workers.into_iter()) { - let mut consumer = wrk.evaluate(&circ, &inputs)?; + for (circ, inputs, garbled_circuit) in circs { + let mut consumer = ev.evaluate(&circ, &inputs)?; for gate in garbled_circuit.gates { consumer.next(gate); } diff --git a/crates/garble-core/src/garbler.rs b/crates/garble-core/src/garbler.rs index e66e850a..18c18637 100644 --- a/crates/garble-core/src/garbler.rs +++ b/crates/garble-core/src/garbler.rs @@ -1,9 +1,12 @@ use core::fmt; -use std::{marker::PhantomData, ops::Range}; +use std::ops::Range; -use crate::{DEFAULT_BATCH_SIZE, EncryptedGateBatch, SetupMsg, circuit::EncryptedGate}; +use crate::{DEFAULT_BATCH_SIZE, EncryptedGateBatch, circuit::EncryptedGate}; use mpz_circuits::{Circuit, Gate}; -use mpz_core::{Block, aes::FixedKeyAes}; +use mpz_core::{ + Block, + aes::{FIXED_KEY_AES, FixedKeyAes}, +}; use mpz_memory_core::correlated::{Delta, Key}; /// Errors that can occur during garbled circuit generation. @@ -14,22 +17,16 @@ pub enum GarblerError { InputLength { expected: usize, actual: usize }, #[error("garbler not finished")] NotFinished, - #[error("attempted to set up garbler twice")] - AlreadySetup, - #[error("garbler was not in set up state as expected")] - NotSetup, - #[error("AND gate count mismatch: expected no more than {expected}, got {actual}")] - GateCountMismatch { expected: usize, actual: usize }, } -/// Computes half-gate garbled AND gate. +/// Computes half-gate garbled AND gate #[inline] pub(crate) fn and_gate( cipher: &FixedKeyAes, x_0: &Block, y_0: &Block, delta: &Delta, - gid: u128, + gid: usize, ) -> (Block, EncryptedGate) { let delta = delta.as_block(); let x_1 = x_0 ^ delta; @@ -37,8 +34,8 @@ pub(crate) fn and_gate( let p_a = x_0.lsb(); let p_b = y_0.lsb(); - let j = Block::new(gid.to_be_bytes()); - let k = Block::new((gid + 1).to_be_bytes()); + let j = Block::new((gid as u128).to_be_bytes()); + let k = Block::new(((gid + 1) as u128).to_be_bytes()); let mut h = [*x_0, *y_0, x_1, y_1]; cipher.tccr_many(&[j, k, j, k], &mut h); @@ -65,143 +62,25 @@ pub struct GarblerOutput { pub outputs: Vec, } -/// Garbled circuit generator. -#[derive(Debug)] +/// Garbler. +#[derive(Debug, Default)] pub struct Garbler { - delta: Delta, - state: State, + /// Buffer for the 0-bit labels. + buffer: Vec, } impl Garbler { - /// Creates a new garbler with the given `seed` and `delta`. - /// - /// The seed is used to derive the initial gate ID and cipher key - /// for multi-instance security (see ). - pub fn new(seed: [u8; 16], delta: Delta) -> Self { - // Randomize gate id for better multi-instance security - // https://eprint.iacr.org/2019/1168 Section 5. - let gid_bytes: [u8; 16] = *blake3::derive_key("mpz-garble-core gid", &seed) - .first_chunk() - .unwrap(); - - // Randomize the key of the fixed-key cipher to confine - // security degradation to a single instance, preventing - // it from compounding across multiple instances - // (see Fig. 4 in https://eprint.iacr.org/2019/1168). - let key: [u8; 16] = *blake3::derive_key("mpz-garble-core key", &seed) - .first_chunk() - .unwrap(); - - Self { - state: State::Initialized(Initialized { - initial_gid: u128::from_le_bytes(gid_bytes), - key, - }), - delta, - } - } - - /// Sets up the garbler returning a setup message. - pub fn setup(&mut self) -> Result { - let state = if let State::Initialized(state) = self.state.take() { - state - } else { - return Err(GarblerError::AlreadySetup); - }; - - let msg = SetupMsg { - initial_gid: state.initial_gid, - key: state.key, - }; - - self.state = State::Setup(Setup { - current_gid: state.initial_gid, - key: state.key, - }); - - Ok(msg) - } - - /// Allocates a worker for the given `count` of AND gates. - pub fn alloc_worker(&mut self, count: usize) -> Result { - let mut state = if let State::Setup(state) = self.state.take() { - state - } else { - return Err(GarblerError::NotSetup); - }; - - let worker = GarblerWorker { - initial_id: state.alloc(count), - count, - key: state.key, - delta: self.delta, - }; - - self.state = State::Setup(state); - - Ok(worker) - } - /// Returns an iterator over the encrypted gates of a circuit. /// /// # Arguments /// /// * `circ` - The circuit to garble. + /// * `delta` - The delta value to use for garbling. /// * `inputs` - The input labels to the circuit. pub fn generate<'a>( &'a mut self, circ: &'a Circuit, - inputs: &[Key], - ) -> Result>, GarblerError> { - self.alloc_worker(circ.and_count())?.generate(circ, inputs) - } - - /// Returns an iterator over batched encrypted gates of a circuit. - /// - /// # Arguments - /// - /// * `circ` - The circuit to garble. - /// * `inputs` - The input labels to the circuit. - pub fn generate_batched<'a>( - &'a mut self, - circ: &'a Circuit, - inputs: &[Key], - ) -> Result>, GarblerError> { - self.alloc_worker(circ.and_count())? - .generate(circ, inputs) - .map(EncryptedGateBatchIter) - } - - /// Returns whether garbler was set up. - pub fn is_setup(&self) -> bool { - matches!(self.state, State::Setup(_)) - } -} - -/// A worker responsible for garbling a single circuit. -/// -/// Multiple workers can be run in paraller to garble multiple circuits. -pub struct GarblerWorker { - /// Initial AND gate id of the circuit. - initial_id: u128, - /// AND gate count to be garbled. - count: usize, - /// Key for the cipher used to encrypt the gates. - key: [u8; 16], - delta: Delta, -} - -impl GarblerWorker { - /// Returns an iterator over the encrypted gates of a circuit, consuming - /// the worker. - /// - /// # Arguments - /// - /// * `circ` - The circuit to garble. - /// * `inputs` - The input labels to the circuit. - pub fn generate<'a>( - self, - circ: &'a Circuit, + delta: Delta, inputs: &[Key], ) -> Result>, GarblerError> { if inputs.len() != circ.inputs().len() { @@ -211,95 +90,52 @@ impl GarblerWorker { }); } - if circ.and_count() > self.count { - return Err(GarblerError::GateCountMismatch { - expected: self.count, - actual: circ.and_count(), - }); + // Expand the buffer to fit the circuit + if circ.feed_count() > self.buffer.len() { + self.buffer.resize(circ.feed_count(), Default::default()); } - let mut buffer = vec![Default::default(); circ.feed_count()]; - buffer[..inputs.len()].copy_from_slice(Key::as_blocks(inputs)); + self.buffer[..inputs.len()].copy_from_slice(Key::as_blocks(inputs)); Ok(EncryptedGateIter::new( - self.delta, + delta, circ.gates().iter(), - buffer, - self.initial_id, + &mut self.buffer, circ.and_count(), circ.outputs(), - FixedKeyAes::new(self.key), )) } - /// Returns an iterator over batched encrypted gates of a circuit, - /// consuming the worker. + /// Returns an iterator over batched encrypted gates of a circuit. /// /// # Arguments /// /// * `circ` - The circuit to garble. + /// * `delta` - The delta value to use for garbling. /// * `inputs` - The input labels to the circuit. pub fn generate_batched<'a>( - self, + &'a mut self, circ: &'a Circuit, + delta: Delta, inputs: &[Key], ) -> Result>, GarblerError> { - self.generate(circ, inputs).map(EncryptedGateBatchIter) - } -} - -#[derive(Debug)] -enum State { - Initialized(Initialized), - Setup(Setup), - Error, -} - -impl State { - pub(crate) fn take(&mut self) -> State { - std::mem::replace(self, State::Error) - } -} - -#[derive(Debug)] -struct Initialized { - /// Initial gate id. - pub(super) initial_gid: u128, - /// Key for the cipher used to encrypt the gates. - pub(super) key: [u8; 16], -} - -#[derive(Debug)] -struct Setup { - /// The id to be assigned to the next garbled AND gate. - pub current_gid: u128, - /// Key for the cipher used to encrypt the gates. - pub key: [u8; 16], -} - -impl Setup { - /// Allocates ids for the given `count` of AND gates, returning the - /// current id. - fn alloc(&mut self, count: usize) -> u128 { - let old = self.current_gid; - // Each AND gates consumes 2 ids. - self.current_gid += (count * 2) as u128; - old + self.generate(circ, delta, inputs) + .map(EncryptedGateBatchIter) } } /// Iterator over encrypted gates of a garbled circuit. pub struct EncryptedGateIter<'a, I> { /// Cipher to use to encrypt the gates. - cipher: FixedKeyAes, + cipher: &'static FixedKeyAes, /// Global offset. delta: Delta, /// Buffer for the 0-bit labels. - labels: Vec, + labels: &'a mut [Block], /// Iterator over the gates. gates: I, - /// Current AND gate id. - gid: u128, + /// Current gate id. + gid: usize, /// Number of AND gates generated. counter: usize, /// Number of AND gates in the circuit. @@ -308,7 +144,6 @@ pub struct EncryptedGateIter<'a, I> { outputs: Range, /// Whether the entire circuit has been garbled. complete: bool, - pd: PhantomData<&'a ()>, } impl fmt::Debug for EncryptedGateIter<'_, I> { @@ -324,23 +159,20 @@ where fn new( delta: Delta, gates: I, - labels: Vec, - gid: u128, + labels: &'a mut [Block], and_count: usize, outputs: Range, - cipher: FixedKeyAes, ) -> Self { Self { - cipher, + cipher: &(*FIXED_KEY_AES), delta, gates, labels, - gid, + gid: 1, counter: 0, and_count, outputs, complete: false, - pd: PhantomData, } } @@ -395,7 +227,7 @@ where let x_0 = self.labels[node_x.id()]; let y_0 = self.labels[node_y.id()]; let (z_0, encrypted_gate) = - and_gate(&self.cipher, &x_0, &y_0, &self.delta, self.gid); + and_gate(self.cipher, &x_0, &y_0, &self.delta, self.gid); self.labels[node_z.id()] = z_0; self.gid += 2; diff --git a/crates/garble-core/src/lib.rs b/crates/garble-core/src/lib.rs index 48463b3c..b66a25f8 100644 --- a/crates/garble-core/src/lib.rs +++ b/crates/garble-core/src/lib.rs @@ -14,13 +14,12 @@ pub(crate) mod view; pub use circuit::{EncryptedGate, EncryptedGateBatch, GarbledCircuit}; pub use evaluator::{ EncryptedGateBatchConsumer, EncryptedGateConsumer, Evaluator, EvaluatorError, EvaluatorOutput, - EvaluatorWorker, evaluate_garbled_circuits, + evaluate_garbled_circuits, }; pub use garbler::{ - EncryptedGateBatchIter, EncryptedGateIter, Garbler, GarblerError, GarblerOutput, GarblerWorker, + EncryptedGateBatchIter, EncryptedGateIter, Garbler, GarblerError, GarblerOutput, }; pub use mpz_memory_core::correlated::{Delta, Key, Mac}; -use serde::{Deserialize, Serialize}; pub use view::FlushView; const KB: usize = 1024; @@ -38,15 +37,6 @@ const MAX_BATCH_SIZE: usize = 4 * KB; /// This puts an upper limit on that waste. pub(crate) const DEFAULT_BATCH_SIZE: usize = MAX_BATCH_SIZE / BYTES_PER_GATE; -/// Setup message passed from the garbler to the evaluator. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SetupMsg { - /// Initial AND gate id. - initial_gid: u128, - /// Key for the cipher used to encrypt the gates. - key: [u8; 16], -} - #[cfg(test)] mod tests { use aes::{ @@ -75,7 +65,7 @@ mod tests { let x_1 = x_0 ^ delta.as_block(); let y_0 = Block::random(&mut rng); let y_1 = y_0 ^ delta.as_block(); - let gid: u128 = 1; + let gid: usize = 1; let (z_0, encrypted_gate) = gb::and_gate(cipher, &x_0, &y_0, &delta, gid); let z_1 = z_0 ^ delta.as_block(); @@ -111,12 +101,10 @@ mod tests { .map(|(key, bit)| key.auth(bit, &delta)) .collect::>(); - let mut gb = Garbler::new(rng.random(), delta); - let setup = gb.setup().unwrap(); + let mut gb = Garbler::default(); let mut ev = Evaluator::default(); - ev.setup(setup).unwrap(); - let mut gb_iter = gb.generate_batched(&AES128, &input_keys).unwrap(); + let mut gb_iter = gb.generate_batched(&AES128, delta, &input_keys).unwrap(); let mut ev_consumer = ev.evaluate_batched(&AES128, &input_macs).unwrap(); for batch in gb_iter.by_ref() { @@ -173,51 +161,27 @@ mod tests { .map(|(key, bit)| key.auth(bit, &delta)) .collect::>(); - let mut gb = Garbler::new(rng.random(), delta); - let setup = gb.setup().unwrap(); - let mut ev = Evaluator::default(); - ev.setup(setup).unwrap(); - - // Allocate 2 workers from garbler - let gb_worker1 = gb.alloc_worker(AES128.and_count()).unwrap(); - let gb_worker2 = gb.alloc_worker(AES128.and_count()).unwrap(); + let mut gb = Garbler::default(); + let mut gb_iter = gb.generate_batched(&AES128, delta, &input_keys).unwrap(); - // Garble circuit 1 with worker1 - let mut gb_iter1 = gb_worker1.generate_batched(&AES128, &input_keys).unwrap(); - let mut gates1 = Vec::new(); - for batch in gb_iter1.by_ref() { - gates1.extend(batch.into_array()); - } - let garbled_circuit1 = GarbledCircuit { gates: gates1 }; - let GarblerOutput { - outputs: output_keys1, - } = gb_iter1.finish().unwrap(); - - // Garble circuit 2 with worker2 - let mut gb_iter2 = gb_worker2.generate_batched(&AES128, &input_keys).unwrap(); - let mut gates2 = Vec::new(); - for batch in gb_iter2.by_ref() { - gates2.extend(batch.into_array()); + let mut gates = Vec::new(); + for batch in gb_iter.by_ref() { + gates.extend(batch.into_array()); } - let garbled_circuit2 = GarbledCircuit { gates: gates2 }; + + let garbled_circuit = GarbledCircuit { gates }; + let GarblerOutput { - outputs: output_keys2, - } = gb_iter2.finish().unwrap(); - - // Allocate 2 workers from evaluator - let outputs = evaluate_garbled_circuits( - vec![ - (AES128.clone(), input_macs.clone(), garbled_circuit1), - (AES128.clone(), input_macs.clone(), garbled_circuit2), - ], - vec![ - ev.alloc_worker(AES128.and_count()).unwrap(), - ev.alloc_worker(AES128.and_count()).unwrap(), - ], - ) + outputs: output_keys, + } = gb_iter.finish().unwrap(); + + let outputs = evaluate_garbled_circuits(vec![ + (AES128.clone(), input_macs.clone(), garbled_circuit.clone()), + (AES128.clone(), input_macs.clone(), garbled_circuit.clone()), + ]) .unwrap(); - for (output, output_keys) in outputs.into_iter().zip([output_keys1, output_keys2]) { + for output in outputs { let EvaluatorOutput { outputs: output_macs, } = output; @@ -264,12 +228,10 @@ mod tests { .map(|(key, bit)| key.auth(bit, &delta)) .collect::>(); - let mut gb = Garbler::new(rng.random(), delta); - let setup = gb.setup().unwrap(); + let mut gb = Garbler::default(); let mut ev = Evaluator::default(); - ev.setup(setup).unwrap(); - let mut gb_iter = gb.generate_batched(&circ, &input_keys).unwrap(); + let mut gb_iter = gb.generate_batched(&circ, delta, &input_keys).unwrap(); let mut ev_consumer = ev.evaluate_batched(&circ, &input_macs).unwrap(); for batch in gb_iter.by_ref() { diff --git a/crates/garble-core/src/store/evaluator.rs b/crates/garble-core/src/store/evaluator.rs index 5f70e6be..c7ce5d87 100644 --- a/crates/garble-core/src/store/evaluator.rs +++ b/crates/garble-core/src/store/evaluator.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use blake3::Hasher; use rangeset::{iter::RangeIterator, ops::Set}; use tokio::sync::{Mutex, OwnedMutexGuard}; @@ -158,7 +157,6 @@ impl EvaluatorStore { *bit ^= mac_bit; }); - let mut hasher = Hasher::new(); let start_id = slice.ptr().as_usize(); for (i, ((mac, bit), commitment)) in self .mac_store @@ -168,8 +166,7 @@ impl EvaluatorStore { .zip(self.commit_store.try_get(slice)?) .enumerate() { - hasher.reset(); - commitment.check((start_id + i) as u64, *bit, mac, &mut hasher)?; + commitment.check((start_id + i) as u64, *bit, mac)?; } self.data_store.try_set(slice, &data)?; diff --git a/crates/garble/Cargo.toml b/crates/garble/Cargo.toml index 1930aa32..37c1f28d 100644 --- a/crates/garble/Cargo.toml +++ b/crates/garble/Cargo.toml @@ -25,7 +25,6 @@ mpz-vm-core = { workspace = true } serio = { workspace = true } async-trait = { workspace = true } -blake3 = { workspace = true } derive_builder = { workspace = true } futures = { workspace = true } hashbrown = { workspace = true } diff --git a/crates/garble/src/evaluator.rs b/crates/garble/src/evaluator.rs index 5b2c2b2d..114cb418 100644 --- a/crates/garble/src/evaluator.rs +++ b/crates/garble/src/evaluator.rs @@ -2,7 +2,9 @@ use std::sync::Arc; use mpz_circuits::Circuit; use mpz_common::Context; -use mpz_garble_core::{EncryptedGateBatch, EvaluatorOutput, EvaluatorWorker, GarbledCircuit}; +use mpz_garble_core::{ + EncryptedGateBatch, Evaluator as EvaluatorCore, EvaluatorOutput, GarbledCircuit, +}; use mpz_memory_core::correlated::Mac; use serio::stream::IoStreamExt; @@ -38,15 +40,14 @@ pub(crate) async fn receive_garbled_circuit( /// * `ctx` - The context to use. /// * `circ` - The circuit to evaluate. /// * `inputs` - The inputs of the circuit. -/// * `worker` - The worker to use. #[tracing::instrument(fields(thread = %ctx.id()), skip_all)] pub(crate) async fn evaluate( ctx: &mut Context, circ: Arc, inputs: &[Mac], - worker: EvaluatorWorker, ) -> Result { - let mut ev_consumer = worker.evaluate_batched(&circ, inputs)?; + let mut ev = EvaluatorCore::default(); + let mut ev_consumer = ev.evaluate_batched(&circ, inputs)?; let io = ctx.io_mut(); while ev_consumer.wants_gates() { diff --git a/crates/garble/src/garbler.rs b/crates/garble/src/garbler.rs index 5422ec2c..4a8ae674 100644 --- a/crates/garble/src/garbler.rs +++ b/crates/garble/src/garbler.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use mpz_circuits::Circuit; use mpz_common::Context; -use mpz_garble_core::{GarblerOutput, GarblerWorker}; -use mpz_memory_core::correlated::Key; +use mpz_garble_core::{Garbler as GarblerCore, GarblerOutput}; +use mpz_memory_core::correlated::{Delta, Key}; use serio::SinkExt; /// Generate a garbled circuit, streaming the encrypted gates to the evaluator @@ -18,16 +18,18 @@ use serio::SinkExt; /// /// * `ctx` - The context to use. /// * `circ` - The circuit to garble. +/// * `delta` - The garblers delta value. /// * `inputs` - The inputs of the circuit. -/// * `worker` - The worker to use. +/// * `hash` - Whether to hash the circuit. #[tracing::instrument(fields(thread = %ctx.id()), skip_all)] pub(crate) async fn generate( ctx: &mut Context, circ: Arc, + delta: Delta, inputs: &[Key], - worker: GarblerWorker, ) -> Result { - let mut gb_iter = worker.generate_batched(&circ, inputs)?; + let mut gb = GarblerCore::default(); + let mut gb_iter = gb.generate_batched(&circ, delta, inputs)?; let io = ctx.io_mut(); while let Some(batch) = gb_iter.by_ref().next() { diff --git a/crates/garble/src/protocol/semihonest/evaluator.rs b/crates/garble/src/protocol/semihonest/evaluator.rs index 93ed0a6b..dfd7e3cd 100644 --- a/crates/garble/src/protocol/semihonest/evaluator.rs +++ b/crates/garble/src/protocol/semihonest/evaluator.rs @@ -2,15 +2,11 @@ use std::sync::Arc; use async_trait::async_trait; use hashbrown::HashMap; -use serio::stream::IoStreamExt; use tokio::sync::Mutex; use mpz_common::{Context, Flush}; use mpz_core::{Block, bitvec::BitVec}; -use mpz_garble_core::{ - Evaluator as Core, EvaluatorOutput, EvaluatorWorker, GarbledCircuit, SetupMsg, - evaluate_garbled_circuits, -}; +use mpz_garble_core::{EvaluatorOutput, GarbledCircuit, evaluate_garbled_circuits}; use mpz_memory_core::{DecodeFuture, Memory, Slice, View, binary::Binary}; use mpz_ot::cot::COTReceiver; use mpz_vm_core::{Call, Callable, Execute, Result, VmError}; @@ -26,7 +22,6 @@ pub struct Evaluator { store: Arc>>, call_stack: Vec<(Call, Slice)>, preprocessed: HashMap, - core: Arc>, } impl Evaluator { @@ -36,7 +31,6 @@ impl Evaluator { store: Arc::new(Mutex::new(EvaluatorStore::new(cot))), call_stack: Vec::new(), preprocessed: HashMap::new(), - core: Arc::new(Mutex::new(Core::default())), } } @@ -81,23 +75,12 @@ impl Evaluator { break; } - let workers = calls - .iter() - .map(|(call, _, _)| { - self.core - .try_lock() - .unwrap() - .alloc_worker(call.and_count()) - .expect("execute_preprocessed is always called after core was set up") - }) - .collect::>(); - for ( EvaluatorOutput { outputs: output_macs, }, output, - ) in evaluate_garbled_circuits(calls, workers) + ) in evaluate_garbled_circuits(calls) .map_err(VmError::execute)? .into_iter() .zip(outputs) @@ -245,14 +228,6 @@ where async fn preprocess(&mut self, ctx: &mut Context) -> Result<()> { let mut cot = self.store.try_lock().unwrap().acquire_cot(); - { - let mut core = self.core.try_lock().unwrap(); - if !core.is_setup() { - let msg: SetupMsg = ctx.io_mut().expect_next().await?; - core.setup(msg).map_err(VmError::execute)?; - } - } - let mut call_stack = std::mem::take(&mut self.call_stack); let (_, preprocessed) = ctx @@ -326,14 +301,6 @@ where self.execute_preprocessed()?; } - { - let mut core = self.core.try_lock().unwrap(); - if !core.is_setup() { - let msg: SetupMsg = ctx.io_mut().expect_next().await?; - core.setup(msg).map_err(VmError::execute)?; - } - } - while !self.call_stack.is_empty() { let calls = self.take_execute_calls(); @@ -341,28 +308,14 @@ where break; } - let mut core = self.core.try_lock().unwrap(); - let workers = calls - .iter() - .map(|call| { - core.alloc_worker(call.0.circ().and_count()) - .expect("core was set up") - }) - .collect::>(); - - let iter = calls - .into_iter() - .zip(workers.into_iter()) - .collect::>(); - let store = self.store.clone(); let outputs = ctx .map( - iter, - async move |ctx, ((call, output), wrk): ((Call, Slice), EvaluatorWorker)| { - evaluate(ctx, store.clone(), call, output, wrk).await + calls, + async move |ctx, (call, output): (Call, Slice)| { + evaluate(ctx, store.clone(), call, output).await }, - |((call, _), _)| call.circ().and_count(), + |(call, _)| call.circ().and_count(), ) .await .map_err(VmError::execute)?; @@ -385,7 +338,6 @@ async fn evaluate( store: Arc>>, call: Call, output: Slice, - worker: EvaluatorWorker, ) -> Result<()> { let (circ, inputs) = call.into_parts(); @@ -399,7 +351,7 @@ async fn evaluate( let EvaluatorOutput { outputs: output_macs, - } = crate::evaluator::evaluate(ctx, circ, &input_macs, worker) + } = crate::evaluator::evaluate(ctx, circ, &input_macs) .await .map_err(VmError::execute)?; diff --git a/crates/garble/src/protocol/semihonest/garbler.rs b/crates/garble/src/protocol/semihonest/garbler.rs index 8a6531a7..3355a48b 100644 --- a/crates/garble/src/protocol/semihonest/garbler.rs +++ b/crates/garble/src/protocol/semihonest/garbler.rs @@ -1,12 +1,11 @@ use std::sync::Arc; use async_trait::async_trait; -use serio::SinkExt; use tokio::sync::Mutex; use mpz_common::{Context, Flush}; use mpz_core::{Block, bitvec::BitVec}; -use mpz_garble_core::{Garbler as Core, GarblerOutput, GarblerWorker}; +use mpz_garble_core::GarblerOutput; use mpz_memory_core::{DecodeFuture, Memory, Slice, View, binary::Binary, correlated::Delta}; use mpz_ot::cot::COTSender; use mpz_vm_core::{Call, Callable, Execute, Result, VmError}; @@ -19,25 +18,15 @@ pub struct Garbler { store: Arc>>, call_stack: Vec<(Call, Slice)>, preprocessed: Vec<(Vec, Slice)>, - core: Arc>, } impl Garbler { /// Creates a new garbler. pub fn new(cot: COT, seed: [u8; 16], delta: Delta) -> Self { - // Derive separate seeds for store and core to avoid PRG reuse - let store_seed: [u8; 16] = *blake3::derive_key("mpz-garble store", &seed) - .first_chunk() - .unwrap(); - let core_seed: [u8; 16] = *blake3::derive_key("mpz-garble core", &seed) - .first_chunk() - .unwrap(); - Self { - store: Arc::new(Mutex::new(GarblerStore::new(store_seed, delta, cot))), + store: Arc::new(Mutex::new(GarblerStore::new(seed, delta, cot))), call_stack: Vec::new(), preprocessed: Vec::new(), - core: Arc::new(Mutex::new(Core::new(core_seed, delta))), } } @@ -209,13 +198,10 @@ where } async fn preprocess(&mut self, ctx: &mut Context) -> Result<()> { - let mut cot = self.store.try_lock().unwrap().acquire_cot(); - - let mut core = Mutex::try_lock_owned(self.core.clone()).unwrap(); - if !core.is_setup() { - let msg = core.setup().expect("core was not set up"); - ctx.io_mut().send(msg).await?; - } + let (delta, mut cot) = { + let store = self.store.try_lock().unwrap(); + (*store.delta(), store.acquire_cot()) + }; let mut call_stack = std::mem::take(&mut self.call_stack); let store = self.store.clone(); @@ -240,43 +226,18 @@ where // in a non-empty call stack. debug_assert!(!calls.is_empty()); - let workers = calls - .iter() - .map(|call| { - core.alloc_worker(call.0.circ().and_count()) - .expect("core was set up") - }) - .collect::>(); - - let iter = calls - .into_iter() - .zip(workers.into_iter()) - .collect::>(); - let store = store.clone(); - let outputs = - ctx - .map( - iter, - async move |ctx: &mut Context, - ((call, output), wrk): ( - (Call, Slice), - GarblerWorker, - )| { - generate( - ctx, - store.clone(), - call, - output, - Mode::Preprocess, - wrk, - ) + let outputs = ctx + .map( + calls, + async move |ctx: &mut Context, (call, output): (Call, Slice)| { + generate(ctx, store.clone(), delta, call, output, Mode::Preprocess) .await - }, - |((call, _), _)| call.circ().and_count(), - ) - .await - .map_err(VmError::execute)?; + }, + |(call, _)| call.circ().and_count(), + ) + .await + .map_err(VmError::execute)?; outputs.into_iter().collect::>()?; } @@ -307,11 +268,7 @@ where async fn execute(&mut self, ctx: &mut Context) -> Result<()> { self.mark_executed()?; - let mut core = Mutex::try_lock_owned(self.core.clone()).unwrap(); - if !core.is_setup() { - let msg = core.setup().expect("core was not set up"); - ctx.io_mut().send(msg).await?; - } + let delta = *self.store.try_lock().unwrap().delta(); while !self.call_stack.is_empty() { let calls = self.take_execute_calls(); @@ -320,27 +277,14 @@ where break; } - let workers = calls - .iter() - .map(|call| { - core.alloc_worker(call.0.circ().and_count()) - .expect("core was set up") - }) - .collect::>(); - - let iter = calls - .into_iter() - .zip(workers.into_iter()) - .collect::>(); - let store = self.store.clone(); let outputs = ctx .map( - iter, - async move |ctx: &mut Context, ((call, output), wrk): ((Call, Slice), GarblerWorker)| { - generate(ctx, store.clone(), call, output, Mode::Execute, wrk).await + calls, + async move |ctx: &mut Context, (call, output): (Call, Slice)| { + generate(ctx, store.clone(), delta, call, output, Mode::Execute).await }, - |((call, _), _)| call.circ().and_count(), + |(call, _)| call.circ().and_count(), ) .await .map_err(VmError::execute)?; @@ -362,10 +306,10 @@ enum Mode { async fn generate( ctx: &mut Context, store: Arc>>, + delta: Delta, call: Call, output: Slice, mode: Mode, - worker: GarblerWorker, ) -> Result<()> { let (circ, inputs) = call.into_parts(); @@ -379,7 +323,7 @@ async fn generate( let GarblerOutput { outputs: output_keys, - } = crate::garbler::generate(ctx, circ, &input_keys, worker) + } = crate::garbler::generate(ctx, circ, delta, &input_keys) .await .map_err(VmError::execute)?; diff --git a/crates/garble/src/store/garbler.rs b/crates/garble/src/store/garbler.rs index 9857eff1..35dba70b 100644 --- a/crates/garble/src/store/garbler.rs +++ b/crates/garble/src/store/garbler.rs @@ -25,6 +25,10 @@ impl GarblerStore { } } + pub(crate) fn delta(&self) -> &Delta { + self.core.delta() + } + /// Returns a lock on the COT sender. pub(crate) fn acquire_cot(&self) -> OwnedMutexGuard { self.core.acquire_cot() diff --git a/crates/memory-core/src/correlated.rs b/crates/memory-core/src/correlated.rs index 14461a5f..1b8ed9b6 100644 --- a/crates/memory-core/src/correlated.rs +++ b/crates/memory-core/src/correlated.rs @@ -195,13 +195,7 @@ impl BitXor<&Delta> for &Block { pub struct MacCommitment(pub(crate) [Block; 2]); impl MacCommitment { - pub fn check( - &self, - id: u64, - value: bool, - mac: &Mac, - hasher: &mut Hasher, - ) -> Result<(), MacCommitmentError> { + pub fn check(&self, id: u64, value: bool, mac: &Mac) -> Result<(), MacCommitmentError> { let [low, high] = &self.0; let select = &self.0[value as usize]; @@ -213,6 +207,7 @@ impl MacCommitment { }); } + let mut hasher = Hasher::new(); hasher.update(&id.to_be_bytes()); hasher.update(mac.as_bytes()); let out: [u8; 16] = hasher.finalize().as_bytes()[0..16] diff --git a/crates/memory-core/src/correlated/keys.rs b/crates/memory-core/src/correlated/keys.rs index 1832d83e..bff9a71b 100644 --- a/crates/memory-core/src/correlated/keys.rs +++ b/crates/memory-core/src/correlated/keys.rs @@ -43,23 +43,17 @@ impl Key { /// Commits to the MACs of a value. #[inline] - pub fn commit(&self, id: u64, delta: &Delta, hasher: &mut Hasher) -> MacCommitment { + pub fn commit(&self, id: u64, delta: &Delta) -> MacCommitment { let macs = [self.0, self.0 ^ delta.as_block()]; - let commitments = macs - .into_iter() - .map(|mac| { - hasher.reset(); - hasher.update(&id.to_be_bytes()); - hasher.update(mac.as_bytes()); - let out: [u8; 16] = hasher.finalize().as_bytes()[0..16] - .try_into() - .expect("16 bytes"); - Block::from(out) - }) - .collect::>(); + let commitments = macs.map(|mac| { + let mut hasher = Hasher::new(); + hasher.update(&id.to_be_bytes()); + hasher.update(mac.as_bytes()); + Block::new(hasher.finalize().as_bytes()[..16].try_into().unwrap()) + }); - MacCommitment(commitments.try_into().expect("only 2 MACs")) + MacCommitment(commitments) } /// Returns a MAC for the given bit. @@ -260,15 +254,11 @@ impl KeyStore { pub fn commit(&self, slice: Slice) -> Result> { let start_id = slice.ptr.0; let keys = self.keys.try_get(slice)?; - let mut hasher = Hasher::new(); let commitments = keys .iter() .enumerate() - .map(|(i, key)| { - hasher.reset(); - key.commit((start_id + i) as u64, &self.delta, &mut hasher) - }) + .map(|(i, key)| key.commit((start_id + i) as u64, &self.delta)) .collect(); Ok(commitments) diff --git a/crates/wasm-bench/src/garble/evaluator_core.rs b/crates/wasm-bench/src/garble/evaluator_core.rs index 1b23cdf2..f6f0e42d 100644 --- a/crates/wasm-bench/src/garble/evaluator_core.rs +++ b/crates/wasm-bench/src/garble/evaluator_core.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use mpz_circuits::{AES128, Circuit}; -use mpz_garble_core::{EncryptedGate, Evaluator, GarbledCircuit, Garbler, Key, SetupMsg}; +use mpz_garble_core::{EncryptedGate, Evaluator, GarbledCircuit, Garbler, Key}; use mpz_memory_core::correlated::{Delta, Mac}; use rand::{Rng, SeedableRng, rngs::StdRng}; use std::sync::Arc; @@ -18,8 +18,6 @@ struct BenchState { inputs: Vec, eval_inputs: Vec, gates: Vec, - setup: SetupMsg, - seed: [u8; 16], // Note: batches are regenerated per iteration since EncryptedGateBatch doesn't impl Clone } @@ -27,7 +25,6 @@ impl BenchState { fn new() -> Self { let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); - let seed: [u8; 16] = rng.random(); let inputs: Vec = (0..256).map(|_| rng.random()).collect(); let choices: Vec = (0..256).map(|_| rng.random()).collect(); @@ -39,9 +36,8 @@ impl BenchState { .collect(); // Pre-garble circuit for evaluation benchmarks (single gates) - let mut gb = Garbler::new(seed, delta); - let setup = gb.setup().unwrap(); - let mut iter = gb.generate(&AES128, &inputs).unwrap(); + let mut gb = Garbler::default(); + let mut iter = gb.generate(&AES128, delta, &inputs).unwrap(); let gates: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); @@ -50,8 +46,6 @@ impl BenchState { inputs, eval_inputs, gates, - setup, - seed, } } } @@ -72,7 +66,6 @@ pub fn garble_core_half_gates_evaluate(n: u32) -> BenchResult { let start = performance.now(); let mut ev = Evaluator::default(); - ev.setup(state.setup.clone()).unwrap(); for _ in 0..n { let mut consumer = ev.evaluate(&AES128, &state.eval_inputs).unwrap(); for gate in &state.gates { @@ -99,14 +92,14 @@ pub fn garble_core_half_gates_evaluate_batched(n: u32) -> BenchResult { STATE.with(|state| { let mut total_elapsed = 0.0; let mut ev = Evaluator::default(); - ev.setup(state.setup.clone()).unwrap(); for _ in 0..n { // Regenerate batches for this iteration (untimed) // EncryptedGateBatch doesn't implement Clone, so we must regenerate - let mut gb = Garbler::new(state.seed, state.delta); - let _ = gb.setup().unwrap(); - let mut iter = gb.generate_batched(&AES128, &state.inputs).unwrap(); + let mut gb = Garbler::default(); + let mut iter = gb + .generate_batched(&AES128, state.delta, &state.inputs) + .unwrap(); let batches: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); @@ -177,7 +170,6 @@ pub async fn garble_core_half_gates_evaluate_parallel(n: u32, concurrency: u32) // Setup: generate keys and macs once let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); - let seed: [u8; 16] = rng.random(); let inputs: Vec = (0..256).map(|_| rng.random()).collect(); let choices: Vec = (0..256).map(|_| rng.random()).collect(); let eval_inputs: Vec = inputs @@ -189,10 +181,9 @@ pub async fn garble_core_half_gates_evaluate_parallel(n: u32, concurrency: u32) for &circuit_count in PARALLEL_THRESHOLDS { // Pre-garble circuits for this threshold (untimed) let mut garbled_circuits = Vec::with_capacity(circuit_count); - let mut gb = Garbler::new(seed, delta); - let setup = gb.setup().unwrap(); for _ in 0..circuit_count { - let mut iter = gb.generate(&AES128, &inputs).unwrap(); + let mut gb = Garbler::default(); + let mut iter = gb.generate(&AES128, delta, &inputs).unwrap(); let gates: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); garbled_circuits.push(GarbledCircuit { gates }); @@ -202,25 +193,16 @@ pub async fn garble_core_half_gates_evaluate_parallel(n: u32, concurrency: u32) // Build input for parallel evaluation let circs: Vec<_> = garbled_circuits .iter() - .map(|gc| { - ( - circuit.clone(), - eval_inputs.clone(), - gc.clone(), - setup.clone(), - ) - }) + .map(|gc| (circuit.clone(), eval_inputs.clone(), gc.clone())) .collect(); // Timed: parallel evaluation using par_iter directly in local pool let start = performance.now(); let _outputs: Vec<_> = circs .into_par_iter() - .map(|(circ, inputs, garbled_circuit, setup)| { - let mut ev = Evaluator::default(); - ev.setup(setup).unwrap(); - let worker = ev.alloc_worker(circ.and_count()).unwrap(); - let mut consumer = worker.evaluate(&circ, &inputs).unwrap(); + .map(|(circ, inputs, garbled_circuit)| { + let mut ev = Evaluator::with_capacity(circ.feed_count()); + let mut consumer = ev.evaluate(&circ, &inputs).unwrap(); for gate in garbled_circuit.gates { consumer.next(gate); } diff --git a/crates/wasm-bench/src/garble/garbler_core.rs b/crates/wasm-bench/src/garble/garbler_core.rs index 640bb26c..65b49b47 100644 --- a/crates/wasm-bench/src/garble/garbler_core.rs +++ b/crates/wasm-bench/src/garble/garbler_core.rs @@ -13,7 +13,6 @@ use rand::{Rng, SeedableRng, rngs::StdRng}; struct BenchState { delta: Delta, inputs: Vec, - seed: [u8; 16], } impl BenchState { @@ -21,13 +20,8 @@ impl BenchState { let mut rng = StdRng::seed_from_u64(0); let delta = Delta::random(&mut rng); let inputs: Vec = (0..256).map(|_| rng.random()).collect(); - let seed: [u8; 16] = rng.random(); - Self { - delta, - inputs, - seed, - } + Self { delta, inputs } } } @@ -46,12 +40,11 @@ pub fn garble_core_aes128_and_count() -> u32 { #[wasm_bindgen] pub fn garble_core_half_gates_garble(n: u32) -> u32 { STATE.with(|state| { - let mut gb = Garbler::new(state.seed, state.delta); - let _ = gb.setup().unwrap(); + let mut gb = Garbler::default(); let mut checksum = 0u32; for _ in 0..n { - let mut iter = gb.generate(&AES128, &state.inputs).unwrap(); + let mut iter = gb.generate(&AES128, state.delta, &state.inputs).unwrap(); let gates: Vec<_> = iter.by_ref().collect(); let _ = iter.finish().unwrap(); checksum = checksum.wrapping_add(gates.len() as u32); diff --git a/crates/zk-core/src/check.rs b/crates/zk-core/src/check.rs index 244c1e88..7c0f78c2 100644 --- a/crates/zk-core/src/check.rs +++ b/crates/zk-core/src/check.rs @@ -115,11 +115,11 @@ impl Check { let (mut u, mut v) = macs .par_chunks(SEGMENT_SIZE) .enumerate() - .map_with( - rng, - |rng, (stream_id, segment)| { + .map( + |(stream_id, segment)| { + let mut rng = rng.clone(); rng.set_stream(stream_id as u64); - process_segment(rng, segment) + process_segment(&mut rng, segment) } ) .reduce( @@ -205,11 +205,11 @@ impl Check { let mut w = keys .par_chunks(SEGMENT_SIZE) .enumerate() - .map_with( - rng, - |rng, (stream_id, segment)| { + .map( + |(stream_id, segment)| { + let mut rng = rng.clone(); rng.set_stream(stream_id as u64); - process_segment(rng, segment) + process_segment(&mut rng, segment) } ) .reduce(