Merge branch 'main' into exp_gate

This commit is contained in:
Nicholas Ward 2021-07-23 20:01:58 -07:00
commit da9017bcbc
29 changed files with 489 additions and 445 deletions

View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
.collect()
}
pub fn add_gate_no_constants(&mut self, gate_type: GateRef<F, D>) -> 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, D>, constants: Vec<F>) -> usize {
pub fn add_gate<G: Gate<F, D>>(&mut self, gate_type: G, constants: Vec<F>) -> 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<F, D>) {
fn check_gate_compatibility<G: Gate<F, D>>(&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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
}
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
.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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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()

View File

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

View File

@ -20,7 +20,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
fn compute_evaluation(
&mut self,
x: Target,
old_x_index: Target,
old_x_index_bits: &[Target],
arity_bits: usize,
last_evals: &[ExtensionTarget<D>],
beta: ExtensionTarget<D>,
@ -33,13 +33,9 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
"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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
};
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
"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(),

View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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<Item = impl Borrow<Target>>,
) -> 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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
// 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<Item = impl Borrow<Target>>,
) -> 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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// 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`.

View File

@ -24,7 +24,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
second_multiplicand_1: ExtensionTarget<D>,
second_addend: ExtensionTarget<D>,
) -> (ExtensionTarget<D>, ExtensionTarget<D>) {
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,

View File

@ -11,8 +11,8 @@ use crate::wire::Wire;
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
pub fn permute(&mut self, inputs: [Target; 12]) -> [Target; 12] {
let zero = self.zero();
let gate =
self.add_gate_no_constants(GMiMCGate::<F, D, GMIMC_ROUNDS>::with_automatic_constants());
let gate_type = GMiMCGate::<F, D, GMIMC_ROUNDS>::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::<F, D, GMIMC_ROUNDS>::WIRE_SWAP;
@ -22,15 +22,6 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
});
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::<F, D, GMIMC_ROUNDS>::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::<F, D, GMIMC_ROUNDS>::wire_input(i);

View File

@ -15,11 +15,8 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
element: ExtensionTarget<D>,
v: Vec<ExtensionTarget<D>>,
) -> Vec<ExtensionTarget<D>> {
let gate = InsertionGate::<F, D> {
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(

View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
interpolation_points: &[(Target, ExtensionTarget<D>)],
evaluation_point: ExtensionTarget<D>,
) -> ExtensionTarget<D> {
let gate = InterpolationGate::<F, D> {
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(

View File

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

View File

@ -14,6 +14,13 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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<Target> {
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) {

View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// 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<D>,
y: ExtensionTarget<D>,
) -> ExtensionTarget<D> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<D>],
) -> Vec<ExtensionTarget<D>> {
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<const D: usize>(
num_rotation: usize,
v: &[ExtensionTarget<D>],
) -> Vec<ExtensionTarget<D>> {
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::<F, 4>::new(config);
let v = (0..len)
.map(|_| builder.constant_extension(FF::rand()))
.collect::<Vec<_>>();
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(())
}
}

76
src/gadgets/select.rs Normal file
View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// 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<D>,
y: ExtensionTarget<D>,
) -> ExtensionTarget<D> {
let b_ext = self.convert_to_ext(b);
let gate = self.num_gates();
// Holds `by - y`.
let first_out =
ExtensionTarget::from_range(gate, ArithmeticExtensionGate::<D>::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::<F, 4>::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)
}
}

View File

@ -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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// Split the given element into a list of targets, where each one represents a
@ -11,14 +16,12 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
x: Target,
num_limbs: usize,
) -> Vec<Target> {
let gate = self.add_gate(BaseSumGate::<B>::new(num_limbs), vec![]);
let gate_type = BaseSumGate::<B>::new(num_limbs);
let gate = self.add_gate(gate_type.clone(), vec![]);
let sum = Target::wire(gate, BaseSumGate::<B>::WIRE_SUM);
self.route(x, sum);
Target::wires_from_range(
gate,
BaseSumGate::<B>::START_LIMBS..BaseSumGate::<B>::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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
Target::wire(gate, BaseSumGate::<B>::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<Item = impl Borrow<Target>> + 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<const B: usize> {
gate_index: usize,
limbs: Vec<Target>,
}
impl<F: Field, const B: usize> SimpleGenerator<F> for BaseSumGenerator<B> {
fn dependencies(&self) -> Vec<Target> {
self.limbs.clone()
}
fn run_once(&self, witness: &PartialWitness<F>) -> GeneratedValues<F> {
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::<B>::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::<F, 4>::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::<Vec<_>>()
.iter(),
);
builder.assert_equal(x, y);
let data = builder.build();
let proof = data.prove(PartialWitness::new())?;
verify(proof, &data.verifier_only, &data.common)
}
}

View File

@ -35,7 +35,7 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
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::<Vec<_>>();
let mut bits = Vec::with_capacity(num_bits);

View File

@ -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<const D: usize>;
impl<const D: usize> ArithmeticExtensionGate<D> {
pub fn new<F: Extendable<D>>() -> GateRef<F, D> {
GateRef::new(ArithmeticExtensionGate)
}
pub fn wires_first_multiplicand_0() -> Range<usize> {
0..D
}
@ -74,6 +71,31 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ArithmeticExtensionGate<D>
constraints
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -234,6 +256,6 @@ mod tests {
#[test]
fn low_degree() {
test_low_degree(ArithmeticExtensionGate::<4>::new::<CrandallField>())
test_low_degree::<CrandallField, _, 4>(ArithmeticExtensionGate)
}
}

View File

@ -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<const B: usize> {
num_limbs: usize,
}
impl<const B: usize> BaseSumGate<B> {
pub fn new<F: Extendable<D>, const D: usize>(num_limbs: usize) -> GateRef<F, D> {
GateRef::new(BaseSumGate::<B> { num_limbs })
pub fn new(num_limbs: usize) -> Self {
Self { num_limbs }
}
pub const WIRE_SUM: usize = 0;
@ -57,6 +57,20 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
constraints
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -172,10 +186,11 @@ impl<F: Field, const B: usize> SimpleGenerator<F> for BaseSplitGenerator<B> {
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::<CrandallField, 4>(11))
test_low_degree::<CrandallField, _, 4>(BaseSumGate::<6>::new(11))
}
}

View File

@ -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<F: Extendable<D>, const D: usize>() -> GateRef<F, D> {
GateRef::new(ConstantGate)
}
pub const CONST_INPUT: usize = 0;
pub const WIRE_OUTPUT: usize = 0;
@ -33,6 +29,12 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ConstantGate {
vec![output - input]
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -100,6 +102,6 @@ mod tests {
#[test]
fn low_degree() {
test_low_degree(ConstantGate::get::<CrandallField, 4>())
test_low_degree::<CrandallField, _, 4>(ConstantGate)
}
}

View File

@ -139,7 +139,7 @@ impl<F: Extendable<D>, const D: usize> Debug for GateRef<F, D> {
/// A gate along with any constants used to configure it.
pub struct GateInstance<F: Extendable<D>, const D: usize> {
pub gate_type: GateRef<F, D>,
pub gate_ref: GateRef<F, D>,
pub constants: Vec<F>,
}

View File

@ -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<F: Extendable<D>, const D: usize>(gate: GateRef<F, D>) {
let gate = gate.0;
pub(crate) fn test_low_degree<F: Extendable<D>, G: Gate<F, D>, const D: usize>(gate: G) {
let rate_bits = log2_ceil(gate.degree() + 1);
let wire_ldes = random_low_degree_matrix::<F::Extension>(gate.num_wires(), rate_bits);

View File

@ -237,12 +237,12 @@ mod tests {
const D: usize = 4;
let gates = vec![
NoopGate::get::<F, D>(),
ConstantGate::get(),
ArithmeticExtensionGate::new(),
BaseSumGate::<4>::new(4),
GMiMCGate::<F, D, GMIMC_ROUNDS>::with_automatic_constants(),
InterpolationGate::new(4),
GateRef::new(NoopGate),
GateRef::new(ConstantGate),
GateRef::new(ArithmeticExtensionGate),
GateRef::new(BaseSumGate::<4>::new(4)),
GateRef::new(GMiMCGate::<F, D, GMIMC_ROUNDS>::new_automatic_constants()),
GateRef::new(InterpolationGate::new(4)),
];
let len = gates.len();

View File

@ -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<F: Extendable<D>, const D: usize, const R: usize> {
}
impl<F: Extendable<D>, const D: usize, const R: usize> GMiMCGate<F, D, R> {
pub fn with_constants(constants: Arc<[F; R]>) -> GateRef<F, D> {
let gate = GMiMCGate::<F, D, R> { constants };
GateRef::new(gate)
pub fn new(constants: Arc<[F; R]>) -> Self {
Self { constants }
}
pub fn with_automatic_constants() -> GateRef<F, D> {
pub fn new_automatic_constants() -> Self {
let constants = Arc::new(gmimc_automatic_constants::<F, R>());
Self::with_constants(constants)
Self::new(constants)
}
/// The wire index for the `i`th input to the permutation.
@ -48,22 +47,18 @@ impl<F: Extendable<D>, const D: usize, const R: usize> GMiMCGate<F, D, R> {
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<F: Extendable<D>, const D: usize, const R: usize> Gate<F, D> 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<F: Extendable<D>, const D: usize, const R: usize> Gate<F, D> for GMiMCGate<
constraints
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -131,13 +165,6 @@ impl<F: Extendable<D>, const D: usize, const R: usize> Gate<F, D> 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<F: Extendable<D>, const D: usize, const R: usize> Gate<F, D> for GMiMCGate<
}
fn num_constraints(&self) -> usize {
R + W + 2
R + W + 1
}
}
@ -221,12 +248,11 @@ impl<F: Extendable<D>, const D: usize, const R: usize> SimpleGenerator<F>
for GMiMCGenerator<F, D, R>
{
fn dependencies(&self) -> Vec<Target> {
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::<F, D, R>::wire_input(i));
}
dep_input_indices.push(GMiMCGate::<F, D, R>::WIRE_SWAP);
dep_input_indices.push(GMiMCGate::<F, D, R>::WIRE_INDEX_ACCUMULATOR_OLD);
dep_input_indices
.into_iter()
@ -240,7 +266,7 @@ impl<F: Extendable<D>, const D: usize, const R: usize> SimpleGenerator<F>
}
fn run_once(&self, witness: &PartialWitness<F>) -> GeneratedValues<F> {
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<F: Extendable<D>, const D: usize, const R: usize> SimpleGenerator<F>
}
}
// Update the index accumulator.
let old_index_acc_value = witness.get_wire(Wire {
gate: self.gate_index,
input: GMiMCGate::<F, D, R>::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::<F, D, R>::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<F, 4, R>;
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::<Vec<_>>();
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<F, 4, R>;
let gate = Gate::with_constants(constants);
let gate = GMiMCGate::<F, 4, R>::new(constants);
test_low_degree(gate)
}
@ -414,7 +407,7 @@ mod tests {
let mut pw = PartialWitness::<F>::new();
let constants = Arc::new([F::TWO; R]);
type Gate = GMiMCGate<F, 4, R>;
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) {

View File

@ -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<F: Extendable<D>, const D: usize> {
pub vec_size: usize,
pub _phantom: PhantomData<F>,
_phantom: PhantomData<F>,
}
impl<F: Extendable<D>, const D: usize> InsertionGate<F, D> {
pub fn new(vec_size: usize) -> GateRef<F, D> {
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<F: Extendable<D>, const D: usize> Gate<F, D> for InsertionGate<F, D> {
constraints
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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::<Vec<_>>();
let output_list_items = (0..=self.vec_size)
.map(|i| vars.get_local_ext(self.wires_output_list_item(i)))
.collect::<Vec<_>>();
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<F, D>,
@ -312,8 +349,7 @@ mod tests {
#[test]
fn low_degree() {
type F = CrandallField;
test_low_degree(InsertionGate::<F, 4>::new(4));
test_low_degree::<CrandallField, _, 4>(InsertionGate::new(4));
}
#[test]

View File

@ -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<F: Extendable<D>, const D: usize> {
pub num_points: usize,
pub _phantom: PhantomData<F>,
_phantom: PhantomData<F>,
}
impl<F: Extendable<D>, const D: usize> InterpolationGate<F, D> {
pub fn new(num_points: usize) -> GateRef<F, D> {
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<F: Extendable<D>, const D: usize> Gate<F, D> for InterpolationGate<F, D> {
constraints
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -297,7 +320,7 @@ mod tests {
#[test]
fn low_degree() {
type F = CrandallField;
test_low_degree(InterpolationGate::<F, 4>::new(4));
test_low_degree::<CrandallField, _, 4>(InterpolationGate::new(4));
}
#[test]
@ -313,31 +336,15 @@ mod tests {
points: Vec<F>,
eval_point: FF,
) -> Vec<FF> {
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] = <FF as FieldExtension<D>>::to_basefield_array(
&coeffs.eval(points[j].into()),
)[i];
}
}
for i in 0..D {
v[num_points * 5 + i] =
<FF as FieldExtension<D>>::to_basefield_array(&eval_point)[i];
}
for i in 0..D {
v[num_points * 5 + D + i] =
<FF as FieldExtension<D>>::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] = <FF as FieldExtension<D>>::to_basefield_array(&coeffs.coeffs[i])[j];
}
v.extend(coeffs.coeffs[i].0);
}
v.iter().map(|&x| x.into()).collect::<Vec<_>>()
}

View File

@ -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<F: Extendable<D>, const D: usize>() -> GateRef<F, D> {
GateRef::new(NoopGate)
}
}
impl<F: Extendable<D>, const D: usize> Gate<F, D> for NoopGate {
fn id(&self) -> String {
"NoopGate".into()
@ -23,6 +17,10 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for NoopGate {
Vec::new()
}
fn eval_unfiltered_base(&self, _vars: EvaluationVarsBase<F>) -> Vec<F> {
Vec::new()
}
fn eval_unfiltered_recursively(
&self,
_builder: &mut CircuitBuilder<F, D>,
@ -64,6 +62,6 @@ mod tests {
#[test]
fn low_degree() {
test_low_degree(NoopGate::get::<CrandallField, 4>())
test_low_degree::<CrandallField, _, 4>(NoopGate)
}
}

View File

@ -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<F: Extendable<D>, const D: usize>() -> GateRef<F, D> {
GateRef::new(PublicInputGate)
}
pub fn wires_public_inputs_hash() -> Range<usize> {
0..4
}
@ -32,6 +28,13 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for PublicInputGate {
.collect()
}
fn eval_unfiltered_base(&self, vars: EvaluationVarsBase<F>) -> Vec<F> {
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<F, D>,
@ -79,6 +82,6 @@ mod tests {
#[test]
fn low_degree() {
test_low_degree(PublicInputGate::get::<CrandallField, 4>())
test_low_degree::<CrandallField, _, 4>(PublicInputGate)
}
}

View File

@ -59,24 +59,21 @@ pub(crate) fn verify_merkle_proof<F: Field>(
impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
/// 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<Target>,
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::<F, D, GMIMC_ROUNDS>::with_automatic_constants());
for (&bit, &sibling) in leaf_index_bits.iter().zip(&proof.siblings) {
let gate_type = GMiMCGate::<F, D, GMIMC_ROUNDS>::new_automatic_constants();
let gate = self.add_gate(gate_type, vec![]);
let swap_wire = GMiMCGate::<F, D, GMIMC_ROUNDS>::WIRE_SWAP;
let swap_wire = Target::Wire(Wire {
@ -85,20 +82,6 @@ impl<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
});
self.generate_copy(bit, swap_wire);
let old_acc_wire = GMiMCGate::<F, D, GMIMC_ROUNDS>::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::<F, D, GMIMC_ROUNDS>::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<F: Extendable<D>, const D: usize> CircuitBuilder<F, D> {
)
}
// 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)?;

View File

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

View File

@ -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<D>, const D: usize> EvaluationVars<'a, F, D> {
}
impl<'a, F: Field> EvaluationVarsBase<'a, F> {
pub fn get_local_ext<const D: usize>(&self, wire_range: Range<usize>) -> F::Extension
where
F: Extendable<D>,
{
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()..];
}

View File

@ -170,7 +170,7 @@ impl<F: Field> PartialWitness<F> {
"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),
}