More prover work

This commit is contained in:
Daniel Lubarov 2021-03-25 15:20:14 -07:00
parent 8c95dd11d7
commit ba96ab4e99
19 changed files with 440 additions and 86 deletions

View File

@ -5,11 +5,13 @@ authors = ["Daniel Lubarov <daniel@lubarov.com>"]
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

View File

@ -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<F: Field> {
pub struct CircuitBuilder<F: Field> {
config: CircuitConfig,
/// The types of gates used in this circuit.
@ -19,9 +26,9 @@ pub struct CircuitBuilder2<F: Field> {
generators: Vec<Box<dyn WitnessGenerator<F>>>,
}
impl<F: Field> CircuitBuilder2<F> {
impl<F: Field> CircuitBuilder<F> {
pub fn new(config: CircuitConfig) -> Self {
CircuitBuilder2 {
CircuitBuilder {
config,
gates: HashSet::new(),
gate_instances: Vec::new(),
@ -29,6 +36,10 @@ impl<F: Field> CircuitBuilder2<F> {
}
}
pub fn add_gate_no_constants(&mut self, gate_type: GateRef<F>) -> 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<F>, constants: Vec<F>) -> usize {
// If we haven't seen a gate of this type before, check that it's compatible with our
@ -96,18 +107,93 @@ impl<F: Field> CircuitBuilder2<F> {
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<Box<dyn WitnessGenerator<F>>> {
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<Vec<F>> {
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::<Vec<_>>();
transpose(&constants_per_gate)
}
fn sigma_vecs(&self) -> Vec<Vec<F>> {
vec![vec![F::ZERO]] // TODO
}
/// Builds a "full circuit", with both prover and verifier data.
pub fn build(&self) -> CircuitData<F> {
todo!()
pub fn build(mut self) -> CircuitData<F> {
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<F> {
todo!()
pub fn build_prover(mut self) -> ProverCircuitData<F> {
// 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<F> {
todo!()
pub fn build_verifier(mut self) -> VerifierCircuitData<F> {
// TODO: Can skip parts of this.
let CircuitData { prover_only, verifier_only, common } = self.build();
VerifierCircuitData { verifier_only, common }
}
}

View File

@ -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<F: Field> {
prover_only: ProverOnlyCircuitData<F>,
verifier_only: VerifierOnlyCircuitData,
common: CommonCircuitData<F>,
pub(crate) prover_only: ProverOnlyCircuitData<F>,
pub(crate) verifier_only: VerifierOnlyCircuitData,
pub(crate) common: CommonCircuitData<F>,
}
impl<F: Field> CircuitData<F> {
@ -39,8 +39,8 @@ impl<F: Field> CircuitData<F> {
/// Circuit data required by the prover.
pub struct ProverCircuitData<F: Field> {
prover_only: ProverOnlyCircuitData<F>,
common: CommonCircuitData<F>,
pub(crate) prover_only: ProverOnlyCircuitData<F>,
pub(crate) common: CommonCircuitData<F>,
}
impl<F: Field> ProverCircuitData<F> {
@ -51,8 +51,8 @@ impl<F: Field> ProverCircuitData<F> {
/// Circuit data required by the prover.
pub struct VerifierCircuitData<F: Field> {
verifier_only: VerifierOnlyCircuitData,
common: CommonCircuitData<F>,
pub(crate) verifier_only: VerifierOnlyCircuitData,
pub(crate) common: CommonCircuitData<F>,
}
impl<F: Field> VerifierCircuitData<F> {
@ -71,18 +71,18 @@ pub(crate) struct VerifierOnlyCircuitData {}
/// Circuit data required by both the prover and the verifier.
pub(crate) struct CommonCircuitData<F: Field> {
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<GateRef<F>>,
pub(crate) gates: Vec<GateRef<F>>,
/// A commitment to each constant polynomial.
pub constants_root: Hash<F>,
pub(crate) constants_root: Hash<F>,
/// A commitment to each permutation polynomial.
pub sigmas_root: Hash<F>,
pub(crate) sigmas_root: Hash<F>,
}
impl<F: Field> CommonCircuitData<F> {

View File

@ -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<F: Field>(pub(crate) Rc<ConstraintPolynomialInner<F>>);
impl<F: Field> ConstraintPolynomial<F> {
@ -115,7 +115,7 @@ impl<F: Field> ConstraintPolynomial<F> {
}
pub fn cube(&self) -> Self {
self.exp(3)
self * self * self
}
pub fn degree(&self) -> BigUint {
@ -239,6 +239,12 @@ impl<F: Field> ConstraintPolynomial<F> {
}
}
impl<F: Field> Debug for ConstraintPolynomial<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<F: Field> Display for ConstraintPolynomial<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)

View File

@ -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<Self> {
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 {

View File

@ -141,6 +141,20 @@ pub fn coset_ifft<F: Field>(points: Vec<F>, shift: F) -> Vec<F> {
.collect()
}
pub fn lde_multiple<F: Field>(points: Vec<Vec<F>>, rate_bits: usize) -> Vec<Vec<F>> {
points.into_iter().map(|p| lde(p, rate_bits)).collect()
}
pub fn lde<F: Field>(points: Vec<F>, rate_bits: usize) -> Vec<F> {
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};

View File

@ -43,6 +43,7 @@ pub fn split_le_generator_local_wires<F: Field>(
Box::new(SplitGenerator { integer, bits })
}
#[derive(Debug)]
struct SplitGenerator {
integer: Target,
bits: Vec<Target>,

View File

@ -98,6 +98,7 @@ impl<F: Field, DG: DeterministicGate<F>> Gate<F> for DeterministicGateAdapter<F,
}
}
#[derive(Debug)]
struct OutputGenerator<F: Field> {
gate_index: usize,
location: GateOutputLocation,

View File

@ -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<F: Field> DeterministicGate<F> for FriConsistencyGate {
format!("{:?}", self)
}
fn outputs(&self, config: CircuitConfig) -> OutputGraph<F> {
fn outputs(&self, _config: CircuitConfig) -> OutputGraph<F> {
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<F: Field> DeterministicGate<F> for FriConsistencyGate {
_config: CircuitConfig,
gate_index: usize,
local_constants: Vec<F>,
next_constants: Vec<F>,
_next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator<F>>> {
let interpolant_generator = Box::new(
InterpolantGenerator {
@ -283,6 +284,7 @@ impl<F: Field> DeterministicGate<F> for FriConsistencyGate {
}
}
#[derive(Debug)]
struct InterpolantGenerator<F: Field> {
gate: FriConsistencyGate,
gate_index: usize,

View File

@ -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<F: Field>: 'static {
/// A set of expressions which must evaluate to zero.
fn constraints(&self, config: CircuitConfig) -> Vec<ConstraintPolynomial<F>>;
// fn eval_constraints(&self, config: CircuitConfig, vars: EvaluationVars<F>) -> Vec<F> {
// 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<F>,
next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator<F>>>;

View File

@ -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;

35
src/gates/noop.rs Normal file
View File

@ -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<F: Field>() -> GateRef<F> {
GateRef::new(NoopGate)
}
}
impl<F: Field> Gate<F> for NoopGate {
fn id(&self) -> String {
"NoopGate".into()
}
fn constraints(&self, _config: CircuitConfig) -> Vec<ConstraintPolynomial<F>> {
Vec::new()
}
fn generators(
&self,
_config: CircuitConfig,
_gate_index: usize,
_local_constants: Vec<F>,
_next_constants: Vec<F>
) -> Vec<Box<dyn WitnessGenerator<F>>> {
Vec::new()
}
}

View File

@ -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<F: Field>(
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<F: Field>(
}
/// A generator participates in the generation of the witness.
pub trait WitnessGenerator<F: Field>: 'static {
pub trait WitnessGenerator<F: Field>: '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<Target>;
@ -73,7 +79,7 @@ pub trait WitnessGenerator<F: Field>: 'static {
}
/// A generator which runs once after a list of dependencies is present in the witness.
pub trait SimpleGenerator<F: Field>: 'static {
pub trait SimpleGenerator<F: Field>: 'static + Debug {
fn dependencies(&self) -> Vec<Target>;
fn run_once(&self, witness: &PartialWitness<F>) -> PartialWitness<F>;
@ -94,6 +100,7 @@ impl<F: Field, SG: SimpleGenerator<F>> WitnessGenerator<F> for SG {
}
/// A generator which copies one wire to another.
#[derive(Debug)]
pub(crate) struct CopyGenerator {
pub(crate) src: Target,
pub(crate) dst: Target,

View File

@ -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<F: Field>(mut inputs: Vec<F>) -> Hash<F> {
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<F: Field>(x: Hash<F>, y: Hash<F>) -> Hash<F> {
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<F: Field>(inputs: Vec<F>, pad: bool) -> Hash<F> {
pub fn hash_n_to_1<F: Field>(inputs: Vec<F>, 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<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F> {
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<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F> {
// TODO: Parallelize.
let mut vecs_t = transpose(&vecs);
let mut hashes = vecs_t.into_iter()
.map(|leaf_set| hash_or_noop(leaf_set))
.collect::<Vec<_>>();
while hashes.len() > 1 {
hashes = hashes.chunks(2)
.map(|pair| compress(pair[0], pair[1]))
.collect();
}
hashes[0]
}

View File

@ -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::<CrandallField>();
// bench_fft();
println!();
bench_gmimc::<CrandallField>();
// bench_gmimc::<CrandallField>();
let overall_duration = overall_start.elapsed();
println!("Overall time: {:?}", overall_duration);
@ -48,15 +64,42 @@ fn main() {
// field_search()
}
fn bench_prove<F: Field>() {
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::<F, 12, GMIMC_ROUNDS>::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::<F>::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<F: Field>() {
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<F: Field>() {
}
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]);

View File

@ -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<F: Field> {
pub(crate) elements: Vec<F>,
pub(crate) elements: [F; 4],
}
impl<F: Field> Hash<F> {
pub(crate) fn from_partial(mut elements: Vec<F>) -> 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 {

View File

@ -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<F: Field>(
prover_data: &ProverOnlyCircuitData<F>,
@ -14,22 +19,33 @@ pub(crate) fn prove<F: Field>(
inputs: PartialWitness<F>,
) -> Proof2<F> {
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::<Vec<_>>();
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<F: Field>(
}
}
/// 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<F: Field>(vecs: Vec<Vec<F>>) -> Hash<F> {
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<F: Field>(
input: usize,
witness: &PartialWitness<F>,
degree: usize,
lde_size: usize,
rate_bits: usize,
) -> Vec<F> {
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)
}

View File

@ -1,5 +1,5 @@
use crate::circuit_builder::CircuitBuilder2;
use crate::circuit_builder::CircuitBuilder;
use crate::field::field::Field;
pub fn add_recursive_verifier<F: Field>(builder: &mut CircuitBuilder2<F>) {
pub fn add_recursive_verifier<F: Field>(builder: &mut CircuitBuilder<F>) {
}

View File

@ -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<T: Clone>(matrix: &[Vec<T>]) -> Vec<Vec<T>> {
}
transposed
}
/// Permutes `arr` such that each index is mapped to its reverse in binary.
pub(crate) fn reverse_index_bits<T: Copy>(arr: Vec<T>) -> Vec<T> {
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]);
}
}