diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 1cccc68c..5d286a46 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -14,7 +14,7 @@ use crate::field::cosets::get_unique_coset_shifts; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::gates::constant::ConstantGate; -use crate::gates::gate::{GateInstance, GateRef, PrefixedGate}; +use crate::gates::gate::{Gate, GateInstance, GateRef, PrefixedGate}; use crate::gates::gate_tree::Tree; use crate::gates::noop::NoopGate; use crate::gates::public_input::PublicInputGate; @@ -124,41 +124,35 @@ impl, const D: usize> CircuitBuilder { .collect() } - 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 { + pub fn add_gate>(&mut self, gate_type: G, constants: Vec) -> usize { + self.check_gate_compatibility(&gate_type); assert_eq!( - gate_type.0.num_constants(), + gate_type.num_constants(), constants.len(), "Number of constants doesn't match." ); - // If we haven't seen a gate of this type before, check that it's compatible with our - // circuit configuration, then register it. - if !self.gates.contains(&gate_type) { - self.check_gate_compatibility(&gate_type); - self.gates.insert(gate_type.clone()); - } let index = self.gate_instances.len(); + self.add_generators(gate_type.generators(index, &constants)); - self.add_generators(gate_type.0.generators(index, &constants)); + // Register this gate type if we haven't seen it before. + let gate_ref = GateRef::new(gate_type); + self.gates.insert(gate_ref.clone()); self.gate_instances.push(GateInstance { - gate_type, + gate_ref, constants, }); index } - fn check_gate_compatibility(&self, gate: &GateRef) { + fn check_gate_compatibility>(&self, gate: &G) { assert!( - gate.0.num_wires() <= self.config.num_wires, + gate.num_wires() <= self.config.num_wires, "{:?} requires {} wires, but our GateConfig has only {}", - gate.0.id(), - gate.0.num_wires(), + gate.id(), + gate.num_wires(), self.config.num_wires ); } @@ -287,7 +281,7 @@ impl, const D: usize> CircuitBuilder { return target; } - let gate = self.add_gate(ConstantGate::get(), vec![c]); + let gate = self.add_gate(ConstantGate, vec![c]); let target = Target::Wire(Wire { gate, input: ConstantGate::WIRE_OUTPUT, @@ -377,7 +371,7 @@ impl, const D: usize> CircuitBuilder { } while !self.gate_instances.len().is_power_of_two() { - self.add_gate_no_constants(NoopGate::get()); + self.add_gate(NoopGate, vec![]); } } @@ -394,7 +388,7 @@ impl, const D: usize> CircuitBuilder { // For each "regular" blinding factor, we simply add a no-op gate, and insert a random value // for each wire. for _ in 0..regular_poly_openings { - let gate = self.add_gate_no_constants(NoopGate::get()); + let gate = self.add_gate(NoopGate, vec![]); for w in 0..num_wires { self.add_generator(RandomValueGenerator { target: Target::Wire(Wire { gate, input: w }), @@ -406,8 +400,8 @@ impl, const D: usize> CircuitBuilder { // enforce a copy constraint between them. // See https://mirprotocol.org/blog/Adding-zero-knowledge-to-Plonk-Halo for _ in 0..z_openings { - let gate_1 = self.add_gate_no_constants(NoopGate::get()); - let gate_2 = self.add_gate_no_constants(NoopGate::get()); + let gate_1 = self.add_gate(NoopGate, vec![]); + let gate_2 = self.add_gate(NoopGate, vec![]); for w in 0..num_routed_wires { self.add_generator(RandomValueGenerator { @@ -441,7 +435,7 @@ impl, const D: usize> CircuitBuilder { .map(|gate| { let prefix = &gates .iter() - .find(|g| g.gate.0.id() == gate.gate_type.0.id()) + .find(|g| g.gate.0.id() == gate.gate_ref.0.id()) .unwrap() .prefix; let mut prefixed_constants = Vec::with_capacity(num_constants); @@ -498,7 +492,7 @@ impl, const D: usize> CircuitBuilder { // Hash the public inputs, and route them to a `PublicInputGate` which will enforce that // those hash wires match the claimed public inputs. let public_inputs_hash = self.hash_n_to_hash(self.public_inputs.clone(), true); - let pi_gate = self.add_gate_no_constants(PublicInputGate::get()); + let pi_gate = self.add_gate(PublicInputGate, vec![]); for (&hash_part, wire) in public_inputs_hash .elements .iter() diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 912f05cc..787c446d 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -56,7 +56,7 @@ impl CircuitConfig { pub(crate) fn large_config() -> Self { Self { - num_wires: 134, + num_wires: 126, num_routed_wires: 34, security_bits: 128, rate_bits: 3, diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index 2327418f..7592bc1f 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -20,7 +20,7 @@ impl, const D: usize> CircuitBuilder { fn compute_evaluation( &mut self, x: Target, - old_x_index: Target, + old_x_index_bits: &[Target], arity_bits: usize, last_evals: &[ExtensionTarget], beta: ExtensionTarget, @@ -33,13 +33,9 @@ impl, const D: usize> CircuitBuilder { // The evaluation vector needs to be reordered first. let mut evals = last_evals.to_vec(); reverse_index_bits_in_place(&mut evals); - let mut old_x_index_bits = self.split_le(old_x_index, arity_bits); - old_x_index_bits.reverse(); // Want `g^(arity - rev_old_x_index)` as in the out-of-circuit version. // Compute it as `g^(arity-1-rev_old_x_index) * g`, where the first term is gotten using two's complement. - // TODO: Once the exponentiation gate lands, we won't need the bits and will be able to compute - // `g^(arity-rev_old_x_index)` directly. - let start = self.exp_from_complement_bits(gt, &old_x_index_bits); + let start = self.exp_from_complement_bits(gt, old_x_index_bits.iter().rev()); let coset_start = self.mul_many(&[start, gt, x]); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. @@ -151,7 +147,7 @@ impl, const D: usize> CircuitBuilder { fn fri_verify_initial_proof( &mut self, - x_index: Target, + x_index_bits: &[Target], proof: &FriInitialTreeProofTarget, initial_merkle_roots: &[HashTarget], ) { @@ -164,7 +160,7 @@ impl, const D: usize> CircuitBuilder { context!( self, &format!("verify {}'th initial Merkle proof", i), - self.verify_merkle_proof(evals.clone(), x_index, root, merkle_proof) + self.verify_merkle_proof(evals.clone(), x_index_bits, root, merkle_proof) ); } } @@ -256,28 +252,26 @@ impl, const D: usize> CircuitBuilder { let config = &common_data.config.fri_config; let n_log = log2_strict(n); // TODO: Do we need to range check `x_index` to a target smaller than `p`? - let mut x_index = challenger.get_challenge(self); - x_index = self.split_low_high(x_index, n_log, 64).0; - let mut x_index_num_bits = n_log; + let x_index = challenger.get_challenge(self); + let mut x_index_bits = self.low_bits(x_index, n_log, 64); let mut domain_size = n; context!( self, "check FRI initial proof", self.fri_verify_initial_proof( - x_index, + &x_index_bits, &round_proof.initial_trees_proof, initial_merkle_roots, ) ); - let mut old_x_index = self.zero(); + let mut old_x_index_bits = Vec::new(); // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. let mut subgroup_x = context!(self, "compute x from its index", { let g = self.constant(F::MULTIPLICATIVE_GROUP_GENERATOR); let phi = self.constant(F::primitive_root_of_unity(n_log)); - let reversed_x = self.reverse_limbs::<2>(x_index, n_log); - let phi = self.exp(phi, reversed_x, n_log); + let phi = self.exp_from_bits(phi, x_index_bits.iter().rev()); self.mul(g, phi) }); @@ -305,7 +299,7 @@ impl, const D: usize> CircuitBuilder { "infer evaluation using interpolation", self.compute_evaluation( subgroup_x, - old_x_index, + &old_x_index_bits, config.reduction_arity_bits[i - 1], last_evals, betas[i - 1], @@ -314,15 +308,16 @@ impl, const D: usize> CircuitBuilder { }; let mut evals = round_proof.steps[i].evals.clone(); // Insert P(y) into the evaluation vector, since it wasn't included by the prover. - let (low_x_index, high_x_index) = - self.split_low_high(x_index, arity_bits, x_index_num_bits); + let high_x_index_bits = x_index_bits.split_off(arity_bits); + old_x_index_bits = x_index_bits; + let low_x_index = self.le_sum(old_x_index_bits.iter()); evals = self.insert(low_x_index, e_x, evals); context!( self, "verify FRI round Merkle proof.", self.verify_merkle_proof( flatten_target(&evals), - high_x_index, + &high_x_index_bits, proof.commit_phase_merkle_roots[i], &round_proof.steps[i].merkle_proof, ) @@ -334,9 +329,7 @@ impl, const D: usize> CircuitBuilder { subgroup_x = self.exp_power_of_2(subgroup_x, config.reduction_arity_bits[i - 1]); } domain_size = next_domain_size; - old_x_index = low_x_index; - x_index = high_x_index; - x_index_num_bits -= arity_bits; + x_index_bits = high_x_index_bits; } let last_evals = evaluations.last().unwrap(); @@ -346,7 +339,7 @@ impl, const D: usize> CircuitBuilder { "infer final evaluation using interpolation", self.compute_evaluation( subgroup_x, - old_x_index, + &old_x_index_bits, final_arity_bits, last_evals, *betas.last().unwrap(), diff --git a/src/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs index 6dcf1b3d..cfeef82e 100644 --- a/src/gadgets/arithmetic.rs +++ b/src/gadgets/arithmetic.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::Extendable; use crate::target::Target; @@ -168,16 +170,18 @@ impl, const D: usize> CircuitBuilder { // TODO: Optimize this, maybe with a new gate. // TODO: Test /// Exponentiate `base` to the power of `exponent`, given by its little-endian bits. - pub fn exp_from_bits(&mut self, base: Target, exponent_bits: &[Target]) -> Target { + pub fn exp_from_bits( + &mut self, + base: Target, + exponent_bits: impl Iterator>, + ) -> Target { let mut current = base; - let one_ext = self.one_extension(); - let mut product = self.one(); + let one = self.one(); + let mut product = one; - for &bit in exponent_bits { - // TODO: Add base field select. - let current_ext = self.convert_to_ext(current); - let multiplicand = self.select(bit, current_ext, one_ext); - product = self.mul(product, multiplicand.0[0]); + for bit in exponent_bits { + let multiplicand = self.select(*bit.borrow(), current, one); + product = self.mul(product, multiplicand); current = self.mul(current, current); } @@ -187,16 +191,18 @@ impl, const D: usize> CircuitBuilder { // TODO: Optimize this, maybe with a new gate. // TODO: Test /// Exponentiate `base` to the power of `2^bit_length-1-exponent`, given by its little-endian bits. - pub fn exp_from_complement_bits(&mut self, base: Target, exponent_bits: &[Target]) -> Target { + pub fn exp_from_complement_bits( + &mut self, + base: Target, + exponent_bits: impl Iterator>, + ) -> Target { let mut current = base; - let one_ext = self.one_extension(); - let mut product = self.one(); + let one = self.one(); + let mut product = one; - for &bit in exponent_bits { - let current_ext = self.convert_to_ext(current); - // TODO: Add base field select. - let multiplicand = self.select(bit, one_ext, current_ext); - product = self.mul(product, multiplicand.0[0]); + for bit in exponent_bits { + let multiplicand = self.select(*bit.borrow(), one, current); + product = self.mul(product, multiplicand); current = self.mul(current, current); } @@ -208,7 +214,7 @@ impl, const D: usize> CircuitBuilder { /// Exponentiate `base` to the power of `exponent`, where `exponent < 2^num_bits`. pub fn exp(&mut self, base: Target, exponent: Target, num_bits: usize) -> Target { let exponent_bits = self.split_le(exponent, num_bits); - self.exp_from_bits(base, &exponent_bits) + self.exp_from_bits(base, exponent_bits.iter()) } /// Exponentiate `base` to the power of a known `exponent`. diff --git a/src/gadgets/arithmetic_extension.rs b/src/gadgets/arithmetic_extension.rs index e6efb451..7d43dbfb 100644 --- a/src/gadgets/arithmetic_extension.rs +++ b/src/gadgets/arithmetic_extension.rs @@ -24,7 +24,7 @@ impl, const D: usize> CircuitBuilder { second_multiplicand_1: ExtensionTarget, second_addend: ExtensionTarget, ) -> (ExtensionTarget, ExtensionTarget) { - let gate = self.add_gate(ArithmeticExtensionGate::new(), vec![const_0, const_1]); + let gate = self.add_gate(ArithmeticExtensionGate, vec![const_0, const_1]); let wire_first_multiplicand_0 = ExtensionTarget::from_range( gate, diff --git a/src/gadgets/hash.rs b/src/gadgets/hash.rs index fae6d433..dfe46772 100644 --- a/src/gadgets/hash.rs +++ b/src/gadgets/hash.rs @@ -11,8 +11,8 @@ use crate::wire::Wire; impl, const D: usize> CircuitBuilder { pub fn permute(&mut self, inputs: [Target; 12]) -> [Target; 12] { let zero = self.zero(); - let gate = - self.add_gate_no_constants(GMiMCGate::::with_automatic_constants()); + let gate_type = GMiMCGate::::new_automatic_constants(); + let gate = self.add_gate(gate_type, vec![]); // We don't want to swap any inputs, so set that wire to 0. let swap_wire = GMiMCGate::::WIRE_SWAP; @@ -22,15 +22,6 @@ impl, const D: usize> CircuitBuilder { }); self.route(zero, swap_wire); - // The old accumulator wire doesn't matter, since we won't read the new accumulator wire. - // We do have to set it to something though, so we'll arbitrary pick 0. - let old_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD; - let old_acc_wire = Target::Wire(Wire { - gate, - input: old_acc_wire, - }); - self.route(zero, old_acc_wire); - // Route input wires. for i in 0..12 { let in_wire = GMiMCGate::::wire_input(i); diff --git a/src/gadgets/insert.rs b/src/gadgets/insert.rs index bcdf16cc..8406b83a 100644 --- a/src/gadgets/insert.rs +++ b/src/gadgets/insert.rs @@ -15,11 +15,8 @@ impl, const D: usize> CircuitBuilder { element: ExtensionTarget, v: Vec>, ) -> Vec> { - let gate = InsertionGate:: { - vec_size: v.len(), - _phantom: PhantomData, - }; - let gate_index = self.add_gate_no_constants(InsertionGate::new(v.len())); + let gate = InsertionGate::new(v.len()); + let gate_index = self.add_gate(gate.clone(), vec![]); v.iter().enumerate().for_each(|(i, &val)| { self.route_extension( diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 26e049d2..5de2c651 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; @@ -33,12 +31,8 @@ impl, const D: usize> CircuitBuilder { interpolation_points: &[(Target, ExtensionTarget)], evaluation_point: ExtensionTarget, ) -> ExtensionTarget { - let gate = InterpolationGate:: { - num_points: interpolation_points.len(), - _phantom: PhantomData, - }; - let gate_index = - self.add_gate_no_constants(InterpolationGate::new(interpolation_points.len())); + let gate = InterpolationGate::new(interpolation_points.len()); + let gate_index = self.add_gate(gate.clone(), vec![]); for (i, &(p, v)) in interpolation_points.iter().enumerate() { self.route(p, Target::wire(gate_index, gate.wire_point(i))); self.route_extension( diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index 2f216870..4c4160e1 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -5,6 +5,6 @@ pub mod insert; pub mod interpolation; pub mod polynomial; pub mod range_check; -pub mod rotate; +pub mod select; pub mod split_base; pub(crate) mod split_join; diff --git a/src/gadgets/range_check.rs b/src/gadgets/range_check.rs index c0848af8..e3408cbf 100644 --- a/src/gadgets/range_check.rs +++ b/src/gadgets/range_check.rs @@ -14,6 +14,13 @@ impl, const D: usize> CircuitBuilder { self.route(x, sum); } + /// Returns the first `num_low_bits` little-endian bits of `x`. + pub fn low_bits(&mut self, x: Target, num_low_bits: usize, num_bits: usize) -> Vec { + let mut res = self.split_le(x, num_bits); + res.truncate(num_low_bits); + res + } + /// Returns `(a,b)` such that `x = a + 2^n_log * b` with `a < 2^n_log`. /// `x` is assumed to be range-checked for having `num_bits` bits. pub fn split_low_high(&mut self, x: Target, n_log: usize, num_bits: usize) -> (Target, Target) { diff --git a/src/gadgets/rotate.rs b/src/gadgets/rotate.rs deleted file mode 100644 index 67677795..00000000 --- a/src/gadgets/rotate.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::circuit_builder::CircuitBuilder; -use crate::field::extension_field::target::ExtensionTarget; -use crate::field::extension_field::Extendable; -use crate::target::Target; -use crate::util::log2_ceil; - -impl, const D: usize> CircuitBuilder { - /// Selects `x` or `y` based on `b`, which is assumed to be binary. - /// In particular, this returns `if b { x } else { y }`. - /// Note: This does not range-check `b`. - // TODO: This uses 10 gates per call. If addends are added to `MulExtensionGate`, this will be - // reduced to 2 gates. We could also use a new degree 2 `SelectGate` for this. - // If `num_routed_wire` is larger than 26, we could batch two `select` in one gate. - pub fn select( - &mut self, - b: Target, - x: ExtensionTarget, - y: ExtensionTarget, - ) -> ExtensionTarget { - let b_y_minus_y = self.scalar_mul_sub_extension(b, y, y); - self.scalar_mul_sub_extension(b, x, b_y_minus_y) - } - - /// Left-rotates an array `k` times if `b=1` else return the same array. - pub fn rotate_left_fixed( - &mut self, - b: Target, - k: usize, - v: &[ExtensionTarget], - ) -> Vec> { - let len = v.len(); - debug_assert!(k < len, "Trying to rotate by more than the vector length."); - let mut res = Vec::new(); - - for i in 0..len { - res.push(self.select(b, v[(i + k) % len], v[i])); - } - - res - } - - /// Left-rotates an array `k` times if `b=1` else return the same array. - pub fn rotate_right_fixed( - &mut self, - b: Target, - k: usize, - v: &[ExtensionTarget], - ) -> Vec> { - let len = v.len(); - debug_assert!(k < len, "Trying to rotate by more than the vector length."); - let mut res = Vec::new(); - - for i in 0..len { - res.push(self.select(b, v[(len + i - k) % len], v[i])); - } - - res - } - - /// Left-rotates an vector by the `Target` having bits given in little-endian by `num_rotation_bits`. - pub fn rotate_left_from_bits( - &mut self, - num_rotation_bits: &[Target], - v: &[ExtensionTarget], - ) -> Vec> { - let mut v = v.to_vec(); - - for i in 0..num_rotation_bits.len() { - v = self.rotate_left_fixed(num_rotation_bits[i], 1 << i, &v); - } - - v - } - - pub fn rotate_right_from_bits( - &mut self, - num_rotation_bits: &[Target], - v: &[ExtensionTarget], - ) -> Vec> { - let mut v = v.to_vec(); - - for i in 0..num_rotation_bits.len() { - v = self.rotate_right_fixed(num_rotation_bits[i], 1 << i, &v); - } - - v - } - - /// Left-rotates an array by `num_rotation`. Assumes that `num_rotation` is range-checked to be - /// less than `2^len_bits`. - pub fn rotate_left( - &mut self, - num_rotation: Target, - v: &[ExtensionTarget], - ) -> Vec> { - let len_bits = log2_ceil(v.len()); - let bits = self.split_le(num_rotation, len_bits); - - self.rotate_left_from_bits(&bits, v) - } - - pub fn rotate_right( - &mut self, - num_rotation: Target, - v: &[ExtensionTarget], - ) -> Vec> { - let len_bits = log2_ceil(v.len()); - let bits = self.split_le(num_rotation, len_bits); - - self.rotate_right_from_bits(&bits, v) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - - use super::*; - use crate::circuit_data::CircuitConfig; - use crate::field::crandall_field::CrandallField; - use crate::field::extension_field::quartic::QuarticCrandallField; - use crate::field::field::Field; - use crate::verifier::verify; - use crate::witness::PartialWitness; - - fn real_rotate( - num_rotation: usize, - v: &[ExtensionTarget], - ) -> Vec> { - let mut res = v.to_vec(); - res.rotate_left(num_rotation); - res - } - - fn test_rotate_given_len(len: usize) -> Result<()> { - type F = CrandallField; - type FF = QuarticCrandallField; - let config = CircuitConfig::large_config(); - let mut builder = CircuitBuilder::::new(config); - let v = (0..len) - .map(|_| builder.constant_extension(FF::rand())) - .collect::>(); - - for i in 0..len { - let it = builder.constant(F::from_canonical_usize(i)); - let rotated = real_rotate(i, &v); - let purported_rotated = builder.rotate_left(it, &v); - - for (x, y) in rotated.into_iter().zip(purported_rotated) { - builder.assert_equal_extension(x, y); - } - } - - let data = builder.build(); - let proof = data.prove(PartialWitness::new())?; - - verify(proof, &data.verifier_only, &data.common) - } - - #[test] - fn test_rotate() -> Result<()> { - for len in 1..5 { - test_rotate_given_len(len)?; - } - Ok(()) - } -} diff --git a/src/gadgets/select.rs b/src/gadgets/select.rs new file mode 100644 index 00000000..bbd36d76 --- /dev/null +++ b/src/gadgets/select.rs @@ -0,0 +1,76 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::gates::arithmetic::ArithmeticExtensionGate; +use crate::target::Target; + +impl, const D: usize> CircuitBuilder { + /// Selects `x` or `y` based on `b`, which is assumed to be binary, i.e., this returns `if b { x } else { y }`. + /// This expression is gotten as `bx - (by-y)`, which can be computed with a single `ArithmeticExtensionGate`. + /// Note: This does not range-check `b`. + pub fn select_ext( + &mut self, + b: Target, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let b_ext = self.convert_to_ext(b); + let gate = self.num_gates(); + // Holds `by - y`. + let first_out = + ExtensionTarget::from_range(gate, ArithmeticExtensionGate::::wires_first_output()); + self.double_arithmetic_extension(F::ONE, F::NEG_ONE, b_ext, y, y, b_ext, x, first_out) + .1 + } + + /// See `select_ext`. + pub fn select(&mut self, b: Target, x: Target, y: Target) -> Target { + let x_ext = self.convert_to_ext(x); + let y_ext = self.convert_to_ext(y); + self.select_ext(b, x_ext, y_ext).to_target_array()[0] + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::field::Field; + use crate::verifier::verify; + use crate::witness::PartialWitness; + + #[test] + fn test_select() -> Result<()> { + type F = CrandallField; + type FF = QuarticCrandallField; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::new(); + + let (x, y) = (FF::rand(), FF::rand()); + let xt = builder.add_virtual_extension_target(); + let yt = builder.add_virtual_extension_target(); + let truet = builder.add_virtual_target(); + let falset = builder.add_virtual_target(); + + pw.set_extension_target(xt, x); + pw.set_extension_target(yt, y); + pw.set_target(truet, F::ONE); + pw.set_target(falset, F::ZERO); + + let should_be_x = builder.select_ext(truet, xt, yt); + let should_be_y = builder.select_ext(falset, xt, yt); + + builder.assert_equal_extension(should_be_x, xt); + builder.assert_equal_extension(should_be_y, yt); + + let data = builder.build(); + let proof = data.prove(pw)?; + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/src/gadgets/split_base.rs b/src/gadgets/split_base.rs index 1223170e..1929fd1c 100644 --- a/src/gadgets/split_base.rs +++ b/src/gadgets/split_base.rs @@ -1,7 +1,12 @@ +use std::borrow::Borrow; + use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::Extendable; +use crate::field::field::Field; use crate::gates::base_sum::BaseSumGate; +use crate::generator::{GeneratedValues, SimpleGenerator}; use crate::target::Target; +use crate::witness::PartialWitness; impl, const D: usize> CircuitBuilder { /// Split the given element into a list of targets, where each one represents a @@ -11,14 +16,12 @@ impl, const D: usize> CircuitBuilder { x: Target, num_limbs: usize, ) -> Vec { - let gate = self.add_gate(BaseSumGate::::new(num_limbs), vec![]); + let gate_type = BaseSumGate::::new(num_limbs); + let gate = self.add_gate(gate_type.clone(), vec![]); let sum = Target::wire(gate, BaseSumGate::::WIRE_SUM); self.route(x, sum); - Target::wires_from_range( - gate, - BaseSumGate::::START_LIMBS..BaseSumGate::::START_LIMBS + num_limbs, - ) + Target::wires_from_range(gate, gate_type.limbs()) } /// Asserts that `x`'s big-endian bit representation has at least `leading_zeros` leading zeros. @@ -33,11 +36,65 @@ impl, const D: usize> CircuitBuilder { Target::wire(gate, BaseSumGate::::WIRE_REVERSED_SUM) } + + /// Takes an iterator of bits `(b_i)` and returns `sum b_i * 2^i`, i.e., + /// the number with little-endian bit representation given by `bits`. + pub(crate) fn le_sum( + &mut self, + bits: impl ExactSizeIterator> + Clone, + ) -> Target { + let num_bits = bits.len(); + debug_assert!( + BaseSumGate::<2>::START_LIMBS + num_bits <= self.config.num_routed_wires, + "Not enough routed wires." + ); + let gate_index = self.add_gate(BaseSumGate::<2>::new(num_bits), vec![]); + for (limb, wire) in bits + .clone() + .zip(BaseSumGate::<2>::START_LIMBS..BaseSumGate::<2>::START_LIMBS + num_bits) + { + self.route(*limb.borrow(), Target::wire(gate_index, wire)); + } + + self.add_generator(BaseSumGenerator::<2> { + gate_index, + limbs: bits.map(|l| *l.borrow()).collect(), + }); + + Target::wire(gate_index, BaseSumGate::<2>::WIRE_SUM) + } +} + +#[derive(Debug)] +struct BaseSumGenerator { + gate_index: usize, + limbs: Vec, +} + +impl SimpleGenerator for BaseSumGenerator { + fn dependencies(&self) -> Vec { + self.limbs.clone() + } + + fn run_once(&self, witness: &PartialWitness) -> GeneratedValues { + let sum = self + .limbs + .iter() + .map(|&t| witness.get_target(t)) + .rev() + .fold(F::ZERO, |acc, limb| acc * F::from_canonical_usize(B) + limb); + + GeneratedValues::singleton_target( + Target::wire(self.gate_index, BaseSumGate::::WIRE_SUM), + sum, + ) + } } #[cfg(test)] mod tests { use anyhow::Result; + use rand::{thread_rng, Rng}; use super::*; use crate::circuit_data::CircuitConfig; @@ -73,4 +130,36 @@ mod tests { verify(proof, &data.verifier_only, &data.common) } + + #[test] + fn test_base_sum() -> Result<()> { + type F = CrandallField; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + + let n = thread_rng().gen_range(0, 1 << 10); + let x = builder.constant(F::from_canonical_usize(n)); + + let zero = builder.zero(); + let one = builder.one(); + + let y = builder.le_sum( + (0..10) + .scan(n, |acc, _| { + let tmp = *acc % 2; + *acc /= 2; + Some(if tmp == 1 { one } else { zero }) + }) + .collect::>() + .iter(), + ); + + builder.assert_equal(x, y); + + let data = builder.build(); + + let proof = data.prove(PartialWitness::new())?; + + verify(proof, &data.verifier_only, &data.common) + } } diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index 9cc6ab7c..2cf5b9b2 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -35,7 +35,7 @@ impl, const D: usize> CircuitBuilder { let bits_per_gate = self.config.num_routed_wires - BaseSumGate::<2>::START_LIMBS; let k = ceil_div_usize(num_bits, bits_per_gate); let gates = (0..k) - .map(|_| self.add_gate_no_constants(BaseSumGate::<2>::new(bits_per_gate))) + .map(|_| self.add_gate(BaseSumGate::<2>::new(bits_per_gate), vec![])) .collect::>(); let mut bits = Vec::with_capacity(num_bits); diff --git a/src/gates/arithmetic.rs b/src/gates/arithmetic.rs index cf39e09b..4964fbd0 100644 --- a/src/gates/arithmetic.rs +++ b/src/gates/arithmetic.rs @@ -3,10 +3,11 @@ use std::ops::Range; use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; -use crate::gates::gate::{Gate, GateRef}; +use crate::field::extension_field::FieldExtension; +use crate::gates::gate::Gate; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::witness::PartialWitness; /// A gate which can a linear combination `c0*x*y+c1*z` twice with the same `x`. @@ -14,10 +15,6 @@ use crate::witness::PartialWitness; pub struct ArithmeticExtensionGate; impl ArithmeticExtensionGate { - pub fn new>() -> GateRef { - GateRef::new(ArithmeticExtensionGate) - } - pub fn wires_first_multiplicand_0() -> Range { 0..D } @@ -74,6 +71,31 @@ impl, const D: usize> Gate for ArithmeticExtensionGate constraints } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let const_0 = vars.local_constants[0]; + let const_1 = vars.local_constants[1]; + + let first_multiplicand_0 = vars.get_local_ext(Self::wires_first_multiplicand_0()); + let first_multiplicand_1 = vars.get_local_ext(Self::wires_first_multiplicand_1()); + let first_addend = vars.get_local_ext(Self::wires_first_addend()); + let second_multiplicand_0 = vars.get_local_ext(Self::wires_second_multiplicand_0()); + let second_multiplicand_1 = vars.get_local_ext(Self::wires_second_multiplicand_1()); + let second_addend = vars.get_local_ext(Self::wires_second_addend()); + let first_output = vars.get_local_ext(Self::wires_first_output()); + let second_output = vars.get_local_ext(Self::wires_second_output()); + + let first_computed_output = first_multiplicand_0 * first_multiplicand_1 * const_0.into() + + first_addend * const_1.into(); + let second_computed_output = second_multiplicand_0 * second_multiplicand_1 * const_0.into() + + second_addend * const_1.into(); + + let mut constraints = (first_output - first_computed_output) + .to_basefield_array() + .to_vec(); + constraints.extend((second_output - second_computed_output).to_basefield_array()); + constraints + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -234,6 +256,6 @@ mod tests { #[test] fn low_degree() { - test_low_degree(ArithmeticExtensionGate::<4>::new::()) + test_low_degree::(ArithmeticExtensionGate) } } diff --git a/src/gates/base_sum.rs b/src/gates/base_sum.rs index 8ad189ee..cd1694e4 100644 --- a/src/gates/base_sum.rs +++ b/src/gates/base_sum.rs @@ -8,19 +8,19 @@ use crate::gates::gate::{Gate, GateRef}; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::plonk_common::{reduce_with_powers, reduce_with_powers_recursive}; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::witness::PartialWitness; /// A gate which can decompose a number into base B little-endian limbs, /// and compute the limb-reversed (i.e. big-endian) sum. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct BaseSumGate { num_limbs: usize, } impl BaseSumGate { - pub fn new, const D: usize>(num_limbs: usize) -> GateRef { - GateRef::new(BaseSumGate:: { num_limbs }) + pub fn new(num_limbs: usize) -> Self { + Self { num_limbs } } pub const WIRE_SUM: usize = 0; @@ -57,6 +57,20 @@ impl, const D: usize, const B: usize> Gate for BaseSumGat constraints } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let sum = vars.local_wires[Self::WIRE_SUM]; + let reversed_sum = vars.local_wires[Self::WIRE_REVERSED_SUM]; + let mut limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers(&limbs, F::from_canonical_usize(B)); + limbs.reverse(); + let computed_reversed_sum = reduce_with_powers(&limbs, F::from_canonical_usize(B)); + let mut constraints = vec![computed_sum - sum, computed_reversed_sum - reversed_sum]; + for limb in limbs { + constraints.push((0..B).map(|i| limb - F::from_canonical_usize(i)).product()); + } + constraints + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -172,10 +186,11 @@ impl SimpleGenerator for BaseSplitGenerator { mod tests { use crate::field::crandall_field::CrandallField; use crate::gates::base_sum::BaseSumGate; + use crate::gates::gate::GateRef; use crate::gates::gate_testing::test_low_degree; #[test] fn low_degree() { - test_low_degree(BaseSumGate::<6>::new::(11)) + test_low_degree::(BaseSumGate::<6>::new(11)) } } diff --git a/src/gates/constant.rs b/src/gates/constant.rs index 4049d058..1d8f50dd 100644 --- a/src/gates/constant.rs +++ b/src/gates/constant.rs @@ -5,7 +5,7 @@ use crate::field::field::Field; use crate::gates::gate::{Gate, GateRef}; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -13,10 +13,6 @@ use crate::witness::PartialWitness; pub struct ConstantGate; impl ConstantGate { - pub fn get, const D: usize>() -> GateRef { - GateRef::new(ConstantGate) - } - pub const CONST_INPUT: usize = 0; pub const WIRE_OUTPUT: usize = 0; @@ -33,6 +29,12 @@ impl, const D: usize> Gate for ConstantGate { vec![output - input] } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let input = vars.local_constants[Self::CONST_INPUT]; + let output = vars.local_wires[Self::WIRE_OUTPUT]; + vec![output - input] + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -100,6 +102,6 @@ mod tests { #[test] fn low_degree() { - test_low_degree(ConstantGate::get::()) + test_low_degree::(ConstantGate) } } diff --git a/src/gates/gate.rs b/src/gates/gate.rs index 17059332..d961fb58 100644 --- a/src/gates/gate.rs +++ b/src/gates/gate.rs @@ -139,7 +139,7 @@ impl, const D: usize> Debug for GateRef { /// A gate along with any constants used to configure it. pub struct GateInstance, const D: usize> { - pub gate_type: GateRef, + pub gate_ref: GateRef, pub constants: Vec, } diff --git a/src/gates/gate_testing.rs b/src/gates/gate_testing.rs index 2f7a3020..b71d1509 100644 --- a/src/gates/gate_testing.rs +++ b/src/gates/gate_testing.rs @@ -1,6 +1,6 @@ use crate::field::extension_field::Extendable; use crate::field::field::Field; -use crate::gates::gate::GateRef; +use crate::gates::gate::{Gate, GateRef}; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::Hash; use crate::util::{log2_ceil, transpose}; @@ -11,8 +11,7 @@ const WITNESS_DEGREE: usize = WITNESS_SIZE - 1; /// Tests that the constraints imposed by the given gate are low-degree by applying them to random /// low-degree witness polynomials. -pub(crate) fn test_low_degree, const D: usize>(gate: GateRef) { - let gate = gate.0; +pub(crate) fn test_low_degree, G: Gate, const D: usize>(gate: G) { let rate_bits = log2_ceil(gate.degree() + 1); let wire_ldes = random_low_degree_matrix::(gate.num_wires(), rate_bits); diff --git a/src/gates/gate_tree.rs b/src/gates/gate_tree.rs index 0c10ed70..50a6f39e 100644 --- a/src/gates/gate_tree.rs +++ b/src/gates/gate_tree.rs @@ -237,12 +237,12 @@ mod tests { const D: usize = 4; let gates = vec![ - NoopGate::get::(), - ConstantGate::get(), - ArithmeticExtensionGate::new(), - BaseSumGate::<4>::new(4), - GMiMCGate::::with_automatic_constants(), - InterpolationGate::new(4), + GateRef::new(NoopGate), + GateRef::new(ConstantGate), + GateRef::new(ArithmeticExtensionGate), + GateRef::new(BaseSumGate::<4>::new(4)), + GateRef::new(GMiMCGate::::new_automatic_constants()), + GateRef::new(InterpolationGate::new(4)), ]; let len = gates.len(); diff --git a/src/gates/gmimc.rs b/src/gates/gmimc.rs index 0404884b..2c8ca297 100644 --- a/src/gates/gmimc.rs +++ b/src/gates/gmimc.rs @@ -4,11 +4,11 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field::Field; -use crate::gates::gate::{Gate, GateRef}; +use crate::gates::gate::Gate; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::gmimc::gmimc_automatic_constants; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -28,14 +28,13 @@ pub struct GMiMCGate, const D: usize, const R: usize> { } impl, const D: usize, const R: usize> GMiMCGate { - pub fn with_constants(constants: Arc<[F; R]>) -> GateRef { - let gate = GMiMCGate:: { constants }; - GateRef::new(gate) + pub fn new(constants: Arc<[F; R]>) -> Self { + Self { constants } } - pub fn with_automatic_constants() -> GateRef { + pub fn new_automatic_constants() -> Self { let constants = Arc::new(gmimc_automatic_constants::()); - Self::with_constants(constants) + Self::new(constants) } /// The wire index for the `i`th input to the permutation. @@ -48,22 +47,18 @@ impl, const D: usize, const R: usize> GMiMCGate { W + i } - /// Used to incrementally compute the index of the leaf based on a series of swap bits. - pub const WIRE_INDEX_ACCUMULATOR_OLD: usize = 2 * W; - pub const WIRE_INDEX_ACCUMULATOR_NEW: usize = 2 * W + 1; - /// If this is set to 1, the first four inputs will be swapped with the next four inputs. This /// is useful for ordering hashes in Merkle proofs. Otherwise, this should be set to 0. - pub const WIRE_SWAP: usize = 2 * W + 2; + pub const WIRE_SWAP: usize = 2 * W; /// A wire which stores the input to the `i`th cubing. fn wire_cubing_input(i: usize) -> usize { - 2 * W + 3 + i + 2 * W + 1 + i } /// End of wire indices, exclusive. fn end() -> usize { - 2 * W + 3 + R + 2 * W + 1 + R } } @@ -79,11 +74,6 @@ impl, const D: usize, const R: usize> Gate for GMiMCGate< let swap = vars.local_wires[Self::WIRE_SWAP]; constraints.push(swap * (swap - F::Extension::ONE)); - let old_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_OLD]; - let new_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_NEW]; - let computed_new_index_acc = F::Extension::TWO * old_index_acc + swap; - constraints.push(computed_new_index_acc - new_index_acc); - let mut state = Vec::with_capacity(12); for i in 0..4 { let a = vars.local_wires[i]; @@ -121,6 +111,50 @@ impl, const D: usize, const R: usize> Gate for GMiMCGate< constraints } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + // Assert that `swap` is binary. + let swap = vars.local_wires[Self::WIRE_SWAP]; + constraints.push(swap * (swap - F::ONE)); + + let mut state = Vec::with_capacity(12); + for i in 0..4 { + let a = vars.local_wires[i]; + let b = vars.local_wires[i + 4]; + state.push(a + swap * (b - a)); + } + for i in 0..4 { + let a = vars.local_wires[i + 4]; + let b = vars.local_wires[i]; + state.push(a + swap * (b - a)); + } + for i in 8..12 { + state.push(vars.local_wires[i]); + } + + // Value that is implicitly added to each element. + // See https://affine.group/2020/02/starkware-challenge + let mut addition_buffer = F::ZERO; + + for r in 0..R { + let active = r % W; + let cubing_input = state[active] + addition_buffer + self.constants[r].into(); + let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)]; + constraints.push(cubing_input - cubing_input_wire); + let f = cubing_input_wire.cube(); + addition_buffer += f; + state[active] -= f; + } + + for i in 0..W { + state[i] += addition_buffer; + constraints.push(state[i] - vars.local_wires[Self::wire_output(i)]); + } + + constraints + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -131,13 +165,6 @@ impl, const D: usize, const R: usize> Gate for GMiMCGate< let swap = vars.local_wires[Self::WIRE_SWAP]; constraints.push(builder.mul_sub_extension(swap, swap, swap)); - let old_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_OLD]; - let new_index_acc = vars.local_wires[Self::WIRE_INDEX_ACCUMULATOR_NEW]; - // computed_new_index_acc = 2 * old_index_acc + swap - let two = builder.two_extension(); - let computed_new_index_acc = builder.mul_add_extension(two, old_index_acc, swap); - constraints.push(builder.sub_extension(computed_new_index_acc, new_index_acc)); - let mut state = Vec::with_capacity(12); for i in 0..4 { let a = vars.local_wires[i]; @@ -207,7 +234,7 @@ impl, const D: usize, const R: usize> Gate for GMiMCGate< } fn num_constraints(&self) -> usize { - R + W + 2 + R + W + 1 } } @@ -221,12 +248,11 @@ impl, const D: usize, const R: usize> SimpleGenerator for GMiMCGenerator { fn dependencies(&self) -> Vec { - let mut dep_input_indices = Vec::with_capacity(W + 2); + let mut dep_input_indices = Vec::with_capacity(W + 1); for i in 0..W { dep_input_indices.push(GMiMCGate::::wire_input(i)); } dep_input_indices.push(GMiMCGate::::WIRE_SWAP); - dep_input_indices.push(GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD); dep_input_indices .into_iter() @@ -240,7 +266,7 @@ impl, const D: usize, const R: usize> SimpleGenerator } fn run_once(&self, witness: &PartialWitness) -> GeneratedValues { - let mut result = GeneratedValues::with_capacity(R + W + 1); + let mut result = GeneratedValues::with_capacity(R + W); let mut state = (0..W) .map(|i| { @@ -262,20 +288,6 @@ impl, const D: usize, const R: usize> SimpleGenerator } } - // Update the index accumulator. - let old_index_acc_value = witness.get_wire(Wire { - gate: self.gate_index, - input: GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD, - }); - let new_index_acc_value = F::TWO * old_index_acc_value + swap_value; - result.set_wire( - Wire { - gate: self.gate_index, - input: GMiMCGate::::WIRE_INDEX_ACCUMULATOR_NEW, - }, - new_index_acc_value, - ); - // Value that is implicitly added to each element. // See https://affine.group/2020/02/starkware-challenge let mut addition_buffer = F::ZERO; @@ -322,6 +334,7 @@ mod tests { use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; + use crate::gates::gate::{Gate, GateRef}; use crate::gates::gate_testing::test_low_degree; use crate::gates::gmimc::{GMiMCGate, W}; use crate::generator::generate_partial_witness; @@ -338,24 +351,11 @@ mod tests { const R: usize = 101; let constants = Arc::new([F::TWO; R]); type Gate = GMiMCGate; - let gate = Gate::with_constants(constants.clone()); - - let config = CircuitConfig { - num_wires: 134, - num_routed_wires: 200, - ..Default::default() - }; + let gate = Gate::new(constants.clone()); let permutation_inputs = (0..W).map(F::from_canonical_usize).collect::>(); let mut witness = PartialWitness::new(); - witness.set_wire( - Wire { - gate: 0, - input: Gate::WIRE_INDEX_ACCUMULATOR_OLD, - }, - F::from_canonical_usize(7), - ); witness.set_wire( Wire { gate: 0, @@ -373,7 +373,7 @@ mod tests { ); } - let generators = gate.0.generators(0, &[]); + let generators = gate.generators(0, &[]); generate_partial_witness(&mut witness, &generators); let expected_outputs: [F; W] = @@ -386,12 +386,6 @@ mod tests { }); assert_eq!(out, expected_outputs[i]); } - - let acc_new = witness.get_wire(Wire { - gate: 0, - input: Gate::WIRE_INDEX_ACCUMULATOR_NEW, - }); - assert_eq!(acc_new, F::from_canonical_usize(7 * 2)); } #[test] @@ -399,8 +393,7 @@ mod tests { type F = CrandallField; const R: usize = 101; let constants = Arc::new([F::TWO; R]); - type Gate = GMiMCGate; - let gate = Gate::with_constants(constants); + let gate = GMiMCGate::::new(constants); test_low_degree(gate) } @@ -414,7 +407,7 @@ mod tests { let mut pw = PartialWitness::::new(); let constants = Arc::new([F::TWO; R]); type Gate = GMiMCGate; - let gate = Gate::with_constants(constants); + let gate = Gate::new(constants); let wires = FF::rand_vec(Gate::end()); let public_inputs_hash = &Hash::rand(); @@ -424,7 +417,7 @@ mod tests { public_inputs_hash, }; - let ev = gate.0.eval_unfiltered(vars); + let ev = gate.eval_unfiltered(vars); let wires_t = builder.add_virtual_extension_targets(Gate::end()); for i in 0..Gate::end() { @@ -440,7 +433,7 @@ mod tests { public_inputs_hash: &public_inputs_hash_t, }; - let ev_t = gate.0.eval_unfiltered_recursively(&mut builder, vars_t); + let ev_t = gate.eval_unfiltered_recursively(&mut builder, vars_t); assert_eq!(ev.len(), ev_t.len()); for (e, e_t) in ev.into_iter().zip(ev_t) { diff --git a/src/gates/insertion.rs b/src/gates/insertion.rs index 245db852..4b75ef44 100644 --- a/src/gates/insertion.rs +++ b/src/gates/insertion.rs @@ -9,7 +9,7 @@ use crate::field::field::Field; use crate::gates::gate::{Gate, GateRef}; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -17,16 +17,15 @@ use crate::witness::PartialWitness; #[derive(Clone, Debug)] pub(crate) struct InsertionGate, const D: usize> { pub vec_size: usize, - pub _phantom: PhantomData, + _phantom: PhantomData, } impl, const D: usize> InsertionGate { - pub fn new(vec_size: usize) -> GateRef { - let gate = Self { + pub fn new(vec_size: usize) -> Self { + Self { vec_size, _phantom: PhantomData, - }; - GateRef::new(gate) + } } pub fn wires_insertion_index(&self) -> usize { @@ -114,6 +113,44 @@ impl, const D: usize> Gate for InsertionGate { constraints } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let insertion_index = vars.local_wires[self.wires_insertion_index()]; + let list_items = (0..self.vec_size) + .map(|i| vars.get_local_ext(self.wires_original_list_item(i))) + .collect::>(); + let output_list_items = (0..=self.vec_size) + .map(|i| vars.get_local_ext(self.wires_output_list_item(i))) + .collect::>(); + let element_to_insert = vars.get_local_ext(self.wires_element_to_insert()); + + let mut constraints = Vec::new(); + let mut already_inserted = F::ZERO; + for r in 0..=self.vec_size { + let cur_index = F::from_canonical_usize(r); + let difference = cur_index - insertion_index; + let equality_dummy = vars.local_wires[self.wires_equality_dummy_for_round_r(r)]; + let insert_here = vars.local_wires[self.wires_insert_here_for_round_r(r)]; + + // The two equality constraints. + constraints.push(difference * equality_dummy - (F::ONE - insert_here)); + constraints.push(insert_here * difference); + + let mut new_item = element_to_insert * insert_here.into(); + if r > 0 { + new_item += list_items[r - 1] * already_inserted.into(); + } + already_inserted += insert_here; + if r < self.vec_size { + new_item += list_items[r] * (F::ONE - already_inserted).into(); + } + + // Output constraint. + constraints.extend((new_item - output_list_items[r]).to_basefield_array()); + } + + constraints + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -312,8 +349,7 @@ mod tests { #[test] fn low_degree() { - type F = CrandallField; - test_low_degree(InsertionGate::::new(4)); + test_low_degree::(InsertionGate::new(4)); } #[test] diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index 17d34e3a..47fa6d4a 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -10,8 +10,9 @@ use crate::field::interpolation::interpolant; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::{Gate, GateRef}; use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::polynomial::polynomial::PolynomialCoeffs; use crate::target::Target; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -23,16 +24,15 @@ use crate::witness::PartialWitness; #[derive(Clone, Debug)] pub(crate) struct InterpolationGate, const D: usize> { pub num_points: usize, - pub _phantom: PhantomData, + _phantom: PhantomData, } impl, const D: usize> InterpolationGate { - pub fn new(num_points: usize) -> GateRef { - let gate = Self { + pub fn new(num_points: usize) -> Self { + Self { num_points, _phantom: PhantomData, - }; - GateRef::new(gate) + } } fn start_points(&self) -> usize { @@ -121,6 +121,29 @@ impl, const D: usize> Gate for InterpolationGate { constraints } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points) + .map(|i| vars.get_local_ext(self.wires_coeff(i))) + .collect(); + let interpolant = PolynomialCoeffs::new(coeffs); + + for i in 0..self.num_points { + let point = vars.local_wires[self.wire_point(i)]; + let value = vars.get_local_ext(self.wires_value(i)); + let computed_value = interpolant.eval(point.into()); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval(evaluation_point); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -297,7 +320,7 @@ mod tests { #[test] fn low_degree() { type F = CrandallField; - test_low_degree(InterpolationGate::::new(4)); + test_low_degree::(InterpolationGate::new(4)); } #[test] @@ -313,31 +336,15 @@ mod tests { points: Vec, eval_point: FF, ) -> Vec { - let mut v = vec![F::ZERO; num_points * 5 + (coeffs.len() + 3) * D]; + let mut v = Vec::new(); + v.extend_from_slice(&points); for j in 0..num_points { - v[j] = points[j]; - } - for j in 0..num_points { - for i in 0..D { - v[num_points + D * j + i] = >::to_basefield_array( - &coeffs.eval(points[j].into()), - )[i]; - } - } - for i in 0..D { - v[num_points * 5 + i] = - >::to_basefield_array(&eval_point)[i]; - } - for i in 0..D { - v[num_points * 5 + D + i] = - >::to_basefield_array(&coeffs.eval(eval_point))[i]; + v.extend(coeffs.eval(points[j].into()).0); } + v.extend(eval_point.0); + v.extend(coeffs.eval(eval_point).0); for i in 0..coeffs.len() { - for (j, input) in - (0..D).zip(num_points * 5 + (2 + i) * D..num_points * 5 + (3 + i) * D) - { - v[input] = >::to_basefield_array(&coeffs.coeffs[i])[j]; - } + v.extend(coeffs.coeffs[i].0); } v.iter().map(|&x| x.into()).collect::>() } diff --git a/src/gates/noop.rs b/src/gates/noop.rs index a12df932..0787c9fa 100644 --- a/src/gates/noop.rs +++ b/src/gates/noop.rs @@ -3,17 +3,11 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::gates::gate::{Gate, GateRef}; use crate::generator::WitnessGenerator; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// A gate which does nothing. pub struct NoopGate; -impl NoopGate { - pub fn get, const D: usize>() -> GateRef { - GateRef::new(NoopGate) - } -} - impl, const D: usize> Gate for NoopGate { fn id(&self) -> String { "NoopGate".into() @@ -23,6 +17,10 @@ impl, const D: usize> Gate for NoopGate { Vec::new() } + fn eval_unfiltered_base(&self, _vars: EvaluationVarsBase) -> Vec { + Vec::new() + } + fn eval_unfiltered_recursively( &self, _builder: &mut CircuitBuilder, @@ -64,6 +62,6 @@ mod tests { #[test] fn low_degree() { - test_low_degree(NoopGate::get::()) + test_low_degree::(NoopGate) } } diff --git a/src/gates/public_input.rs b/src/gates/public_input.rs index a86b78d5..e8299b26 100644 --- a/src/gates/public_input.rs +++ b/src/gates/public_input.rs @@ -5,16 +5,12 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::gates::gate::{Gate, GateRef}; use crate::generator::WitnessGenerator; -use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// A gate whose first four wires will be equal to a hash of public inputs. pub struct PublicInputGate; impl PublicInputGate { - pub fn get, const D: usize>() -> GateRef { - GateRef::new(PublicInputGate) - } - pub fn wires_public_inputs_hash() -> Range { 0..4 } @@ -32,6 +28,13 @@ impl, const D: usize> Gate for PublicInputGate { .collect() } + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + Self::wires_public_inputs_hash() + .zip(vars.public_inputs_hash.elements) + .map(|(wire, hash_part)| vars.local_wires[wire] - hash_part) + .collect() + } + fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, @@ -79,6 +82,6 @@ mod tests { #[test] fn low_degree() { - test_low_degree(PublicInputGate::get::()) + test_low_degree::(PublicInputGate) } } diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index 03d5099d..6a352dec 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -59,24 +59,21 @@ pub(crate) fn verify_merkle_proof( impl, const D: usize> CircuitBuilder { /// Verifies that the given leaf data is present at the given index in the Merkle tree with the - /// given root. + /// given root. The index is given by it's little-endian bits. pub(crate) fn verify_merkle_proof( &mut self, leaf_data: Vec, - leaf_index: Target, + leaf_index_bits: &[Target], merkle_root: HashTarget, proof: &MerkleProofTarget, ) { let zero = self.zero(); - let height = proof.siblings.len(); - let purported_index_bits = self.split_le_virtual(leaf_index, height); let mut state: HashTarget = self.hash_or_noop(leaf_data); - let mut acc_leaf_index = zero; - for (bit, &sibling) in purported_index_bits.into_iter().zip(&proof.siblings) { - let gate = self - .add_gate_no_constants(GMiMCGate::::with_automatic_constants()); + for (&bit, &sibling) in leaf_index_bits.iter().zip(&proof.siblings) { + let gate_type = GMiMCGate::::new_automatic_constants(); + let gate = self.add_gate(gate_type, vec![]); let swap_wire = GMiMCGate::::WIRE_SWAP; let swap_wire = Target::Wire(Wire { @@ -85,20 +82,6 @@ impl, const D: usize> CircuitBuilder { }); self.generate_copy(bit, swap_wire); - let old_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_OLD; - let old_acc_wire = Target::Wire(Wire { - gate, - input: old_acc_wire, - }); - self.route(acc_leaf_index, old_acc_wire); - - let new_acc_wire = GMiMCGate::::WIRE_INDEX_ACCUMULATOR_NEW; - let new_acc_wire = Target::Wire(Wire { - gate, - input: new_acc_wire, - }); - acc_leaf_index = new_acc_wire; - let input_wires = (0..12) .map(|i| { Target::Wire(Wire { @@ -126,10 +109,6 @@ impl, const D: usize> CircuitBuilder { ) } - // TODO: this is far from optimal. - let leaf_index_rev = self.reverse_limbs::<2>(leaf_index, height); - self.assert_equal(acc_leaf_index, leaf_index_rev); - self.named_assert_hashes_equal(state, merkle_root, "check Merkle root".into()) } @@ -191,13 +170,14 @@ mod tests { pw.set_hash_target(root_t, tree.root); let i_c = builder.constant(F::from_canonical_usize(i)); + let i_bits = builder.split_le(i_c, log_n); let data = builder.add_virtual_targets(tree.leaves[i].len()); for j in 0..data.len() { pw.set_target(data[j], tree.leaves[i][j]); } - builder.verify_merkle_proof(data, i_c, root_t, &proof_t); + builder.verify_merkle_proof(data, &i_bits, root_t, &proof_t); let data = builder.build(); let proof = data.prove(pw)?; diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index 0fe62a77..cc92ccdb 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -365,7 +365,7 @@ mod tests { type F = CrandallField; const D: usize = 4; let config = CircuitConfig { - num_wires: 134, + num_wires: 126, num_routed_wires: 33, security_bits: 128, rate_bits: 3, diff --git a/src/vars.rs b/src/vars.rs index 8e98d41f..66ce2efb 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -3,7 +3,7 @@ use std::ops::Range; use crate::field::extension_field::algebra::ExtensionAlgebra; use crate::field::extension_field::target::{ExtensionAlgebraTarget, ExtensionTarget}; -use crate::field::extension_field::Extendable; +use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::proof::{Hash, HashTarget}; @@ -37,6 +37,15 @@ impl<'a, F: Extendable, const D: usize> EvaluationVars<'a, F, D> { } impl<'a, F: Field> EvaluationVarsBase<'a, F> { + pub fn get_local_ext(&self, wire_range: Range) -> F::Extension + where + F: Extendable, + { + debug_assert_eq!(wire_range.len(), D); + let arr = self.local_wires[wire_range].try_into().unwrap(); + F::Extension::from_basefield_array(arr) + } + pub fn remove_prefix(&mut self, prefix: &[bool]) { self.local_constants = &self.local_constants[prefix.len()..]; } diff --git a/src/witness.rs b/src/witness.rs index ce4a95af..2ef9d8b3 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -170,7 +170,7 @@ impl PartialWitness { "wire {} of gate #{} (`{}`)", input, gate, - gate_instances[*gate].gate_type.0.id() + gate_instances[*gate].gate_ref.0.id() ), Target::VirtualTarget { index } => format!("{}-th virtual target", index), }