From ba96ab4e99d7bc04337545a01a76e6a212bc7df5 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 25 Mar 2021 15:20:14 -0700 Subject: [PATCH] More prover work --- Cargo.toml | 10 +-- src/circuit_builder.rs | 106 +++++++++++++++++++++++++++--- src/circuit_data.rs | 28 ++++---- src/constraint_polynomial.rs | 10 ++- src/field/crandall_field.rs | 51 +++++++++++++- src/field/fft.rs | 14 ++++ src/gadgets/split_join.rs | 1 + src/gates/deterministic_gate.rs | 1 + src/gates/fri_consistency_gate.rs | 8 ++- src/gates/gate.rs | 10 ++- src/gates/mod.rs | 3 +- src/gates/noop.rs | 35 ++++++++++ src/generator.rs | 11 +++- src/hash.rs | 47 +++++++++++++ src/main.rs | 62 ++++++++++++++--- src/proof.rs | 14 +++- src/prover.rs | 65 +++++++++--------- src/recursive_verifier.rs | 4 +- src/util.rs | 46 ++++++++++++- 19 files changed, 440 insertions(+), 86 deletions(-) create mode 100644 src/gates/noop.rs diff --git a/Cargo.toml b/Cargo.toml index c4b40ab9..82e21110 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,13 @@ authors = ["Daniel Lubarov "] edition = "2018" [dependencies] -unroll = "0.1.5" -rayon = "1.5.0" +env_logger = "0.8.3" +log = "0.4.14" num = "0.3" +rayon = "1.5.0" +unroll = "0.1.5" [profile.release] opt-level = 3 -lto = "fat" -codegen-units = 1 +#lto = "fat" +#codegen-units = 1 diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 5f24a54b..0bb88e70 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -1,14 +1,21 @@ use std::collections::HashSet; +use std::time::Instant; -use crate::circuit_data::{CircuitConfig, CircuitData, ProverCircuitData, VerifierCircuitData}; +use log::info; + +use crate::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, VerifierCircuitData, VerifierOnlyCircuitData}; +use crate::field::fft::lde_multiple; use crate::field::field::Field; use crate::gates::constant::ConstantGate2; use crate::gates::gate::{GateInstance, GateRef}; use crate::generator::{CopyGenerator, WitnessGenerator}; +use crate::hash::merkle_root_bit_rev_order; use crate::target::Target; use crate::wire::Wire; +use crate::gates::noop::NoopGate; +use crate::util::transpose; -pub struct CircuitBuilder2 { +pub struct CircuitBuilder { config: CircuitConfig, /// The types of gates used in this circuit. @@ -19,9 +26,9 @@ pub struct CircuitBuilder2 { generators: Vec>>, } -impl CircuitBuilder2 { +impl CircuitBuilder { pub fn new(config: CircuitConfig) -> Self { - CircuitBuilder2 { + CircuitBuilder { config, gates: HashSet::new(), gate_instances: Vec::new(), @@ -29,6 +36,10 @@ impl CircuitBuilder2 { } } + pub fn add_gate_no_constants(&mut self, gate_type: GateRef) -> usize { + self.add_gate(gate_type, Vec::new()) + } + /// Adds a gate to the circuit, and returns its index. pub fn add_gate(&mut self, gate_type: GateRef, constants: Vec) -> usize { // If we haven't seen a gate of this type before, check that it's compatible with our @@ -96,18 +107,93 @@ impl CircuitBuilder2 { Target::Wire(Wire { gate, input: ConstantGate2::WIRE_OUTPUT }) } + fn blind_and_pad(&mut self) { + // TODO: Blind. + + while !self.gate_instances.len().is_power_of_two() { + self.add_gate_no_constants(NoopGate::get()); + } + } + + fn get_generators(&self) -> Vec>> { + self.gate_instances.iter() + .enumerate() + .flat_map(|(gate_index, gate_inst)| gate_inst.gate_type.0.generators( + self.config, + gate_index, + gate_inst.constants.clone(), + vec![])) // TODO: Not supporting next_const for now. + .collect() + } + + fn constant_vecs(&self) -> Vec> { + let num_constants = self.gate_instances.iter() + .map(|gate_inst| gate_inst.constants.len()) + .max() + .unwrap(); + let constants_per_gate = self.gate_instances.iter() + .map(|gate_inst| { + let mut padded_constants = gate_inst.constants.clone(); + for _ in padded_constants.len()..num_constants { + padded_constants.push(F::ZERO); + } + padded_constants + }) + .collect::>(); + transpose(&constants_per_gate) + } + + fn sigma_vecs(&self) -> Vec> { + vec![vec![F::ZERO]] // TODO + } + /// Builds a "full circuit", with both prover and verifier data. - pub fn build(&self) -> CircuitData { - todo!() + pub fn build(mut self) -> CircuitData { + let start = Instant::now(); + info!("degree before blinding & padding: {}", self.gate_instances.len()); + self.blind_and_pad(); + let degree = self.gate_instances.len(); + info!("degree after blinding & padding: {}", degree); + + let constant_vecs = self.constant_vecs(); + let constant_ldes = lde_multiple(constant_vecs, self.config.rate_bits); + let constants_root = merkle_root_bit_rev_order(constant_ldes); + + let sigma_vecs = self.sigma_vecs(); + let sigma_ldes = lde_multiple(sigma_vecs, self.config.rate_bits); + let sigmas_root = merkle_root_bit_rev_order(sigma_ldes); + + let generators = self.get_generators(); + let prover_only = ProverOnlyCircuitData { generators }; + let verifier_only = VerifierOnlyCircuitData {}; + + let common = CommonCircuitData { + config: self.config, + degree, + gates: self.gates.iter().cloned().collect(), + constants_root, + sigmas_root, + }; + + info!("Building circuit took {}s", start.elapsed().as_secs_f32()); + CircuitData { + prover_only, + verifier_only, + common, + } } /// Builds a "prover circuit", with data needed to generate proofs but not verify them. - pub fn build_prover(&self) -> ProverCircuitData { - todo!() + pub fn build_prover(mut self) -> ProverCircuitData { + // TODO: Can skip parts of this. + let CircuitData { prover_only, verifier_only, common } = self.build(); + ProverCircuitData { prover_only, common } } /// Builds a "verifier circuit", with data needed to verify proofs but not generate them. - pub fn build_verifier(&self) -> VerifierCircuitData { - todo!() + pub fn build_verifier(mut self) -> VerifierCircuitData { + // TODO: Can skip parts of this. + let CircuitData { prover_only, verifier_only, common } = self.build(); + VerifierCircuitData { verifier_only, common } } } diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 434a685f..18e78b95 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -1,17 +1,17 @@ -use crate::field::fft::FftPrecomputation; use crate::field::field::Field; use crate::generator::WitnessGenerator; use crate::proof::{Hash, Proof2}; use crate::prover::prove; use crate::verifier::verify; use crate::witness::PartialWitness; -use crate::gates::gate::{GateRef, Gate}; +use crate::gates::gate::{GateRef}; #[derive(Copy, Clone)] pub struct CircuitConfig { pub num_wires: usize, pub num_routed_wires: usize, pub security_bits: usize, + pub rate_bits: usize, } impl CircuitConfig { @@ -22,9 +22,9 @@ impl CircuitConfig { /// Circuit data required by the prover or the verifier. pub struct CircuitData { - prover_only: ProverOnlyCircuitData, - verifier_only: VerifierOnlyCircuitData, - common: CommonCircuitData, + pub(crate) prover_only: ProverOnlyCircuitData, + pub(crate) verifier_only: VerifierOnlyCircuitData, + pub(crate) common: CommonCircuitData, } impl CircuitData { @@ -39,8 +39,8 @@ impl CircuitData { /// Circuit data required by the prover. pub struct ProverCircuitData { - prover_only: ProverOnlyCircuitData, - common: CommonCircuitData, + pub(crate) prover_only: ProverOnlyCircuitData, + pub(crate) common: CommonCircuitData, } impl ProverCircuitData { @@ -51,8 +51,8 @@ impl ProverCircuitData { /// Circuit data required by the prover. pub struct VerifierCircuitData { - verifier_only: VerifierOnlyCircuitData, - common: CommonCircuitData, + pub(crate) verifier_only: VerifierOnlyCircuitData, + pub(crate) common: CommonCircuitData, } impl VerifierCircuitData { @@ -71,18 +71,18 @@ pub(crate) struct VerifierOnlyCircuitData {} /// Circuit data required by both the prover and the verifier. pub(crate) struct CommonCircuitData { - pub config: CircuitConfig, + pub(crate) config: CircuitConfig, - pub degree: usize, + pub(crate) degree: usize, /// The types of gates used in this circuit. - pub gates: Vec>, + pub(crate) gates: Vec>, /// A commitment to each constant polynomial. - pub constants_root: Hash, + pub(crate) constants_root: Hash, /// A commitment to each permutation polynomial. - pub sigmas_root: Hash, + pub(crate) sigmas_root: Hash, } impl CommonCircuitData { diff --git a/src/constraint_polynomial.rs b/src/constraint_polynomial.rs index 847b5f22..9a5e9d1c 100644 --- a/src/constraint_polynomial.rs +++ b/src/constraint_polynomial.rs @@ -27,7 +27,7 @@ pub(crate) struct EvaluationVars<'a, F: Field> { /// This type implements `Hash` and `Eq` based on references rather /// than content. This is useful when we want to use constraint polynomials as `HashMap` keys, but /// we want address-based hashing for performance reasons. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ConstraintPolynomial(pub(crate) Rc>); impl ConstraintPolynomial { @@ -115,7 +115,7 @@ impl ConstraintPolynomial { } pub fn cube(&self) -> Self { - self.exp(3) + self * self * self } pub fn degree(&self) -> BigUint { @@ -239,6 +239,12 @@ impl ConstraintPolynomial { } } +impl Debug for ConstraintPolynomial { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + impl Display for ConstraintPolynomial { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index 3fc143f0..bb2949bf 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -3,6 +3,7 @@ use std::fmt; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::field::field::Field; +use num::Integer; /// P = 2**64 - EPSILON /// = 2**64 - 9 * 2**28 + 1 @@ -58,7 +59,55 @@ impl Field for CrandallField { } fn try_inverse(&self) -> Option { - unimplemented!() + if *self == Self::ZERO { + return None; + } + + // Based on Algorithm 16 of "Efficient Software-Implementation of Finite Fields with + // Applications to Cryptography". + + let mut u = self.0; + let mut v = P; + let mut b = 1; + let mut c = 0; + + while u != 1 && v != 1 { + while u.is_even() { + u >>= 1; + if b.is_odd() { + b += P; + } + b >>= 1; + } + + while v.is_even() { + v >>= 1; + if c.is_odd() { + c += P; + } + c >>= 1; + } + + if u < v { + v -= u; + if c < b { + c += P; + } + c -= b; + } else { + u -= v; + if b < c { + b += P; + } + b -= c; + } + } + + Some(Self(if u == 1 { + b + } else { + c + })) } fn primitive_root_of_unity(n_power: usize) -> Self { diff --git a/src/field/fft.rs b/src/field/fft.rs index a91df5b2..281ee9d2 100644 --- a/src/field/fft.rs +++ b/src/field/fft.rs @@ -141,6 +141,20 @@ pub fn coset_ifft(points: Vec, shift: F) -> Vec { .collect() } +pub fn lde_multiple(points: Vec>, rate_bits: usize) -> Vec> { + points.into_iter().map(|p| lde(p, rate_bits)).collect() +} + +pub fn lde(points: Vec, rate_bits: usize) -> Vec { + let original_size = points.len(); + let lde_size = original_size << rate_bits; + let mut coeffs = ifft(points); + for _ in 0..(lde_size - original_size) { + coeffs.push(F::ZERO); + } + fft(coeffs) +} + // #[cfg(test)] // mod tests { // use crate::{Bls12377Scalar, fft_precompute, fft_with_precomputation, CrandallField, ifft_with_precomputation_power_of_2}; diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index 481d0c61..b017927f 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -43,6 +43,7 @@ pub fn split_le_generator_local_wires( Box::new(SplitGenerator { integer, bits }) } +#[derive(Debug)] struct SplitGenerator { integer: Target, bits: Vec, diff --git a/src/gates/deterministic_gate.rs b/src/gates/deterministic_gate.rs index 34dee585..da231450 100644 --- a/src/gates/deterministic_gate.rs +++ b/src/gates/deterministic_gate.rs @@ -98,6 +98,7 @@ impl> Gate for DeterministicGateAdapter { gate_index: usize, location: GateOutputLocation, diff --git a/src/gates/fri_consistency_gate.rs b/src/gates/fri_consistency_gate.rs index b1fc842d..7decc45d 100644 --- a/src/gates/fri_consistency_gate.rs +++ b/src/gates/fri_consistency_gate.rs @@ -34,7 +34,7 @@ use crate::gadgets::conditionals::conditional_multiply_poly; /// - Evaluates the purported interpolant at `y`, and checks that the result matches the opening /// of `f^(i + 1)(y)`. #[derive(Debug, Copy, Clone)] -struct FriConsistencyGate { +pub(crate) struct FriConsistencyGate { /// The arity of this reduction step. arity_bits: usize, @@ -163,6 +163,7 @@ impl FriConsistencyGate { for i in 0..shared_path_bits { let bit = ConstraintPolynomial::local_wire(self.wire_path_bit_i(i)); g_exp_path_common = conditional_multiply_poly(&g_exp_path_common, &squares[i], &bit); + g_exp_path_common = output_graph.add(g_exp_path_common); } // Then, we factor in the "extra" powers of g specific to each child. @@ -231,7 +232,7 @@ impl DeterministicGate for FriConsistencyGate { format!("{:?}", self) } - fn outputs(&self, config: CircuitConfig) -> OutputGraph { + fn outputs(&self, _config: CircuitConfig) -> OutputGraph { let mut output_graph = ExpandableOutputGraph::new(self.start_unnamed_wires()); self.add_s_j_outputs(&mut output_graph); self.add_y_output(&mut output_graph); @@ -263,7 +264,7 @@ impl DeterministicGate for FriConsistencyGate { _config: CircuitConfig, gate_index: usize, local_constants: Vec, - next_constants: Vec, + _next_constants: Vec, ) -> Vec>> { let interpolant_generator = Box::new( InterpolantGenerator { @@ -283,6 +284,7 @@ impl DeterministicGate for FriConsistencyGate { } } +#[derive(Debug)] struct InterpolantGenerator { gate: FriConsistencyGate, gate_index: usize, diff --git a/src/gates/gate.rs b/src/gates/gate.rs index f5495a4a..9517d1fc 100644 --- a/src/gates/gate.rs +++ b/src/gates/gate.rs @@ -2,7 +2,7 @@ use std::hash::{Hash, Hasher}; use std::rc::Rc; use crate::circuit_data::CircuitConfig; -use crate::constraint_polynomial::ConstraintPolynomial; +use crate::constraint_polynomial::{ConstraintPolynomial, EvaluationVars}; use crate::field::field::Field; use crate::generator::WitnessGenerator; use num::ToPrimitive; @@ -15,10 +15,18 @@ pub trait Gate: 'static { /// A set of expressions which must evaluate to zero. fn constraints(&self, config: CircuitConfig) -> Vec>; + // fn eval_constraints(&self, config: CircuitConfig, vars: EvaluationVars) -> Vec { + // self.constraints(config) + // .into_iter() + // .map(|c| c.evaluate(vars)) + // .collect() + // } + fn generators( &self, config: CircuitConfig, gate_index: usize, + // TODO: Switch to slices? local_constants: Vec, next_constants: Vec, ) -> Vec>>; diff --git a/src/gates/mod.rs b/src/gates/mod.rs index c730d8f1..48c6ce38 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod constant; pub(crate) mod deterministic_gate; +pub(crate) mod fri_consistency_gate; pub(crate) mod gate; pub(crate) mod gmimc; +pub(crate) mod noop; pub(crate) mod output_graph; -pub(crate) mod fri_consistency_gate; diff --git a/src/gates/noop.rs b/src/gates/noop.rs new file mode 100644 index 00000000..99b87b97 --- /dev/null +++ b/src/gates/noop.rs @@ -0,0 +1,35 @@ +use crate::circuit_data::CircuitConfig; +use crate::constraint_polynomial::ConstraintPolynomial; +use crate::field::field::Field; +use crate::gates::deterministic_gate::{DeterministicGate, DeterministicGateAdapter}; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::WitnessGenerator; + +/// A gate which takes a single constant parameter and outputs that value. +pub struct NoopGate; + +impl NoopGate { + pub fn get() -> GateRef { + GateRef::new(NoopGate) + } +} + +impl Gate for NoopGate { + fn id(&self) -> String { + "NoopGate".into() + } + + fn constraints(&self, _config: CircuitConfig) -> Vec> { + Vec::new() + } + + fn generators( + &self, + _config: CircuitConfig, + _gate_index: usize, + _local_constants: Vec, + _next_constants: Vec + ) -> Vec>> { + Vec::new() + } +} diff --git a/src/generator.rs b/src/generator.rs index f317cf9c..4aae83a4 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,4 +1,8 @@ use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::time::Instant; + +use log::trace; use crate::field::field::Field; use crate::target::Target; @@ -36,7 +40,9 @@ pub(crate) fn generate_partial_witness( let mut next_pending_generator_indices = HashSet::new(); for &generator_idx in &pending_generator_indices { + let start = Instant::now(); let (result, finished) = generators[generator_idx].run(&witness); + trace!("run {:?} took {}", generators[generator_idx], start.elapsed().as_secs_f32()); if finished { expired_generator_indices.insert(generator_idx); } @@ -60,7 +66,7 @@ pub(crate) fn generate_partial_witness( } /// A generator participates in the generation of the witness. -pub trait WitnessGenerator: 'static { +pub trait WitnessGenerator: 'static + Debug { /// Targets to be "watched" by this generator. Whenever a target in the watch list is populated, /// the generator will be queued to run. fn watch_list(&self) -> Vec; @@ -73,7 +79,7 @@ pub trait WitnessGenerator: 'static { } /// A generator which runs once after a list of dependencies is present in the witness. -pub trait SimpleGenerator: 'static { +pub trait SimpleGenerator: 'static + Debug { fn dependencies(&self) -> Vec; fn run_once(&self, witness: &PartialWitness) -> PartialWitness; @@ -94,6 +100,7 @@ impl> WitnessGenerator for SG { } /// A generator which copies one wire to another. +#[derive(Debug)] pub(crate) struct CopyGenerator { pub(crate) src: Target, pub(crate) dst: Target, diff --git a/src/hash.rs b/src/hash.rs index 67e43950..57b5c6bc 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -2,9 +2,13 @@ use std::convert::TryInto; +use num::traits::real::Real; +use rayon::prelude::*; + use crate::field::field::Field; use crate::gmimc::{gmimc_compress, gmimc_permute_array}; use crate::proof::Hash; +use crate::util::{reverse_index_bits, transpose}; const RATE: usize = 8; const CAPACITY: usize = 4; @@ -13,6 +17,24 @@ const WIDTH: usize = RATE + CAPACITY; const GMIMC_ROUNDS: usize = 101; const GMIMC_CONSTANTS: [u64; GMIMC_ROUNDS] = [11875528958976719239, 6107683892976199900, 7756999550758271958, 14819109722912164804, 9716579428412441110, 13627117528901194436, 16260683900833506663, 5942251937084147420, 3340009544523273897, 5103423085715007461, 17051583366444092101, 11122892258227244197, 16564300648907092407, 978667924592675864, 17676416205210517593, 1938246372790494499, 8857737698008340728, 1616088456497468086, 15961521580811621978, 17427220057097673602, 14693961562064090188, 694121596646283736, 554241305747273747, 5783347729647881086, 14933083198980931734, 2600898787591841337, 9178797321043036456, 18068112389665928586, 14493389459750307626, 1650694762687203587, 12538946551586403559, 10144328970401184255, 4215161528137084719, 17559540991336287827, 1632269449854444901, 986434918028205468, 14921385763379308253, 4345141219277982730, 2645897826751167170, 9815223670029373528, 7687983869685434132, 13956100321958014639, 519639453142393369, 15617837024229225911, 1557446238053329052, 8130006133842942201, 864716631341688017, 2860289738131495304, 16723700803638270299, 8363528906277648001, 13196016034228493087, 2514677332206134618, 15626342185220554936, 466271571343554681, 17490024028988898434, 6454235936129380878, 15187752952940298536, 18043495619660620405, 17118101079533798167, 13420382916440963101, 535472393366793763, 1071152303676936161, 6351382326603870931, 12029593435043638097, 9983185196487342247, 414304527840226604, 1578977347398530191, 13594880016528059526, 13219707576179925776, 6596253305527634647, 17708788597914990288, 7005038999589109658, 10171979740390484633, 1791376803510914239, 2405996319967739434, 12383033218117026776, 17648019043455213923, 6600216741450137683, 5359884112225925883, 1501497388400572107, 11860887439428904719, 64080876483307031, 11909038931518362287, 14166132102057826906, 14172584203466994499, 593515702472765471, 3423583343794830614, 10041710997716717966, 13434212189787960052, 9943803922749087030, 3216887087479209126, 17385898166602921353, 617799950397934255, 9245115057096506938, 13290383521064450731, 10193883853810413351, 14648839921475785656, 14635698366607946133, 9134302981480720532, 10045888297267997632, 10752096344939765738]; +/// Hash the vector if necessary to reduce its length to ~256 bits. If it already fits, this is a +/// no-op. +pub fn hash_or_noop(mut inputs: Vec) -> Hash { + if inputs.len() <= 4 { + Hash::from_partial(inputs) + } else { + hash_n_to_hash(inputs, false) + } +} + +/// A one-way compression function which takes two ~256 bit inputs and returns a ~256 bit output. +pub fn compress(x: Hash, y: Hash) -> Hash { + let mut inputs = Vec::with_capacity(8); + inputs.extend(&x.elements); + inputs.extend(&y.elements); + hash_n_to_hash(inputs, false) +} + /// If `pad` is enabled, the message is padded using the pad10*1 rule. In general this is required /// for the hash to be secure, but it can safely be disabled in certain cases, like if the input /// length is fixed. @@ -56,3 +78,28 @@ pub fn hash_n_to_hash(inputs: Vec, pad: bool) -> Hash { pub fn hash_n_to_1(inputs: Vec, pad: bool) -> F { hash_n_to_m(inputs, 1, pad)[0] } + +/// Like `merkle_root`, but first reorders each vector so that `new[i] = old[i.reverse_bits()]`. +pub(crate) fn merkle_root_bit_rev_order(vecs: Vec>) -> Hash { + let vecs_reordered = vecs.into_par_iter() + .map(reverse_index_bits) + .collect(); + merkle_root(vecs_reordered) +} + +/// Given `n` vectors, each of length `l`, constructs a Merkle tree with `l` leaves, where each leaf +/// is a hash obtained by hashing a "leaf set" consisting of `n` elements. If `n <= 4`, this hashing +/// is skipped, as there is no need to compress leaf data. +pub(crate) fn merkle_root(vecs: Vec>) -> Hash { + // TODO: Parallelize. + let mut vecs_t = transpose(&vecs); + let mut hashes = vecs_t.into_iter() + .map(|leaf_set| hash_or_noop(leaf_set)) + .collect::>(); + while hashes.len() > 1 { + hashes = hashes.chunks(2) + .map(|pair| compress(pair[0], pair[1])) + .collect(); + } + hashes[0] +} diff --git a/src/main.rs b/src/main.rs index 4f61c262..c3e600f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,14 @@ use field::fft::fft_precompute; use crate::field::field::Field; use crate::util::log2_ceil; +use crate::circuit_builder::CircuitBuilder; +use crate::circuit_data::CircuitConfig; +use crate::witness::PartialWitness; +use crate::gates::fri_consistency_gate::FriConsistencyGate; +use env_logger::Env; +use crate::gates::gmimc::GMiMCGate; +use std::sync::Arc; +use std::convert::TryInto; mod circuit_builder; mod circuit_data; @@ -36,11 +44,19 @@ mod hash; const PROVER_POLYS: usize = 113 + 3 + 4; fn main() { + // Set the default log filter. This can be overridden using the `RUST_LOG` environment variable, + // e.g. `RUST_LOG=debug`. + // We default to debug for now, since there aren't many logs anyway, but we should probably + // change this to info or warn later. + env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); + let overall_start = Instant::now(); + bench_prove::(); + // bench_fft(); println!(); - bench_gmimc::(); + // bench_gmimc::(); let overall_duration = overall_start.elapsed(); println!("Overall time: {:?}", overall_duration); @@ -48,15 +64,42 @@ fn main() { // field_search() } +fn bench_prove() { + let mut gmimc_constants = [F::ZERO; GMIMC_ROUNDS]; + for i in 0..GMIMC_ROUNDS { + gmimc_constants[i] = F::from_canonical_u64(GMIMC_CONSTANTS[i]); + } + let gmimc_gate = GMiMCGate::::with_constants( + Arc::new(gmimc_constants)); + + let config = CircuitConfig { + num_wires: 120, + num_routed_wires: 12, + security_bits: 128, + rate_bits: 3, + }; + + let mut builder = CircuitBuilder::::new(config); + + for _ in 0..5000 { + builder.add_gate_no_constants(gmimc_gate.clone()); + } + + for _ in 0..(40 * 5) { + builder.add_gate( + FriConsistencyGate::new(2, 3, 13), + vec![F::primitive_root_of_unity(13)]); + } + + let prover = builder.build_prover(); + let inputs = PartialWitness::new(); + prover.prove(inputs); +} + const GMIMC_ROUNDS: usize = 101; const GMIMC_CONSTANTS: [u64; GMIMC_ROUNDS] = [11875528958976719239, 6107683892976199900, 7756999550758271958, 14819109722912164804, 9716579428412441110, 13627117528901194436, 16260683900833506663, 5942251937084147420, 3340009544523273897, 5103423085715007461, 17051583366444092101, 11122892258227244197, 16564300648907092407, 978667924592675864, 17676416205210517593, 1938246372790494499, 8857737698008340728, 1616088456497468086, 15961521580811621978, 17427220057097673602, 14693961562064090188, 694121596646283736, 554241305747273747, 5783347729647881086, 14933083198980931734, 2600898787591841337, 9178797321043036456, 18068112389665928586, 14493389459750307626, 1650694762687203587, 12538946551586403559, 10144328970401184255, 4215161528137084719, 17559540991336287827, 1632269449854444901, 986434918028205468, 14921385763379308253, 4345141219277982730, 2645897826751167170, 9815223670029373528, 7687983869685434132, 13956100321958014639, 519639453142393369, 15617837024229225911, 1557446238053329052, 8130006133842942201, 864716631341688017, 2860289738131495304, 16723700803638270299, 8363528906277648001, 13196016034228493087, 2514677332206134618, 15626342185220554936, 466271571343554681, 17490024028988898434, 6454235936129380878, 15187752952940298536, 18043495619660620405, 17118101079533798167, 13420382916440963101, 535472393366793763, 1071152303676936161, 6351382326603870931, 12029593435043638097, 9983185196487342247, 414304527840226604, 1578977347398530191, 13594880016528059526, 13219707576179925776, 6596253305527634647, 17708788597914990288, 7005038999589109658, 10171979740390484633, 1791376803510914239, 2405996319967739434, 12383033218117026776, 17648019043455213923, 6600216741450137683, 5359884112225925883, 1501497388400572107, 11860887439428904719, 64080876483307031, 11909038931518362287, 14166132102057826906, 14172584203466994499, 593515702472765471, 3423583343794830614, 10041710997716717966, 13434212189787960052, 9943803922749087030, 3216887087479209126, 17385898166602921353, 617799950397934255, 9245115057096506938, 13290383521064450731, 10193883853810413351, 14648839921475785656, 14635698366607946133, 9134302981480720532, 10045888297267997632, 10752096344939765738]; fn bench_gmimc() { - let mut constants: [F; GMIMC_ROUNDS] = [F::ZERO; GMIMC_ROUNDS]; - for i in 0..GMIMC_ROUNDS { - constants[i] = F::from_canonical_u64(GMIMC_CONSTANTS[i]); - } - const THREADS: usize = 12; const LDE_BITS: i32 = 3; const W: usize = 13; @@ -86,10 +129,9 @@ fn bench_gmimc() { } fn bench_fft() { - let degree = 1 << log2_ceil(77916); - let lde_bits = 4; + let degree = 1 << 13; + let lde_bits = 3; let lde_size = degree << lde_bits; - let precomputation = fft_precompute(lde_size); println!("{} << {} = {}", degree, lde_bits, lde_size); let start = Instant::now(); @@ -100,7 +142,7 @@ fn bench_fft() { } let start = Instant::now(); - let result = fft::fft_with_precomputation_power_of_2(coeffs, &precomputation); + let result = fft::fft(coeffs); let duration = start.elapsed(); println!("FFT took {:?}", duration); println!("FFT result: {:?}", result[0]); diff --git a/src/proof.rs b/src/proof.rs index 59283651..6304c814 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,8 +1,20 @@ use crate::field::field::Field; use crate::target::Target; +/// Represents a ~256 bit hash output. +#[derive(Copy, Clone)] pub struct Hash { - pub(crate) elements: Vec, + pub(crate) elements: [F; 4], +} + +impl Hash { + pub(crate) fn from_partial(mut elements: Vec) -> Self { + debug_assert!(elements.len() <= 4); + while elements.len() < 4 { + elements.push(F::ZERO); + } + Self { elements: [elements[0], elements[1], elements[2], elements[3]] } + } } pub struct HashTarget { diff --git a/src/prover.rs b/src/prover.rs index 081908a3..76f537ef 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -1,12 +1,17 @@ +use std::time::Instant; + +use log::info; + use crate::circuit_data::{CommonCircuitData, ProverOnlyCircuitData}; -use crate::field::fft::{fft, ifft}; +use crate::field::fft::{fft, ifft, lde}; use crate::field::field::Field; use crate::generator::generate_partial_witness; -use crate::proof::{Proof2, Hash}; -use crate::util::{log2_ceil, transpose}; +use crate::hash::{compress, hash_n_to_hash, hash_n_to_m, hash_or_noop, merkle_root_bit_rev_order}; +use crate::proof::{Hash, Proof2}; +use crate::util::{log2_ceil, reverse_index_bits}; use crate::wire::Wire; use crate::witness::PartialWitness; -use crate::hash::{hash_n_to_hash, hash_n_to_m}; +use rayon::prelude::*; pub(crate) fn prove( prover_data: &ProverOnlyCircuitData, @@ -14,22 +19,33 @@ pub(crate) fn prove( inputs: PartialWitness, ) -> Proof2 { let mut witness = inputs; + let start_witness = Instant::now(); + info!("Running {} generators", prover_data.generators.len()); generate_partial_witness(&mut witness, &prover_data.generators); + info!("Witness generation took {}s", start_witness.elapsed().as_secs_f32()); let config = common_data.config; - let constraint_degree = 1 << log2_ceil(common_data.constraint_degree(config)); - let lde_size = constraint_degree * common_data.degree; - let num_wires = config.num_wires; + + let start_wire_ldes = Instant::now(); + // TODO: Simplify using lde_multiple. + // TODO: Parallelize. let wire_ldes = (0..num_wires) - .map(|i| compute_wire_lde(i, &witness, common_data.degree, lde_size)) + .map(|i| compute_wire_lde(i, &witness, common_data.degree, config.rate_bits)) .collect::>(); - let wires_root = merkle_root(wire_ldes); + info!("Computing wire LDEs took {}s", start_wire_ldes.elapsed().as_secs_f32()); - let z_ldes = todo!(); - let plonk_z_root = merkle_root(z_ldes); + let start_wires_root = Instant::now(); + let wires_root = merkle_root_bit_rev_order(wire_ldes); + info!("Merklizing wire LDEs took {}s", start_wires_root.elapsed().as_secs_f32()); - let plonk_t_root = todo!(); + let plonk_z_vecs = todo!(); + let plonk_z_ldes = todo!(); + let plonk_z_root = merkle_root_bit_rev_order(plonk_z_ldes); + + let plonk_t_vecs = todo!(); + let plonk_t_ldes = todo!(); + let plonk_t_root = merkle_root_bit_rev_order(plonk_t_ldes); let openings = todo!(); @@ -41,37 +57,18 @@ pub(crate) fn prove( } } -/// Given `n` vectors, each of length `l`, constructs a Merkle tree with `l` leaves, where each leaf -/// is a hash obtained by hashing a "leaf set" consisting of `n` elements. If `n <= 4`, this hashing -/// is skipped, as there is no need to compress leaf data. -fn merkle_root(vecs: Vec>) -> Hash { - let n = vecs.len(); - let mut vecs_t = transpose(&vecs); - let l = vecs_t.len(); - if n > 4 { - vecs_t = vecs_t.into_iter() - .map(|leaf_set| hash_n_to_hash(leaf_set, false).elements) - .collect(); - } - todo!() -} - fn compute_wire_lde( input: usize, witness: &PartialWitness, degree: usize, - lde_size: usize, + rate_bits: usize, ) -> Vec { - let wire = (0..degree) + let wire_values = (0..degree) // Some gates do not use all wires, and we do not require that generators populate unused // wires, so some wire values will not be set. We can set these to any value; here we // arbitrary pick zero. Ideally we would verify that no constraints operate on these unset // wires, but that isn't trivial. .map(|gate| witness.try_get_wire(Wire { gate, input }).unwrap_or(F::ZERO)) .collect(); - let mut coeffs = ifft(wire); - for _ in 0..(lde_size - degree) { - coeffs.push(F::ZERO); - } - fft(coeffs) + lde(wire_values, rate_bits) } diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index beaa7b15..6d27514e 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -1,5 +1,5 @@ -use crate::circuit_builder::CircuitBuilder2; +use crate::circuit_builder::CircuitBuilder; use crate::field::field::Field; -pub fn add_recursive_verifier(builder: &mut CircuitBuilder2) { +pub fn add_recursive_verifier(builder: &mut CircuitBuilder) { } diff --git a/src/util.rs b/src/util.rs index fa4f39ec..046bf55f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,3 @@ -// TODO: Can this impl usize? pub(crate) fn ceil_div_usize(a: usize, b: usize) -> usize { (a + b - 1) / b } @@ -29,3 +28,48 @@ pub(crate) fn transpose(matrix: &[Vec]) -> Vec> { } transposed } + +/// Permutes `arr` such that each index is mapped to its reverse in binary. +pub(crate) fn reverse_index_bits(arr: Vec) -> Vec { + let n = arr.len(); + let n_power = log2_strict(n); + + let mut result = Vec::with_capacity(n); + for i in 0..n { + result.push(arr[reverse_bits(i, n_power)]); + } + result +} + +fn reverse_bits(n: usize, num_bits: usize) -> usize { + let mut result = 0; + for i in 0..num_bits { + let i_rev = num_bits - i - 1; + result |= (n >> i & 1) << i_rev; + } + result +} + +#[cfg(test)] +mod tests { + use crate::util::{reverse_bits, reverse_index_bits}; + + #[test] + fn test_reverse_bits() { + assert_eq!(reverse_bits(0b0000000000, 10), 0b0000000000); + assert_eq!(reverse_bits(0b0000000001, 10), 0b1000000000); + assert_eq!(reverse_bits(0b1000000000, 10), 0b0000000001); + assert_eq!(reverse_bits(0b00000, 5), 0b00000); + assert_eq!(reverse_bits(0b01011, 5), 0b11010); + } + + #[test] + fn test_reverse_index_bits() { + assert_eq!( + reverse_index_bits(vec![10, 20, 30, 40]), + vec![10, 30, 20, 40]); + assert_eq!( + reverse_index_bits(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + vec![0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]); + } +}