mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-08 00:33:06 +00:00
Packed evaluation for most gates (#395)
* Most gates support packed evaluation * ComparisonGate * Minor: outdated todo marker * Revert superfluous change * Post-merge fixes * Daniel comments * Minor: Markdown in comments
This commit is contained in:
parent
bbbb57caa6
commit
d4a0a8661e
@ -1,14 +1,19 @@
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::RichField;
|
use crate::field::field_types::RichField;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate which can perform a weighted multiply-add, i.e. `result = c0 x y + c1 z`. If the config
|
/// A gate which can perform a weighted multiply-add, i.e. `result = c0 x y + c1 z`. If the config
|
||||||
/// supports enough routed wires, it can support several such operations in one gate.
|
/// supports enough routed wires, it can support several such operations in one gate.
|
||||||
@ -70,21 +75,14 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for ArithmeticGate
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
let const_0 = vars.local_constants[0];
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let const_1 = vars.local_constants[1];
|
}
|
||||||
|
|
||||||
for i in 0..self.num_ops {
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
let multiplicand_0 = vars.local_wires[Self::wire_ith_multiplicand_0(i)];
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
let multiplicand_1 = vars.local_wires[Self::wire_ith_multiplicand_1(i)];
|
|
||||||
let addend = vars.local_wires[Self::wire_ith_addend(i)];
|
|
||||||
let output = vars.local_wires[Self::wire_ith_output(i)];
|
|
||||||
let computed_output = multiplicand_0 * multiplicand_1 * const_0 + addend * const_1;
|
|
||||||
|
|
||||||
yield_constr.one(output - computed_output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -152,6 +150,27 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for ArithmeticGate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: RichField + Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for ArithmeticGate {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
let const_0 = vars.local_constants[0];
|
||||||
|
let const_1 = vars.local_constants[1];
|
||||||
|
|
||||||
|
for i in 0..self.num_ops {
|
||||||
|
let multiplicand_0 = vars.local_wires[Self::wire_ith_multiplicand_0(i)];
|
||||||
|
let multiplicand_1 = vars.local_wires[Self::wire_ith_multiplicand_1(i)];
|
||||||
|
let addend = vars.local_wires[Self::wire_ith_addend(i)];
|
||||||
|
let output = vars.local_wires[Self::wire_ith_output(i)];
|
||||||
|
let computed_output = multiplicand_0 * multiplicand_1 * const_0 + addend * const_1;
|
||||||
|
|
||||||
|
yield_constr.one(output - computed_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ArithmeticBaseGenerator<F: RichField + Extendable<D>, const D: usize> {
|
struct ArithmeticBaseGenerator<F: RichField + Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -5,7 +5,9 @@ use itertools::unfold;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -13,7 +15,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate to perform a basic mul-add on 32-bit values (we assume they are range-checked beforehand).
|
/// A gate to perform a basic mul-add on 32-bit values (we assume they are range-checked beforehand).
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@ -94,8 +99,8 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for U32ArithmeticGate<F, D> {
|
|||||||
|
|
||||||
constraints.push(combined_output - computed_output);
|
constraints.push(combined_output - computed_output);
|
||||||
|
|
||||||
let mut combined_low_limbs = F::Extension::ZERO;
|
let mut combined_low_limbs = <F::Extension as Field>::ZERO;
|
||||||
let mut combined_high_limbs = F::Extension::ZERO;
|
let mut combined_high_limbs = <F::Extension as Field>::ZERO;
|
||||||
let midpoint = Self::num_limbs() / 2;
|
let midpoint = Self::num_limbs() / 2;
|
||||||
let base = F::Extension::from_canonical_u64(1u64 << Self::limb_bits());
|
let base = F::Extension::from_canonical_u64(1u64 << Self::limb_bits());
|
||||||
for j in (0..Self::num_limbs()).rev() {
|
for j in (0..Self::num_limbs()).rev() {
|
||||||
@ -121,45 +126,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for U32ArithmeticGate<F, D> {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
for i in 0..self.num_ops {
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let multiplicand_0 = vars.local_wires[self.wire_ith_multiplicand_0(i)];
|
}
|
||||||
let multiplicand_1 = vars.local_wires[self.wire_ith_multiplicand_1(i)];
|
|
||||||
let addend = vars.local_wires[self.wire_ith_addend(i)];
|
|
||||||
|
|
||||||
let computed_output = multiplicand_0 * multiplicand_1 + addend;
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
let output_low = vars.local_wires[self.wire_ith_output_low_half(i)];
|
|
||||||
let output_high = vars.local_wires[self.wire_ith_output_high_half(i)];
|
|
||||||
|
|
||||||
let base = F::from_canonical_u64(1 << 32u64);
|
|
||||||
let combined_output = output_high * base + output_low;
|
|
||||||
|
|
||||||
yield_constr.one(combined_output - computed_output);
|
|
||||||
|
|
||||||
let mut combined_low_limbs = F::ZERO;
|
|
||||||
let mut combined_high_limbs = F::ZERO;
|
|
||||||
let midpoint = Self::num_limbs() / 2;
|
|
||||||
let base = F::from_canonical_u64(1u64 << Self::limb_bits());
|
|
||||||
for j in (0..Self::num_limbs()).rev() {
|
|
||||||
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
|
||||||
let max_limb = 1 << Self::limb_bits();
|
|
||||||
let product = (0..max_limb)
|
|
||||||
.map(|x| this_limb - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
yield_constr.one(product);
|
|
||||||
|
|
||||||
if j < midpoint {
|
|
||||||
combined_low_limbs = base * combined_low_limbs + this_limb;
|
|
||||||
} else {
|
|
||||||
combined_high_limbs = base * combined_high_limbs + this_limb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield_constr.one(combined_low_limbs - output_low);
|
|
||||||
yield_constr.one(combined_high_limbs - output_high);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -257,6 +231,51 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for U32ArithmeticGate<F, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for U32ArithmeticGate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
for i in 0..self.num_ops {
|
||||||
|
let multiplicand_0 = vars.local_wires[self.wire_ith_multiplicand_0(i)];
|
||||||
|
let multiplicand_1 = vars.local_wires[self.wire_ith_multiplicand_1(i)];
|
||||||
|
let addend = vars.local_wires[self.wire_ith_addend(i)];
|
||||||
|
|
||||||
|
let computed_output = multiplicand_0 * multiplicand_1 + addend;
|
||||||
|
|
||||||
|
let output_low = vars.local_wires[self.wire_ith_output_low_half(i)];
|
||||||
|
let output_high = vars.local_wires[self.wire_ith_output_high_half(i)];
|
||||||
|
|
||||||
|
let base = F::from_canonical_u64(1 << 32u64);
|
||||||
|
let combined_output = output_high * base + output_low;
|
||||||
|
|
||||||
|
yield_constr.one(combined_output - computed_output);
|
||||||
|
|
||||||
|
let mut combined_low_limbs = P::ZERO;
|
||||||
|
let mut combined_high_limbs = P::ZERO;
|
||||||
|
let midpoint = Self::num_limbs() / 2;
|
||||||
|
let base = F::from_canonical_u64(1u64 << Self::limb_bits());
|
||||||
|
for j in (0..Self::num_limbs()).rev() {
|
||||||
|
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
||||||
|
let max_limb = 1 << Self::limb_bits();
|
||||||
|
let product = (0..max_limb)
|
||||||
|
.map(|x| this_limb - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
yield_constr.one(product);
|
||||||
|
|
||||||
|
if j < midpoint {
|
||||||
|
combined_low_limbs = combined_low_limbs * base + this_limb;
|
||||||
|
} else {
|
||||||
|
combined_high_limbs = combined_high_limbs * base + this_limb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield_constr.one(combined_low_limbs - output_low);
|
||||||
|
yield_constr.one(combined_high_limbs - output_high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct U32ArithmeticGenerator<F: Extendable<D>, const D: usize> {
|
struct U32ArithmeticGenerator<F: Extendable<D>, const D: usize> {
|
||||||
gate: U32ArithmeticGate<F, D>,
|
gate: U32ArithmeticGate<F, D>,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::{Field, PrimeField, RichField};
|
use crate::field::field_types::{Field, PrimeField, RichField};
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -11,7 +13,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
use crate::util::{bits_u64, ceil_div_usize};
|
use crate::util::{bits_u64, ceil_div_usize};
|
||||||
|
|
||||||
// TODO: replace/merge this gate with `ComparisonGate`.
|
// TODO: replace/merge this gate with `ComparisonGate`.
|
||||||
@ -109,7 +114,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for AssertLessThan
|
|||||||
|
|
||||||
let chunk_size = 1 << self.chunk_bits();
|
let chunk_size = 1 << self.chunk_bits();
|
||||||
|
|
||||||
let mut most_significant_diff_so_far = F::Extension::ZERO;
|
let mut most_significant_diff_so_far = <F::Extension as Field>::ZERO;
|
||||||
|
|
||||||
for i in 0..self.num_chunks {
|
for i in 0..self.num_chunks {
|
||||||
// Range-check the chunks to be less than `chunk_size`.
|
// Range-check the chunks to be less than `chunk_size`.
|
||||||
@ -127,14 +132,15 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for AssertLessThan
|
|||||||
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
||||||
|
|
||||||
// Two constraints to assert that `chunks_equal` is valid.
|
// Two constraints to assert that `chunks_equal` is valid.
|
||||||
constraints.push(difference * equality_dummy - (F::Extension::ONE - chunks_equal));
|
constraints
|
||||||
|
.push(difference * equality_dummy - (<F::Extension as Field>::ONE - chunks_equal));
|
||||||
constraints.push(chunks_equal * difference);
|
constraints.push(chunks_equal * difference);
|
||||||
|
|
||||||
// Update `most_significant_diff_so_far`.
|
// Update `most_significant_diff_so_far`.
|
||||||
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
||||||
constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
||||||
most_significant_diff_so_far =
|
most_significant_diff_so_far =
|
||||||
intermediate_value + (F::Extension::ONE - chunks_equal) * difference;
|
intermediate_value + (<F::Extension as Field>::ONE - chunks_equal) * difference;
|
||||||
}
|
}
|
||||||
|
|
||||||
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
||||||
@ -151,70 +157,14 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for AssertLessThan
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
let first_input = vars.local_wires[self.wire_first_input()];
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let second_input = vars.local_wires[self.wire_second_input()];
|
}
|
||||||
|
|
||||||
// Get chunks and assert that they match
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
let first_chunks: Vec<F> = (0..self.num_chunks)
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
.map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
|
|
||||||
.collect();
|
|
||||||
let second_chunks: Vec<F> = (0..self.num_chunks)
|
|
||||||
.map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let first_chunks_combined = reduce_with_powers(
|
|
||||||
&first_chunks,
|
|
||||||
F::from_canonical_usize(1 << self.chunk_bits()),
|
|
||||||
);
|
|
||||||
let second_chunks_combined = reduce_with_powers(
|
|
||||||
&second_chunks,
|
|
||||||
F::from_canonical_usize(1 << self.chunk_bits()),
|
|
||||||
);
|
|
||||||
|
|
||||||
yield_constr.one(first_chunks_combined - first_input);
|
|
||||||
yield_constr.one(second_chunks_combined - second_input);
|
|
||||||
|
|
||||||
let chunk_size = 1 << self.chunk_bits();
|
|
||||||
|
|
||||||
let mut most_significant_diff_so_far = F::ZERO;
|
|
||||||
|
|
||||||
for i in 0..self.num_chunks {
|
|
||||||
// Range-check the chunks to be less than `chunk_size`.
|
|
||||||
let first_product = (0..chunk_size)
|
|
||||||
.map(|x| first_chunks[i] - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
let second_product = (0..chunk_size)
|
|
||||||
.map(|x| second_chunks[i] - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
yield_constr.one(first_product);
|
|
||||||
yield_constr.one(second_product);
|
|
||||||
|
|
||||||
let difference = second_chunks[i] - first_chunks[i];
|
|
||||||
let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
|
|
||||||
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
|
||||||
|
|
||||||
// Two constraints to assert that `chunks_equal` is valid.
|
|
||||||
yield_constr.one(difference * equality_dummy - (F::ONE - chunks_equal));
|
|
||||||
yield_constr.one(chunks_equal * difference);
|
|
||||||
|
|
||||||
// Update `most_significant_diff_so_far`.
|
|
||||||
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
|
||||||
yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
|
||||||
most_significant_diff_so_far =
|
|
||||||
intermediate_value + (F::ONE - chunks_equal) * difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
|
||||||
yield_constr.one(most_significant_diff - most_significant_diff_so_far);
|
|
||||||
|
|
||||||
// Range check `most_significant_diff` to be less than `chunk_size`.
|
|
||||||
let product = (0..chunk_size)
|
|
||||||
.map(|x| most_significant_diff - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
yield_constr.one(product);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -329,6 +279,78 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for AssertLessThan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: RichField + Extendable<D>, const D: usize> PackedEvaluableBase<F, D>
|
||||||
|
for AssertLessThanGate<F, D>
|
||||||
|
{
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
let first_input = vars.local_wires[self.wire_first_input()];
|
||||||
|
let second_input = vars.local_wires[self.wire_second_input()];
|
||||||
|
|
||||||
|
// Get chunks and assert that they match
|
||||||
|
let first_chunks: Vec<_> = (0..self.num_chunks)
|
||||||
|
.map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
|
||||||
|
.collect();
|
||||||
|
let second_chunks: Vec<_> = (0..self.num_chunks)
|
||||||
|
.map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let first_chunks_combined = reduce_with_powers(
|
||||||
|
&first_chunks,
|
||||||
|
F::from_canonical_usize(1 << self.chunk_bits()),
|
||||||
|
);
|
||||||
|
let second_chunks_combined = reduce_with_powers(
|
||||||
|
&second_chunks,
|
||||||
|
F::from_canonical_usize(1 << self.chunk_bits()),
|
||||||
|
);
|
||||||
|
|
||||||
|
yield_constr.one(first_chunks_combined - first_input);
|
||||||
|
yield_constr.one(second_chunks_combined - second_input);
|
||||||
|
|
||||||
|
let chunk_size = 1 << self.chunk_bits();
|
||||||
|
|
||||||
|
let mut most_significant_diff_so_far = P::ZERO;
|
||||||
|
|
||||||
|
for i in 0..self.num_chunks {
|
||||||
|
// Range-check the chunks to be less than `chunk_size`.
|
||||||
|
let first_product = (0..chunk_size)
|
||||||
|
.map(|x| first_chunks[i] - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
let second_product = (0..chunk_size)
|
||||||
|
.map(|x| second_chunks[i] - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
yield_constr.one(first_product);
|
||||||
|
yield_constr.one(second_product);
|
||||||
|
|
||||||
|
let difference = second_chunks[i] - first_chunks[i];
|
||||||
|
let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
|
||||||
|
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
||||||
|
|
||||||
|
// Two constraints to assert that `chunks_equal` is valid.
|
||||||
|
yield_constr.one(difference * equality_dummy - (P::ONE - chunks_equal));
|
||||||
|
yield_constr.one(chunks_equal * difference);
|
||||||
|
|
||||||
|
// Update `most_significant_diff_so_far`.
|
||||||
|
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
||||||
|
yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
||||||
|
most_significant_diff_so_far =
|
||||||
|
intermediate_value + (P::ONE - chunks_equal) * difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
||||||
|
yield_constr.one(most_significant_diff - most_significant_diff_so_far);
|
||||||
|
|
||||||
|
// Range check `most_significant_diff` to be less than `chunk_size`.
|
||||||
|
let product = (0..chunk_size)
|
||||||
|
.map(|x| most_significant_diff - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
yield_constr.one(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct AssertLessThanGenerator<F: RichField + Extendable<D>, const D: usize> {
|
struct AssertLessThanGenerator<F: RichField + Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::ops::Range;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::{Field, PrimeField, RichField};
|
use crate::field::field_types::{Field, PrimeField, RichField};
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -11,7 +13,10 @@ use crate::iop::witness::{PartitionWitness, Witness};
|
|||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate which can decompose a number into base B little-endian limbs.
|
/// A gate which can decompose a number into base B little-endian limbs.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@ -60,21 +65,14 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
let sum = vars.local_wires[Self::WIRE_SUM];
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let limbs = vars.local_wires.view(self.limbs());
|
}
|
||||||
let computed_sum = reduce_with_powers(limbs, F::from_canonical_usize(B));
|
|
||||||
|
|
||||||
yield_constr.one(computed_sum - sum);
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
let constraints_iter = limbs.iter().map(|&limb| {
|
|
||||||
(0..B)
|
|
||||||
.map(|i| unsafe { limb.sub_canonical_u64(i as u64) })
|
|
||||||
.product::<F>()
|
|
||||||
});
|
|
||||||
yield_constr.many(constraints_iter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -136,6 +134,29 @@ impl<F: Extendable<D>, const D: usize, const B: usize> Gate<F, D> for BaseSumGat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize, const B: usize> PackedEvaluableBase<F, D>
|
||||||
|
for BaseSumGate<B>
|
||||||
|
{
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
let sum = vars.local_wires[Self::WIRE_SUM];
|
||||||
|
let limbs = vars.local_wires.view(self.limbs());
|
||||||
|
let computed_sum = reduce_with_powers(limbs, F::from_canonical_usize(B));
|
||||||
|
|
||||||
|
yield_constr.one(computed_sum - sum);
|
||||||
|
|
||||||
|
let constraints_iter = limbs.iter().map(|&limb| {
|
||||||
|
(0..B)
|
||||||
|
.map(|i| limb - F::from_canonical_usize(i))
|
||||||
|
.product::<P>()
|
||||||
|
});
|
||||||
|
yield_constr.many(constraints_iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BaseSplitGenerator<const B: usize> {
|
pub struct BaseSplitGenerator<const B: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::{Field, PrimeField};
|
use crate::field::field_types::{Field, PrimeField};
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -11,7 +13,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
use crate::plonk::plonk_common::{reduce_with_powers, reduce_with_powers_ext_recursive};
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
use crate::util::{bits_u64, ceil_div_usize};
|
use crate::util::{bits_u64, ceil_div_usize};
|
||||||
|
|
||||||
/// A gate for checking that one value is less than or equal to another.
|
/// A gate for checking that one value is less than or equal to another.
|
||||||
@ -116,7 +121,7 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
|
|||||||
|
|
||||||
let chunk_size = 1 << self.chunk_bits();
|
let chunk_size = 1 << self.chunk_bits();
|
||||||
|
|
||||||
let mut most_significant_diff_so_far = F::Extension::ZERO;
|
let mut most_significant_diff_so_far = <F::Extension as Field>::ZERO;
|
||||||
|
|
||||||
for i in 0..self.num_chunks {
|
for i in 0..self.num_chunks {
|
||||||
// Range-check the chunks to be less than `chunk_size`.
|
// Range-check the chunks to be less than `chunk_size`.
|
||||||
@ -134,14 +139,15 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
|
|||||||
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
||||||
|
|
||||||
// Two constraints to assert that `chunks_equal` is valid.
|
// Two constraints to assert that `chunks_equal` is valid.
|
||||||
constraints.push(difference * equality_dummy - (F::Extension::ONE - chunks_equal));
|
constraints
|
||||||
|
.push(difference * equality_dummy - (<F::Extension as Field>::ONE - chunks_equal));
|
||||||
constraints.push(chunks_equal * difference);
|
constraints.push(chunks_equal * difference);
|
||||||
|
|
||||||
// Update `most_significant_diff_so_far`.
|
// Update `most_significant_diff_so_far`.
|
||||||
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
||||||
constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
constraints.push(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
||||||
most_significant_diff_so_far =
|
most_significant_diff_so_far =
|
||||||
intermediate_value + (F::Extension::ONE - chunks_equal) * difference;
|
intermediate_value + (<F::Extension as Field>::ONE - chunks_equal) * difference;
|
||||||
}
|
}
|
||||||
|
|
||||||
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
||||||
@ -153,7 +159,7 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
|
|||||||
|
|
||||||
// Range-check the bits.
|
// Range-check the bits.
|
||||||
for &bit in &most_significant_diff_bits {
|
for &bit in &most_significant_diff_bits {
|
||||||
constraints.push(bit * (F::Extension::ONE - bit));
|
constraints.push(bit * (<F::Extension as Field>::ONE - bit));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::Extension::TWO);
|
let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::Extension::TWO);
|
||||||
@ -169,81 +175,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
let first_input = vars.local_wires[self.wire_first_input()];
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let second_input = vars.local_wires[self.wire_second_input()];
|
}
|
||||||
|
|
||||||
// Get chunks and assert that they match
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
let first_chunks: Vec<F> = (0..self.num_chunks)
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
.map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
|
|
||||||
.collect();
|
|
||||||
let second_chunks: Vec<F> = (0..self.num_chunks)
|
|
||||||
.map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let first_chunks_combined = reduce_with_powers(
|
|
||||||
&first_chunks,
|
|
||||||
F::from_canonical_usize(1 << self.chunk_bits()),
|
|
||||||
);
|
|
||||||
let second_chunks_combined = reduce_with_powers(
|
|
||||||
&second_chunks,
|
|
||||||
F::from_canonical_usize(1 << self.chunk_bits()),
|
|
||||||
);
|
|
||||||
|
|
||||||
yield_constr.one(first_chunks_combined - first_input);
|
|
||||||
yield_constr.one(second_chunks_combined - second_input);
|
|
||||||
|
|
||||||
let chunk_size = 1 << self.chunk_bits();
|
|
||||||
|
|
||||||
let mut most_significant_diff_so_far = F::ZERO;
|
|
||||||
|
|
||||||
for i in 0..self.num_chunks {
|
|
||||||
// Range-check the chunks to be less than `chunk_size`.
|
|
||||||
let first_product: F = (0..chunk_size)
|
|
||||||
.map(|x| first_chunks[i] - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
let second_product: F = (0..chunk_size)
|
|
||||||
.map(|x| second_chunks[i] - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
yield_constr.one(first_product);
|
|
||||||
yield_constr.one(second_product);
|
|
||||||
|
|
||||||
let difference = second_chunks[i] - first_chunks[i];
|
|
||||||
let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
|
|
||||||
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
|
||||||
|
|
||||||
// Two constraints to assert that `chunks_equal` is valid.
|
|
||||||
yield_constr.one(difference * equality_dummy - (F::ONE - chunks_equal));
|
|
||||||
yield_constr.one(chunks_equal * difference);
|
|
||||||
|
|
||||||
// Update `most_significant_diff_so_far`.
|
|
||||||
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
|
||||||
yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
|
||||||
most_significant_diff_so_far =
|
|
||||||
intermediate_value + (F::ONE - chunks_equal) * difference;
|
|
||||||
}
|
|
||||||
|
|
||||||
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
|
||||||
yield_constr.one(most_significant_diff - most_significant_diff_so_far);
|
|
||||||
|
|
||||||
let most_significant_diff_bits: Vec<F> = (0..self.chunk_bits() + 1)
|
|
||||||
.map(|i| vars.local_wires[self.wire_most_significant_diff_bit(i)])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Range-check the bits.
|
|
||||||
for &bit in &most_significant_diff_bits {
|
|
||||||
yield_constr.one(bit * (F::ONE - bit));
|
|
||||||
}
|
|
||||||
|
|
||||||
let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::TWO);
|
|
||||||
let two_n = F::from_canonical_u64(1 << self.chunk_bits());
|
|
||||||
yield_constr.one((two_n + most_significant_diff) - bits_combined);
|
|
||||||
|
|
||||||
// Iff first <= second, the top (n + 1st) bit of (2^n - 1 + most_significant_diff) will be 1.
|
|
||||||
let result_bool = vars.local_wires[self.wire_result_bool()];
|
|
||||||
yield_constr.one(result_bool - most_significant_diff_bits[self.chunk_bits()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -373,6 +312,87 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ComparisonGate<F, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for ComparisonGate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
let first_input = vars.local_wires[self.wire_first_input()];
|
||||||
|
let second_input = vars.local_wires[self.wire_second_input()];
|
||||||
|
|
||||||
|
// Get chunks and assert that they match
|
||||||
|
let first_chunks: Vec<_> = (0..self.num_chunks)
|
||||||
|
.map(|i| vars.local_wires[self.wire_first_chunk_val(i)])
|
||||||
|
.collect();
|
||||||
|
let second_chunks: Vec<_> = (0..self.num_chunks)
|
||||||
|
.map(|i| vars.local_wires[self.wire_second_chunk_val(i)])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let first_chunks_combined = reduce_with_powers(
|
||||||
|
&first_chunks,
|
||||||
|
F::from_canonical_usize(1 << self.chunk_bits()),
|
||||||
|
);
|
||||||
|
let second_chunks_combined = reduce_with_powers(
|
||||||
|
&second_chunks,
|
||||||
|
F::from_canonical_usize(1 << self.chunk_bits()),
|
||||||
|
);
|
||||||
|
|
||||||
|
yield_constr.one(first_chunks_combined - first_input);
|
||||||
|
yield_constr.one(second_chunks_combined - second_input);
|
||||||
|
|
||||||
|
let chunk_size = 1 << self.chunk_bits();
|
||||||
|
|
||||||
|
let mut most_significant_diff_so_far = P::ZERO;
|
||||||
|
|
||||||
|
for i in 0..self.num_chunks {
|
||||||
|
// Range-check the chunks to be less than `chunk_size`.
|
||||||
|
let first_product: P = (0..chunk_size)
|
||||||
|
.map(|x| first_chunks[i] - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
let second_product: P = (0..chunk_size)
|
||||||
|
.map(|x| second_chunks[i] - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
yield_constr.one(first_product);
|
||||||
|
yield_constr.one(second_product);
|
||||||
|
|
||||||
|
let difference = second_chunks[i] - first_chunks[i];
|
||||||
|
let equality_dummy = vars.local_wires[self.wire_equality_dummy(i)];
|
||||||
|
let chunks_equal = vars.local_wires[self.wire_chunks_equal(i)];
|
||||||
|
|
||||||
|
// Two constraints to assert that `chunks_equal` is valid.
|
||||||
|
yield_constr.one(difference * equality_dummy - (P::ONE - chunks_equal));
|
||||||
|
yield_constr.one(chunks_equal * difference);
|
||||||
|
|
||||||
|
// Update `most_significant_diff_so_far`.
|
||||||
|
let intermediate_value = vars.local_wires[self.wire_intermediate_value(i)];
|
||||||
|
yield_constr.one(intermediate_value - chunks_equal * most_significant_diff_so_far);
|
||||||
|
most_significant_diff_so_far =
|
||||||
|
intermediate_value + (P::ONE - chunks_equal) * difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
let most_significant_diff = vars.local_wires[self.wire_most_significant_diff()];
|
||||||
|
yield_constr.one(most_significant_diff - most_significant_diff_so_far);
|
||||||
|
|
||||||
|
let most_significant_diff_bits: Vec<_> = (0..self.chunk_bits() + 1)
|
||||||
|
.map(|i| vars.local_wires[self.wire_most_significant_diff_bit(i)])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Range-check the bits.
|
||||||
|
for &bit in &most_significant_diff_bits {
|
||||||
|
yield_constr.one(bit * (P::ONE - bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bits_combined = reduce_with_powers(&most_significant_diff_bits, F::TWO);
|
||||||
|
let two_n = F::from_canonical_u64(1 << self.chunk_bits());
|
||||||
|
yield_constr.one((most_significant_diff + two_n) - bits_combined);
|
||||||
|
|
||||||
|
// Iff first <= second, the top (n + 1st) bit of (2^n - 1 + most_significant_diff) will be 1.
|
||||||
|
let result_bool = vars.local_wires[self.wire_result_bool()];
|
||||||
|
yield_constr.one(result_bool - most_significant_diff_bits[self.chunk_bits()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ComparisonGenerator<F: Extendable<D>, const D: usize> {
|
struct ComparisonGenerator<F: Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,14 +3,19 @@ use std::ops::Range;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
use crate::iop::wire::Wire;
|
use crate::iop::wire::Wire;
|
||||||
use crate::iop::witness::PartitionWitness;
|
use crate::iop::witness::PartitionWitness;
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate which takes a single constant parameter and outputs that value.
|
/// A gate which takes a single constant parameter and outputs that value.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@ -42,14 +47,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ConstantGate {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
yield_constr.many(
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
self.consts_inputs()
|
}
|
||||||
.zip(self.wires_outputs())
|
|
||||||
.map(|(con, out)| vars.local_constants[con] - vars.local_wires[out]),
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
);
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -95,6 +100,20 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ConstantGate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for ConstantGate {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
yield_constr.many(
|
||||||
|
self.consts_inputs()
|
||||||
|
.zip(self.wires_outputs())
|
||||||
|
.map(|(con, out)| vars.local_constants[con] - vars.local_wires[out]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ConstantGenerator<F: Field> {
|
struct ConstantGenerator<F: Field> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -11,7 +13,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate for raising a value to a power.
|
/// A gate for raising a value to a power.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -81,15 +86,15 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ExponentiationGate<F, D> {
|
|||||||
|
|
||||||
for i in 0..self.num_power_bits {
|
for i in 0..self.num_power_bits {
|
||||||
let prev_intermediate_value = if i == 0 {
|
let prev_intermediate_value = if i == 0 {
|
||||||
F::Extension::ONE
|
<F::Extension as Field>::ONE
|
||||||
} else {
|
} else {
|
||||||
intermediate_values[i - 1].square()
|
<F::Extension as Field>::square(&intermediate_values[i - 1])
|
||||||
};
|
};
|
||||||
|
|
||||||
// power_bits is in LE order, but we accumulate in BE order.
|
// power_bits is in LE order, but we accumulate in BE order.
|
||||||
let cur_bit = power_bits[self.num_power_bits - i - 1];
|
let cur_bit = power_bits[self.num_power_bits - i - 1];
|
||||||
|
|
||||||
let not_cur_bit = F::Extension::ONE - cur_bit;
|
let not_cur_bit = <F::Extension as Field>::ONE - cur_bit;
|
||||||
let computed_intermediate_value =
|
let computed_intermediate_value =
|
||||||
prev_intermediate_value * (cur_bit * base + not_cur_bit);
|
prev_intermediate_value * (cur_bit * base + not_cur_bit);
|
||||||
constraints.push(computed_intermediate_value - intermediate_values[i]);
|
constraints.push(computed_intermediate_value - intermediate_values[i]);
|
||||||
@ -102,37 +107,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ExponentiationGate<F, D> {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
let base = vars.local_wires[self.wire_base()];
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
|
}
|
||||||
|
|
||||||
let power_bits: Vec<_> = (0..self.num_power_bits)
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
.map(|i| vars.local_wires[self.wire_power_bit(i)])
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
.collect();
|
|
||||||
let intermediate_values: Vec<_> = (0..self.num_power_bits)
|
|
||||||
.map(|i| vars.local_wires[self.wire_intermediate_value(i)])
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let output = vars.local_wires[self.wire_output()];
|
|
||||||
|
|
||||||
for i in 0..self.num_power_bits {
|
|
||||||
let prev_intermediate_value = if i == 0 {
|
|
||||||
F::ONE
|
|
||||||
} else {
|
|
||||||
intermediate_values[i - 1].square()
|
|
||||||
};
|
|
||||||
|
|
||||||
// power_bits is in LE order, but we accumulate in BE order.
|
|
||||||
let cur_bit = power_bits[self.num_power_bits - i - 1];
|
|
||||||
|
|
||||||
let not_cur_bit = F::ONE - cur_bit;
|
|
||||||
let computed_intermediate_value =
|
|
||||||
prev_intermediate_value * (cur_bit * base + not_cur_bit);
|
|
||||||
yield_constr.one(computed_intermediate_value - intermediate_values[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield_constr.one(output - intermediate_values[self.num_power_bits - 1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -205,6 +187,43 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for ExponentiationGate<F, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for ExponentiationGate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
let base = vars.local_wires[self.wire_base()];
|
||||||
|
|
||||||
|
let power_bits: Vec<_> = (0..self.num_power_bits)
|
||||||
|
.map(|i| vars.local_wires[self.wire_power_bit(i)])
|
||||||
|
.collect();
|
||||||
|
let intermediate_values: Vec<_> = (0..self.num_power_bits)
|
||||||
|
.map(|i| vars.local_wires[self.wire_intermediate_value(i)])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let output = vars.local_wires[self.wire_output()];
|
||||||
|
|
||||||
|
for i in 0..self.num_power_bits {
|
||||||
|
let prev_intermediate_value = if i == 0 {
|
||||||
|
P::ONE
|
||||||
|
} else {
|
||||||
|
intermediate_values[i - 1].square()
|
||||||
|
};
|
||||||
|
|
||||||
|
// power_bits is in LE order, but we accumulate in BE order.
|
||||||
|
let cur_bit = power_bits[self.num_power_bits - i - 1];
|
||||||
|
|
||||||
|
let not_cur_bit = P::ONE - cur_bit;
|
||||||
|
let computed_intermediate_value =
|
||||||
|
prev_intermediate_value * (cur_bit * base + not_cur_bit);
|
||||||
|
yield_constr.one(computed_intermediate_value - intermediate_values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield_constr.one(output - intermediate_values[self.num_power_bits - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ExponentiationGenerator<F: Extendable<D>, const D: usize> {
|
struct ExponentiationGenerator<F: Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::hash::gmimc;
|
use crate::hash::gmimc;
|
||||||
use crate::hash::gmimc::GMiMC;
|
use crate::hash::gmimc::GMiMC;
|
||||||
@ -12,7 +14,10 @@ use crate::iop::target::Target;
|
|||||||
use crate::iop::wire::Wire;
|
use crate::iop::wire::Wire;
|
||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// Evaluates a full GMiMC permutation with 12 state elements.
|
/// Evaluates a full GMiMC permutation with 12 state elements.
|
||||||
///
|
///
|
||||||
@ -68,7 +73,7 @@ impl<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> Gate<F
|
|||||||
|
|
||||||
// Assert that `swap` is binary.
|
// Assert that `swap` is binary.
|
||||||
let swap = vars.local_wires[Self::WIRE_SWAP];
|
let swap = vars.local_wires[Self::WIRE_SWAP];
|
||||||
constraints.push(swap * (swap - F::Extension::ONE));
|
constraints.push(swap * (swap - <F::Extension as Field>::ONE));
|
||||||
|
|
||||||
let mut state = Vec::with_capacity(12);
|
let mut state = Vec::with_capacity(12);
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
@ -87,7 +92,7 @@ impl<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> Gate<F
|
|||||||
|
|
||||||
// Value that is implicitly added to each element.
|
// Value that is implicitly added to each element.
|
||||||
// See https://affine.group/2020/02/starkware-challenge
|
// See https://affine.group/2020/02/starkware-challenge
|
||||||
let mut addition_buffer = F::Extension::ZERO;
|
let mut addition_buffer = <F::Extension as Field>::ZERO;
|
||||||
|
|
||||||
for r in 0..gmimc::NUM_ROUNDS {
|
for r in 0..gmimc::NUM_ROUNDS {
|
||||||
let active = r % WIDTH;
|
let active = r % WIDTH;
|
||||||
@ -110,47 +115,14 @@ impl<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> Gate<F
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
// Assert that `swap` is binary.
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let swap = vars.local_wires[Self::WIRE_SWAP];
|
}
|
||||||
yield_constr.one(swap * swap.sub_one());
|
|
||||||
|
|
||||||
let mut state = Vec::with_capacity(12);
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
for i in 0..4 {
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
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..gmimc::NUM_ROUNDS {
|
|
||||||
let active = r % WIDTH;
|
|
||||||
let constant = F::from_canonical_u64(<F as GMiMC<WIDTH>>::ROUND_CONSTANTS[r]);
|
|
||||||
let cubing_input = state[active] + addition_buffer + constant;
|
|
||||||
let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)];
|
|
||||||
yield_constr.one(cubing_input - cubing_input_wire);
|
|
||||||
let f = cubing_input_wire.cube();
|
|
||||||
addition_buffer += f;
|
|
||||||
state[active] -= f;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..WIDTH {
|
|
||||||
state[i] += addition_buffer;
|
|
||||||
yield_constr.one(state[i] - vars.local_wires[Self::wire_output(i)]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -236,6 +208,55 @@ impl<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> Gate<F
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> PackedEvaluableBase<F, D>
|
||||||
|
for GMiMCGate<F, D, WIDTH>
|
||||||
|
{
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
// Assert that `swap` is binary.
|
||||||
|
let swap = vars.local_wires[Self::WIRE_SWAP];
|
||||||
|
yield_constr.one(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 = P::ZERO;
|
||||||
|
|
||||||
|
for r in 0..gmimc::NUM_ROUNDS {
|
||||||
|
let active = r % WIDTH;
|
||||||
|
let constant = F::from_canonical_u64(<F as GMiMC<WIDTH>>::ROUND_CONSTANTS[r]);
|
||||||
|
let cubing_input = state[active] + addition_buffer + constant;
|
||||||
|
let cubing_input_wire = vars.local_wires[Self::wire_cubing_input(r)];
|
||||||
|
yield_constr.one(cubing_input - cubing_input_wire);
|
||||||
|
let f = cubing_input_wire.square() * cubing_input_wire;
|
||||||
|
addition_buffer += f;
|
||||||
|
state[active] -= f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..WIDTH {
|
||||||
|
state[i] += addition_buffer;
|
||||||
|
yield_constr.one(state[i] - vars.local_wires[Self::wire_output(i)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct GMiMCGenerator<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> {
|
struct GMiMCGenerator<F: Extendable<D> + GMiMC<WIDTH>, const D: usize, const WIDTH: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ pub mod interpolation;
|
|||||||
pub mod low_degree_interpolation;
|
pub mod low_degree_interpolation;
|
||||||
pub mod multiplication_extension;
|
pub mod multiplication_extension;
|
||||||
pub mod noop;
|
pub mod noop;
|
||||||
|
mod packed_util;
|
||||||
pub mod poseidon;
|
pub mod poseidon;
|
||||||
pub(crate) mod poseidon_mds;
|
pub(crate) mod poseidon_mds;
|
||||||
pub(crate) mod public_input;
|
pub(crate) mod public_input;
|
||||||
|
|||||||
39
src/gates/packed_util.rs
Normal file
39
src/gates/packed_util.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::field::extension_field::Extendable;
|
||||||
|
use crate::field::packable::Packable;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
|
use crate::plonk::vars::{EvaluationVarsBaseBatch, EvaluationVarsBasePacked};
|
||||||
|
|
||||||
|
pub trait PackedEvaluableBase<F: Extendable<D>, const D: usize>: Gate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars_base: EvaluationVarsBasePacked<P>,
|
||||||
|
yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Evaluates entire batch of points. Returns a matrix of constraints. Constraint `j` for point
|
||||||
|
/// `i` is at `index j * batch_size + i`.
|
||||||
|
fn eval_unfiltered_base_batch_packed(&self, vars_batch: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
|
let mut res = vec![F::ZERO; vars_batch.len() * self.num_constraints()];
|
||||||
|
let (vars_packed_iter, vars_leftovers_iter) = vars_batch.pack::<<F as Packable>::Packing>();
|
||||||
|
let leftovers_start = vars_batch.len() - vars_leftovers_iter.len();
|
||||||
|
for (i, vars_packed) in vars_packed_iter.enumerate() {
|
||||||
|
self.eval_unfiltered_base_packed(
|
||||||
|
vars_packed,
|
||||||
|
StridedConstraintConsumer::new(
|
||||||
|
&mut res[..],
|
||||||
|
vars_batch.len(),
|
||||||
|
<F as Packable>::Packing::WIDTH * i,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (i, vars_leftovers) in vars_leftovers_iter.enumerate() {
|
||||||
|
self.eval_unfiltered_base_packed(
|
||||||
|
vars_leftovers,
|
||||||
|
StridedConstraintConsumer::new(&mut res[..], vars_batch.len(), leftovers_start + i),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,11 +2,16 @@ use std::ops::Range;
|
|||||||
|
|
||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::WitnessGenerator;
|
use crate::iop::generator::WitnessGenerator;
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate whose first four wires will be equal to a hash of public inputs.
|
/// A gate whose first four wires will be equal to a hash of public inputs.
|
||||||
pub struct PublicInputGate;
|
pub struct PublicInputGate;
|
||||||
@ -31,14 +36,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for PublicInputGate {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
yield_constr.many(
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
Self::wires_public_inputs_hash()
|
}
|
||||||
.zip(vars.public_inputs_hash.elements)
|
|
||||||
.map(|(wire, hash_part)| vars.local_wires[wire] - hash_part),
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
)
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -80,6 +85,20 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for PublicInputGate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for PublicInputGate {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
yield_constr.many(
|
||||||
|
Self::wires_public_inputs_hash()
|
||||||
|
.zip(vars.public_inputs_hash.elements)
|
||||||
|
.map(|(wire, hash_part)| vars.local_wires[wire] - hash_part),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::field::goldilocks_field::GoldilocksField;
|
use crate::field::goldilocks_field::GoldilocksField;
|
||||||
|
|||||||
@ -5,7 +5,9 @@ use itertools::Itertools;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -13,7 +15,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate for checking that a particular element of a list matches a given value.
|
/// A gate for checking that a particular element of a list matches a given value.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@ -99,14 +104,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for RandomAccessGate<F, D> {
|
|||||||
|
|
||||||
// Assert that each bit wire value is indeed boolean.
|
// Assert that each bit wire value is indeed boolean.
|
||||||
for &b in &bits {
|
for &b in &bits {
|
||||||
constraints.push(b * (b - F::Extension::ONE));
|
constraints.push(b * (b - <F::Extension as Field>::ONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the binary decomposition was correct.
|
// Assert that the binary decomposition was correct.
|
||||||
let reconstructed_index = bits
|
let reconstructed_index = bits
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.fold(F::Extension::ZERO, |acc, &b| acc.double() + b);
|
.fold(<F::Extension as Field>::ZERO, |acc, &b| acc.double() + b);
|
||||||
constraints.push(reconstructed_index - access_index);
|
constraints.push(reconstructed_index - access_index);
|
||||||
|
|
||||||
// Repeatedly fold the list, selecting the left or right item from each pair based on
|
// Repeatedly fold the list, selecting the left or right item from each pair based on
|
||||||
@ -128,41 +133,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for RandomAccessGate<F, D> {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
for copy in 0..self.num_copies {
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let access_index = vars.local_wires[self.wire_access_index(copy)];
|
}
|
||||||
let mut list_items = (0..self.vec_size())
|
|
||||||
.map(|i| vars.local_wires[self.wire_list_item(i, copy)])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let claimed_element = vars.local_wires[self.wire_claimed_element(copy)];
|
|
||||||
let bits = (0..self.bits)
|
|
||||||
.map(|i| vars.local_wires[self.wire_bit(i, copy)])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Assert that each bit wire value is indeed boolean.
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
for &b in &bits {
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
yield_constr.one(b * (b - F::ONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the binary decomposition was correct.
|
|
||||||
let reconstructed_index = bits.iter().rev().fold(F::ZERO, |acc, &b| acc.double() + b);
|
|
||||||
yield_constr.one(reconstructed_index - access_index);
|
|
||||||
|
|
||||||
// Repeatedly fold the list, selecting the left or right item from each pair based on
|
|
||||||
// the corresponding bit.
|
|
||||||
for b in bits {
|
|
||||||
list_items = list_items
|
|
||||||
.iter()
|
|
||||||
.tuples()
|
|
||||||
.map(|(&x, &y)| x + b * (y - x))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert_eq!(list_items.len(), 1);
|
|
||||||
yield_constr.one(list_items[0] - claimed_element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -251,6 +229,47 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for RandomAccessGate<F, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for RandomAccessGate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
for copy in 0..self.num_copies {
|
||||||
|
let access_index = vars.local_wires[self.wire_access_index(copy)];
|
||||||
|
let mut list_items = (0..self.vec_size())
|
||||||
|
.map(|i| vars.local_wires[self.wire_list_item(i, copy)])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let claimed_element = vars.local_wires[self.wire_claimed_element(copy)];
|
||||||
|
let bits = (0..self.bits)
|
||||||
|
.map(|i| vars.local_wires[self.wire_bit(i, copy)])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Assert that each bit wire value is indeed boolean.
|
||||||
|
for &b in &bits {
|
||||||
|
yield_constr.one(b * (b - F::ONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the binary decomposition was correct.
|
||||||
|
let reconstructed_index = bits.iter().rev().fold(P::ZERO, |acc, &b| acc + acc + b);
|
||||||
|
yield_constr.one(reconstructed_index - access_index);
|
||||||
|
|
||||||
|
// Repeatedly fold the list, selecting the left or right item from each pair based on
|
||||||
|
// the corresponding bit.
|
||||||
|
for b in bits {
|
||||||
|
list_items = list_items
|
||||||
|
.iter()
|
||||||
|
.tuples()
|
||||||
|
.map(|(&x, &y)| x + b * (y - x))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert_eq!(list_items.len(), 1);
|
||||||
|
yield_constr.one(list_items[0] - claimed_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RandomAccessGenerator<F: Extendable<D>, const D: usize> {
|
struct RandomAccessGenerator<F: Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::marker::PhantomData;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::{Field, RichField};
|
use crate::field::field_types::{Field, RichField};
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -11,7 +13,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate to perform a subtraction on 32-bit limbs: given `x`, `y`, and `borrow`, it returns
|
/// A gate to perform a subtraction on 32-bit limbs: given `x`, `y`, and `borrow`, it returns
|
||||||
/// the result `x - y - borrow` and, if this underflows, a new `borrow`. Inputs are not range-checked.
|
/// the result `x - y - borrow` and, if this underflows, a new `borrow`. Inputs are not range-checked.
|
||||||
@ -93,7 +98,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for U32Subtraction
|
|||||||
constraints.push(output_result - (result_initial + base * output_borrow));
|
constraints.push(output_result - (result_initial + base * output_borrow));
|
||||||
|
|
||||||
// Range-check output_result to be at most 32 bits.
|
// Range-check output_result to be at most 32 bits.
|
||||||
let mut combined_limbs = F::Extension::ZERO;
|
let mut combined_limbs = <F::Extension as Field>::ZERO;
|
||||||
let limb_base = F::Extension::from_canonical_u64(1u64 << Self::limb_bits());
|
let limb_base = F::Extension::from_canonical_u64(1u64 << Self::limb_bits());
|
||||||
for j in (0..Self::num_limbs()).rev() {
|
for j in (0..Self::num_limbs()).rev() {
|
||||||
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
||||||
@ -108,7 +113,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for U32Subtraction
|
|||||||
constraints.push(combined_limbs - output_result);
|
constraints.push(combined_limbs - output_result);
|
||||||
|
|
||||||
// Range-check output_borrow to be one bit.
|
// Range-check output_borrow to be one bit.
|
||||||
constraints.push(output_borrow * (F::Extension::ONE - output_borrow));
|
constraints.push(output_borrow * (<F::Extension as Field>::ONE - output_borrow));
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints
|
constraints
|
||||||
@ -116,40 +121,14 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for U32Subtraction
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
for i in 0..self.num_ops {
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let input_x = vars.local_wires[self.wire_ith_input_x(i)];
|
}
|
||||||
let input_y = vars.local_wires[self.wire_ith_input_y(i)];
|
|
||||||
let input_borrow = vars.local_wires[self.wire_ith_input_borrow(i)];
|
|
||||||
|
|
||||||
let result_initial = input_x - input_y - input_borrow;
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
let base = F::from_canonical_u64(1 << 32u64);
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
|
|
||||||
let output_result = vars.local_wires[self.wire_ith_output_result(i)];
|
|
||||||
let output_borrow = vars.local_wires[self.wire_ith_output_borrow(i)];
|
|
||||||
|
|
||||||
yield_constr.one(output_result - (result_initial + base * output_borrow));
|
|
||||||
|
|
||||||
// Range-check output_result to be at most 32 bits.
|
|
||||||
let mut combined_limbs = F::ZERO;
|
|
||||||
let limb_base = F::from_canonical_u64(1u64 << Self::limb_bits());
|
|
||||||
for j in (0..Self::num_limbs()).rev() {
|
|
||||||
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
|
||||||
let max_limb = 1 << Self::limb_bits();
|
|
||||||
let product = (0..max_limb)
|
|
||||||
.map(|x| this_limb - F::from_canonical_usize(x))
|
|
||||||
.product();
|
|
||||||
yield_constr.one(product);
|
|
||||||
|
|
||||||
combined_limbs = limb_base * combined_limbs + this_limb;
|
|
||||||
}
|
|
||||||
yield_constr.one(combined_limbs - output_result);
|
|
||||||
|
|
||||||
// Range-check output_borrow to be one bit.
|
|
||||||
yield_constr.one(output_borrow * (F::ONE - output_borrow));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -240,6 +219,48 @@ impl<F: RichField + Extendable<D>, const D: usize> Gate<F, D> for U32Subtraction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: RichField + Extendable<D>, const D: usize> PackedEvaluableBase<F, D>
|
||||||
|
for U32SubtractionGate<F, D>
|
||||||
|
{
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
for i in 0..self.num_ops {
|
||||||
|
let input_x = vars.local_wires[self.wire_ith_input_x(i)];
|
||||||
|
let input_y = vars.local_wires[self.wire_ith_input_y(i)];
|
||||||
|
let input_borrow = vars.local_wires[self.wire_ith_input_borrow(i)];
|
||||||
|
|
||||||
|
let result_initial = input_x - input_y - input_borrow;
|
||||||
|
let base = F::from_canonical_u64(1 << 32u64);
|
||||||
|
|
||||||
|
let output_result = vars.local_wires[self.wire_ith_output_result(i)];
|
||||||
|
let output_borrow = vars.local_wires[self.wire_ith_output_borrow(i)];
|
||||||
|
|
||||||
|
yield_constr.one(output_result - (result_initial + output_borrow * base));
|
||||||
|
|
||||||
|
// Range-check output_result to be at most 32 bits.
|
||||||
|
let mut combined_limbs = P::ZERO;
|
||||||
|
let limb_base = F::from_canonical_u64(1u64 << Self::limb_bits());
|
||||||
|
for j in (0..Self::num_limbs()).rev() {
|
||||||
|
let this_limb = vars.local_wires[self.wire_ith_output_jth_limb(i, j)];
|
||||||
|
let max_limb = 1 << Self::limb_bits();
|
||||||
|
let product = (0..max_limb)
|
||||||
|
.map(|x| this_limb - F::from_canonical_usize(x))
|
||||||
|
.product();
|
||||||
|
yield_constr.one(product);
|
||||||
|
|
||||||
|
combined_limbs = combined_limbs * limb_base + this_limb;
|
||||||
|
}
|
||||||
|
yield_constr.one(combined_limbs - output_result);
|
||||||
|
|
||||||
|
// Range-check output_borrow to be one bit.
|
||||||
|
yield_constr.one(output_borrow * (P::ONE - output_borrow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct U32SubtractionGenerator<F: RichField + Extendable<D>, const D: usize> {
|
struct U32SubtractionGenerator<F: RichField + Extendable<D>, const D: usize> {
|
||||||
gate: U32SubtractionGate<F, D>,
|
gate: U32SubtractionGate<F, D>,
|
||||||
|
|||||||
@ -5,7 +5,9 @@ use array_tool::vec::Union;
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::gates::gate::Gate;
|
use crate::gates::gate::Gate;
|
||||||
|
use crate::gates::packed_util::PackedEvaluableBase;
|
||||||
use crate::gates::util::StridedConstraintConsumer;
|
use crate::gates::util::StridedConstraintConsumer;
|
||||||
use crate::iop::generator::{GeneratedValues, WitnessGenerator};
|
use crate::iop::generator::{GeneratedValues, WitnessGenerator};
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
@ -13,7 +15,10 @@ use crate::iop::wire::Wire;
|
|||||||
use crate::iop::witness::{PartitionWitness, Witness};
|
use crate::iop::witness::{PartitionWitness, Witness};
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::plonk::circuit_data::CircuitConfig;
|
use crate::plonk::circuit_data::CircuitConfig;
|
||||||
use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase};
|
use crate::plonk::vars::{
|
||||||
|
EvaluationTargets, EvaluationVars, EvaluationVarsBase, EvaluationVarsBaseBatch,
|
||||||
|
EvaluationVarsBasePacked,
|
||||||
|
};
|
||||||
|
|
||||||
/// A gate for conditionally swapping input values based on a boolean.
|
/// A gate for conditionally swapping input values based on a boolean.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -77,7 +82,7 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for SwitchGate<F, D> {
|
|||||||
|
|
||||||
for c in 0..self.num_copies {
|
for c in 0..self.num_copies {
|
||||||
let switch_bool = vars.local_wires[self.wire_switch_bool(c)];
|
let switch_bool = vars.local_wires[self.wire_switch_bool(c)];
|
||||||
let not_switch = F::Extension::ONE - switch_bool;
|
let not_switch = <F::Extension as Field>::ONE - switch_bool;
|
||||||
|
|
||||||
for e in 0..self.chunk_size {
|
for e in 0..self.chunk_size {
|
||||||
let first_input = vars.local_wires[self.wire_first_input(c, e)];
|
let first_input = vars.local_wires[self.wire_first_input(c, e)];
|
||||||
@ -97,25 +102,14 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for SwitchGate<F, D> {
|
|||||||
|
|
||||||
fn eval_unfiltered_base_one(
|
fn eval_unfiltered_base_one(
|
||||||
&self,
|
&self,
|
||||||
vars: EvaluationVarsBase<F>,
|
_vars: EvaluationVarsBase<F>,
|
||||||
mut yield_constr: StridedConstraintConsumer<F>,
|
_yield_constr: StridedConstraintConsumer<F>,
|
||||||
) {
|
) {
|
||||||
for c in 0..self.num_copies {
|
panic!("use eval_unfiltered_base_packed instead");
|
||||||
let switch_bool = vars.local_wires[self.wire_switch_bool(c)];
|
}
|
||||||
let not_switch = F::ONE - switch_bool;
|
|
||||||
|
|
||||||
for e in 0..self.chunk_size {
|
fn eval_unfiltered_base_batch(&self, vars_base: EvaluationVarsBaseBatch<F>) -> Vec<F> {
|
||||||
let first_input = vars.local_wires[self.wire_first_input(c, e)];
|
self.eval_unfiltered_base_batch_packed(vars_base)
|
||||||
let second_input = vars.local_wires[self.wire_second_input(c, e)];
|
|
||||||
let first_output = vars.local_wires[self.wire_first_output(c, e)];
|
|
||||||
let second_output = vars.local_wires[self.wire_second_output(c, e)];
|
|
||||||
|
|
||||||
yield_constr.one(switch_bool * (first_input - second_output));
|
|
||||||
yield_constr.one(switch_bool * (second_input - first_output));
|
|
||||||
yield_constr.one(not_switch * (first_input - first_output));
|
|
||||||
yield_constr.one(not_switch * (second_input - second_output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_unfiltered_recursively(
|
fn eval_unfiltered_recursively(
|
||||||
@ -194,6 +188,31 @@ impl<F: Extendable<D>, const D: usize> Gate<F, D> for SwitchGate<F, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Extendable<D>, const D: usize> PackedEvaluableBase<F, D> for SwitchGate<F, D> {
|
||||||
|
fn eval_unfiltered_base_packed<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
vars: EvaluationVarsBasePacked<P>,
|
||||||
|
mut yield_constr: StridedConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
for c in 0..self.num_copies {
|
||||||
|
let switch_bool = vars.local_wires[self.wire_switch_bool(c)];
|
||||||
|
let not_switch = P::ONE - switch_bool;
|
||||||
|
|
||||||
|
for e in 0..self.chunk_size {
|
||||||
|
let first_input = vars.local_wires[self.wire_first_input(c, e)];
|
||||||
|
let second_input = vars.local_wires[self.wire_second_input(c, e)];
|
||||||
|
let first_output = vars.local_wires[self.wire_first_output(c, e)];
|
||||||
|
let second_output = vars.local_wires[self.wire_second_output(c, e)];
|
||||||
|
|
||||||
|
yield_constr.one(switch_bool * (first_input - second_output));
|
||||||
|
yield_constr.one(switch_bool * (second_input - first_output));
|
||||||
|
yield_constr.one(not_switch * (first_input - first_output));
|
||||||
|
yield_constr.one(not_switch * (second_input - second_output));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SwitchGenerator<F: Extendable<D>, const D: usize> {
|
struct SwitchGenerator<F: Extendable<D>, const D: usize> {
|
||||||
gate_index: usize,
|
gate_index: usize,
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::field::field_types::Field;
|
use crate::field::packed_field::PackedField;
|
||||||
|
|
||||||
/// Writes constraints yielded by a gate to a buffer, with a given stride.
|
/// Writes constraints yielded by a gate to a buffer, with a given stride.
|
||||||
/// Permits us to abstract the underlying memory layout. In particular, we can make a matrix of
|
/// Permits us to abstract the underlying memory layout. In particular, we can make a matrix of
|
||||||
/// constraints where every column is an evaluation point and every row is a constraint index, with
|
/// constraints where every column is an evaluation point and every row is a constraint index, with
|
||||||
/// the matrix stored in row-contiguous form.
|
/// the matrix stored in row-contiguous form.
|
||||||
pub struct StridedConstraintConsumer<'a, F: Field> {
|
pub struct StridedConstraintConsumer<'a, P: PackedField> {
|
||||||
// This is a particularly neat way of doing this, more so than a slice. We increase start by
|
// This is a particularly neat way of doing this, more so than a slice. We increase start by
|
||||||
// stride at every step and terminate when it equals end.
|
// stride at every step and terminate when it equals end.
|
||||||
start: *mut F,
|
start: *mut P::Scalar,
|
||||||
end: *mut F,
|
end: *mut P::Scalar,
|
||||||
stride: usize,
|
stride: usize,
|
||||||
_phantom: PhantomData<&'a mut [F]>,
|
_phantom: PhantomData<&'a mut [P::Scalar]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F: Field> StridedConstraintConsumer<'a, F> {
|
impl<'a, P: PackedField> StridedConstraintConsumer<'a, P> {
|
||||||
pub fn new(buffer: &'a mut [F], stride: usize, offset: usize) -> Self {
|
pub fn new(buffer: &'a mut [P::Scalar], stride: usize, offset: usize) -> Self {
|
||||||
|
assert!(stride >= P::WIDTH);
|
||||||
assert!(offset < stride);
|
assert!(offset < stride);
|
||||||
assert_eq!(buffer.len() % stride, 0);
|
assert_eq!(buffer.len() % stride, 0);
|
||||||
let ptr_range = buffer.as_mut_ptr_range();
|
let ptr_range = buffer.as_mut_ptr_range();
|
||||||
@ -38,12 +39,12 @@ impl<'a, F: Field> StridedConstraintConsumer<'a, F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Emit one constraint.
|
/// Emit one constraint.
|
||||||
pub fn one(&mut self, constraint: F) {
|
pub fn one(&mut self, constraint: P) {
|
||||||
if self.start != self.end {
|
if self.start != self.end {
|
||||||
// # Safety
|
// # Safety
|
||||||
// The checks in `new` guarantee that this points to valid space.
|
// The checks in `new` guarantee that this points to valid space.
|
||||||
unsafe {
|
unsafe {
|
||||||
*self.start = constraint;
|
*self.start.cast() = constraint;
|
||||||
}
|
}
|
||||||
// See the comment in `new`. `wrapping_add` is needed to avoid UB if we've just
|
// See the comment in `new`. `wrapping_add` is needed to avoid UB if we've just
|
||||||
// exhausted our buffer (and hence we're setting `self.start` to point past the end).
|
// exhausted our buffer (and hence we're setting `self.start` to point past the end).
|
||||||
@ -54,7 +55,7 @@ impl<'a, F: Field> StridedConstraintConsumer<'a, F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience method that calls `.one()` multiple times.
|
/// Convenience method that calls `.one()` multiple times.
|
||||||
pub fn many<I: IntoIterator<Item = F>>(&mut self, constraints: I) {
|
pub fn many<I: IntoIterator<Item = P>>(&mut self, constraints: I) {
|
||||||
constraints
|
constraints
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|constraint| self.one(constraint));
|
.for_each(|constraint| self.one(constraint));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::field::extension_field::target::ExtensionTarget;
|
use crate::field::extension_field::target::ExtensionTarget;
|
||||||
use crate::field::extension_field::Extendable;
|
use crate::field::extension_field::Extendable;
|
||||||
use crate::field::field_types::{Field, RichField};
|
use crate::field::field_types::{Field, RichField};
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::fri::commitment::SALT_SIZE;
|
use crate::fri::commitment::SALT_SIZE;
|
||||||
use crate::iop::target::Target;
|
use crate::iop::target::Target;
|
||||||
use crate::plonk::circuit_builder::CircuitBuilder;
|
use crate::plonk::circuit_builder::CircuitBuilder;
|
||||||
@ -157,14 +158,14 @@ pub(crate) fn reduce_with_powers_multi<
|
|||||||
cumul
|
cumul
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reduce_with_powers<'a, F: Field, T: IntoIterator<Item = &'a F>>(
|
pub(crate) fn reduce_with_powers<'a, P: PackedField, T: IntoIterator<Item = &'a P>>(
|
||||||
terms: T,
|
terms: T,
|
||||||
alpha: F,
|
alpha: P::Scalar,
|
||||||
) -> F
|
) -> P
|
||||||
where
|
where
|
||||||
T::IntoIter: DoubleEndedIterator,
|
T::IntoIter: DoubleEndedIterator,
|
||||||
{
|
{
|
||||||
let mut sum = F::ZERO;
|
let mut sum = P::ZERO;
|
||||||
for &term in terms.into_iter().rev() {
|
for &term in terms.into_iter().rev() {
|
||||||
sum = sum * alpha + term;
|
sum = sum * alpha + term;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use crate::field::extension_field::algebra::ExtensionAlgebra;
|
|||||||
use crate::field::extension_field::target::{ExtensionAlgebraTarget, ExtensionTarget};
|
use crate::field::extension_field::target::{ExtensionAlgebraTarget, ExtensionTarget};
|
||||||
use crate::field::extension_field::{Extendable, FieldExtension};
|
use crate::field::extension_field::{Extendable, FieldExtension};
|
||||||
use crate::field::field_types::Field;
|
use crate::field::field_types::Field;
|
||||||
|
use crate::field::packed_field::PackedField;
|
||||||
use crate::hash::hash_types::{HashOut, HashOutTarget};
|
use crate::hash::hash_types::{HashOut, HashOutTarget};
|
||||||
use crate::util::strided_view::PackedStridedView;
|
use crate::util::strided_view::PackedStridedView;
|
||||||
|
|
||||||
@ -33,6 +34,16 @@ pub struct EvaluationVarsBase<'a, F: Field> {
|
|||||||
pub(crate) public_inputs_hash: &'a HashOut<F>,
|
pub(crate) public_inputs_hash: &'a HashOut<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `EvaluationVarsBase`, but packed.
|
||||||
|
// It's a separate struct because `EvaluationVarsBase` implements `get_local_ext` and we do not yet
|
||||||
|
// have packed extension fields.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct EvaluationVarsBasePacked<'a, P: PackedField> {
|
||||||
|
pub(crate) local_constants: PackedStridedView<'a, P>,
|
||||||
|
pub(crate) local_wires: PackedStridedView<'a, P>,
|
||||||
|
pub(crate) public_inputs_hash: &'a HashOut<P::Scalar>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, F: Extendable<D>, const D: usize> EvaluationVars<'a, F, D> {
|
impl<'a, F: Extendable<D>, const D: usize> EvaluationVars<'a, F, D> {
|
||||||
pub fn get_local_ext_algebra(
|
pub fn get_local_ext_algebra(
|
||||||
&self,
|
&self,
|
||||||
@ -88,6 +99,19 @@ impl<'a, F: Field> EvaluationVarsBaseBatch<'a, F> {
|
|||||||
pub fn iter(&self) -> EvaluationVarsBaseBatchIter<'a, F> {
|
pub fn iter(&self) -> EvaluationVarsBaseBatchIter<'a, F> {
|
||||||
EvaluationVarsBaseBatchIter::new(*self)
|
EvaluationVarsBaseBatchIter::new(*self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pack<P: PackedField<Scalar = F>>(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
EvaluationVarsBaseBatchIterPacked<'a, P>,
|
||||||
|
EvaluationVarsBaseBatchIterPacked<'a, F>,
|
||||||
|
) {
|
||||||
|
let n_leftovers = self.len() % P::WIDTH;
|
||||||
|
(
|
||||||
|
EvaluationVarsBaseBatchIterPacked::new_with_start(*self, 0),
|
||||||
|
EvaluationVarsBaseBatchIterPacked::new_with_start(*self, self.len() - n_leftovers),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, F: Field> EvaluationVarsBase<'a, F> {
|
impl<'a, F: Field> EvaluationVarsBase<'a, F> {
|
||||||
@ -126,6 +150,63 @@ impl<'a, F: Field> Iterator for EvaluationVarsBaseBatchIter<'a, F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterator of packed views (`EvaluationVarsBasePacked`) into a `EvaluationVarsBaseBatch`.
|
||||||
|
/// Note: if the length of `EvaluationVarsBaseBatch` is not a multiple of `P::WIDTH`, then the
|
||||||
|
/// leftovers at the end are ignored.
|
||||||
|
pub struct EvaluationVarsBaseBatchIterPacked<'a, P: PackedField> {
|
||||||
|
/// Index to yield next, in units of `P::Scalar`. E.g. if `P::WIDTH == 4`, then we will yield
|
||||||
|
/// the vars for points `i`, `i + 1`, `i + 2`, and `i + 3`, packed.
|
||||||
|
i: usize,
|
||||||
|
vars_batch: EvaluationVarsBaseBatch<'a, P::Scalar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: PackedField> EvaluationVarsBaseBatchIterPacked<'a, P> {
|
||||||
|
pub fn new_with_start(
|
||||||
|
vars_batch: EvaluationVarsBaseBatch<'a, P::Scalar>,
|
||||||
|
start: usize,
|
||||||
|
) -> Self {
|
||||||
|
assert!(start <= vars_batch.len());
|
||||||
|
EvaluationVarsBaseBatchIterPacked {
|
||||||
|
i: start,
|
||||||
|
vars_batch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: PackedField> Iterator for EvaluationVarsBaseBatchIterPacked<'a, P> {
|
||||||
|
type Item = EvaluationVarsBasePacked<'a, P>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.i + P::WIDTH <= self.vars_batch.len() {
|
||||||
|
let local_constants = PackedStridedView::new(
|
||||||
|
self.vars_batch.local_constants,
|
||||||
|
self.vars_batch.len(),
|
||||||
|
self.i,
|
||||||
|
);
|
||||||
|
let local_wires =
|
||||||
|
PackedStridedView::new(self.vars_batch.local_wires, self.vars_batch.len(), self.i);
|
||||||
|
let res = EvaluationVarsBasePacked {
|
||||||
|
local_constants,
|
||||||
|
local_wires,
|
||||||
|
public_inputs_hash: self.vars_batch.public_inputs_hash,
|
||||||
|
};
|
||||||
|
self.i += P::WIDTH;
|
||||||
|
Some(res)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.len();
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: PackedField> ExactSizeIterator for EvaluationVarsBaseBatchIterPacked<'a, P> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
(self.vars_batch.len() - self.i) / P::WIDTH
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, const D: usize> EvaluationTargets<'a, D> {
|
impl<'a, const D: usize> EvaluationTargets<'a, D> {
|
||||||
pub fn remove_prefix(&mut self, prefix: &[bool]) {
|
pub fn remove_prefix(&mut self, prefix: &[bool]) {
|
||||||
self.local_constants = &self.local_constants[prefix.len()..];
|
self.local_constants = &self.local_constants[prefix.len()..];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user