From 1479400555ae29fe30c4c6f7d3721000daa04b0d Mon Sep 17 00:00:00 2001 From: Ahmad Afuni Date: Thu, 4 Sep 2025 00:47:59 +1000 Subject: [PATCH] feat(backend): use custom gate for Schnorr signature verification (#397) * schnorr signature prover optimization. TODO: implemented eval_unfiltered_circuit, serialize * Use new gate(s) in MainPod circuit * Formatting * Clean-up * Code review * Code review * Code review * Formatting * Remove unnecessary elements --------- Co-authored-by: Linus Tang --- src/backends/plonky2/primitives/ec/curve.rs | 114 ++- src/backends/plonky2/primitives/ec/field.rs | 39 +- .../plonky2/primitives/ec/gates/curve.rs | 902 +++++++++++++++++- .../plonky2/primitives/ec/gates/field.rs | 350 +++++-- src/backends/plonky2/primitives/ec/schnorr.rs | 24 +- src/backends/plonky2/recursion/circuit.rs | 23 +- src/backends/plonky2/serialization.rs | 21 +- 7 files changed, 1310 insertions(+), 163 deletions(-) diff --git a/src/backends/plonky2/primitives/ec/curve.rs b/src/backends/plonky2/primitives/ec/curve.rs index 907b2a2..474120b 100644 --- a/src/backends/plonky2/primitives/ec/curve.rs +++ b/src/backends/plonky2/primitives/ec/curve.rs @@ -32,7 +32,7 @@ use crate::backends::plonky2::{ primitives::ec::{ bits::BigUInt320Target, field::{get_nnf_target, CircuitBuilderNNF, OEFTarget}, - gates::{curve::ECAddHomogOffset, generic::SimpleGate}, + gates::curve::{ECAddHomogOffsetGate, ECAddXuGate}, }, Error, }; @@ -305,6 +305,17 @@ pub(super) fn add_homog>(x1: F, u1: F, x2: F, u [x, z, u, t] } +/// Adds two elliptic curve points in affine coordinates. +pub(super) fn add_xu + std::ops::Div>( + x1: F, + u1: F, + x2: F, + u2: F, +) -> [F; 2] { + let [x, z, u, t] = add_homog(x1, u1, x2, u2); + [x / z, u / t] +} + // See CircuitBuilderEllptic::add_point for an explanation of why we need this function. // cf. https://github.com/pornin/ecgfp5/blob/ce059c6d1e1662db437aecbf3db6bb67fe63c716/rust/src/curve.rs#L157 pub(super) fn add_homog_offset>( @@ -341,7 +352,7 @@ static GROUP_ORDER_HALF_ROUND_UP: LazyLock = impl Point { const B1_U32: u32 = 263; - const B1: GoldilocksField = GoldilocksField(Self::B1_U32 as u64); + pub(crate) const B1: GoldilocksField = GoldilocksField(Self::B1_U32 as u64); pub fn b() -> ECField { ECField::from_basefield_array([ @@ -555,6 +566,75 @@ where } } +pub trait CircuitBuilderSignature { + /// Computes `a*g + b*p`, where `g` is the generator of the curve. + fn linear_combination_point_gen( + &mut self, + a: &[BoolTarget; 320], + b: &[BoolTarget; 320], + p: &PointTarget, + ) -> PointTarget; +} + +impl CircuitBuilderSignature for CircuitBuilder { + fn linear_combination_point_gen( + &mut self, + a: &[BoolTarget; 320], + b: &[BoolTarget; 320], + p: &PointTarget, + ) -> PointTarget { + let y = p; + let zero = self.identity_point(); + let zero_target = self.zero(); + + let mut ans = zero.clone(); // accumulator + + for x in 0..107 { + // prepare to apply gate + let mut inputs = Vec::with_capacity(26); + + // scalar bits for g + if x == 0 { + inputs.push(zero_target); + } else { + inputs.push(a[320 - 3 * x].target); + } + inputs.push(a[319 - 3 * x].target); + inputs.push(a[318 - 3 * x].target); + + // scalar bits for p (y) + if x == 0 { + inputs.push(zero_target); + } else { + inputs.push(b[320 - 3 * x].target); + } + inputs.push(b[319 - 3 * x].target); + inputs.push(b[318 - 3 * x].target); + + // y point + inputs.extend_from_slice(&y.x.components); + inputs.extend_from_slice(&y.u.components); + + // accumulator + inputs.extend_from_slice(&ans.x.components); + inputs.extend_from_slice(&ans.u.components); + + // apply gate + let outputs = ECAddXuGate::apply(self, &inputs); + let x = FieldTarget::new(array::from_fn(|i| outputs[i])); + let u = FieldTarget::new(array::from_fn(|i| outputs[5 + i])); + ans = PointTarget { + x, + u, + checked_on_curve: true, + checked_in_subgroup: p.checked_in_subgroup, + }; + } + + ans + } +} + pub trait CircuitBuilderElliptic { fn add_virtual_point_target_unsafe(&mut self) -> PointTarget; fn add_virtual_point_target(&mut self) -> PointTarget; @@ -617,7 +697,9 @@ impl CircuitBuilderElliptic for CircuitBuilder { inputs.extend_from_slice(&p1.u.components); inputs.extend_from_slice(&p2.x.components); inputs.extend_from_slice(&p2.u.components); - let outputs = ECAddHomogOffset::apply(self, &inputs); + + let outputs = ECAddHomogOffsetGate::apply(self, &inputs); + // plonky2 expects all gate constraints to be satisfied by the zero vector. // So our elliptic curve addition gate computes [x,z-b,u,t-b], and we have to add the b here. let [x, z, u, t] = @@ -638,32 +720,6 @@ impl CircuitBuilderElliptic for CircuitBuilder { fn double_point(&mut self, p: &PointTarget) -> PointTarget { self.add_point(p, p) - /* - let t3 = self.nnf_mul(&p.u, &p.u); - let one = self.one(); - let neg_one = self.neg_one(); - let two = self.two(); - let neg_four = self.constant(GoldilocksField::from_noncanonical_i64(-4)); - let four_b = self.constant(GoldilocksField::from_canonical_u32(4 * Point::B1_U32)); - let w1_1 = self.nnf_add_scalar_times_generator_power(one, 0, &p.x); - let w1_2 = self.nnf_add(&w1_1, &w1_1); - let w1_3 = self.nnf_mul(&w1_2, &t3); - let w1_4 = self.nnf_mul_scalar(neg_one, &w1_3); - let w1 = self.nnf_add_scalar_times_generator_power(one, 0, &w1_4); - let x_1 = self.nnf_mul_scalar(four_b, &t3); - let x = self.nnf_mul_generator(&x_1); - let z = self.nnf_mul(&w1, &w1); - let u_1 = self.nnf_add(&w1, &p.u); - let u_2 = self.nnf_mul(&u_1, &u_1); - let u_3 = self.nnf_sub(&u_2, &t3); - let u = self.nnf_sub(&u_3, &z); - let t_1 = self.nnf_mul_scalar(neg_four, &t3); - let t_2 = self.nnf_add_scalar_times_generator_power(two, 0, &t_1); - let t = self.nnf_sub(&t_2, &z); - let xq = self.nnf_div(&x, &z); - let uq = self.nnf_div(&u, &t); - PointTarget { x: xq, u: uq } - */ } fn multiply_point(&mut self, p1_scalar: &[BoolTarget; 320], p1: &PointTarget) -> PointTarget { diff --git a/src/backends/plonky2/primitives/ec/field.rs b/src/backends/plonky2/primitives/ec/field.rs index 8234a38..a72ed46 100644 --- a/src/backends/plonky2/primitives/ec/field.rs +++ b/src/backends/plonky2/primitives/ec/field.rs @@ -1,4 +1,4 @@ -use std::{array, marker::PhantomData}; +use std::marker::PhantomData; use num::BigUint; use plonky2::{ @@ -19,10 +19,7 @@ use serde::{Deserialize, Serialize}; //use super::gates::field::NNFMulGate; use crate::{ - backends::plonky2::{ - basetypes::D, - primitives::ec::gates::{field::NNFMulSimple, generic::SimpleGate}, - }, + backends::plonky2::{basetypes::D, primitives::ec::gates::field::NNFMulGate}, middleware::F, }; @@ -219,11 +216,33 @@ impl + FieldExtension> OEFTarget::new(std::array::from_fn(|i| sub_targets[i])) } fn nnf_mul(&mut self, x: &OEFTarget, y: &OEFTarget) -> OEFTarget { - let mut inputs = Vec::with_capacity(10); - inputs.extend_from_slice(&x.components); - inputs.extend_from_slice(&y.components); - let outputs = NNFMulSimple::::apply(self, &inputs); - OEFTarget::new(array::from_fn(|i| outputs[i])) + let gate = NNFMulGate::::new_from_config(&self.config); + let (gate, i) = self.find_slot(gate, &[], &[]); + let wires_m0: OEFTarget = OEFTarget::new( + NNFMulGate::::wires_ith_multiplicand_0(i) + .map(|i| Target::wire(gate, i)) + .collect::>() + .try_into() + .unwrap(), + ); + let wires_m1: OEFTarget = OEFTarget::new( + NNFMulGate::::wires_ith_multiplicand_1(i) + .map(|i| Target::wire(gate, i)) + .collect::>() + .try_into() + .unwrap(), + ); + let output: OEFTarget = OEFTarget::new( + NNFMulGate::::wires_ith_output(i) + .map(|i| Target::wire(gate, i)) + .collect::>() + .try_into() + .unwrap(), + ); + self.nnf_connect(&wires_m0, x); + self.nnf_connect(&wires_m1, y); + + output } fn nnf_div(&mut self, x: &OEFTarget, y: &OEFTarget) -> OEFTarget { let one = self.nnf_one(); diff --git a/src/backends/plonky2/primitives/ec/gates/curve.rs b/src/backends/plonky2/primitives/ec/gates/curve.rs index ecce193..990390b 100644 --- a/src/backends/plonky2/primitives/ec/gates/curve.rs +++ b/src/backends/plonky2/primitives/ec/gates/curve.rs @@ -1,10 +1,34 @@ -use std::array; +use std::{array, ops::Range}; -use plonky2::field::goldilocks_field::GoldilocksField; +use itertools::{zip_eq, Itertools}; +use plonky2::{ + field::{ + extension::{quintic::QuinticExtension, Extendable, FieldExtension, OEF}, + goldilocks_field::GoldilocksField, + types::Field, + }, + gates::gate::Gate, + hash::hash_types::RichField, + iop::{ + ext_target::ExtensionTarget, + generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}, + target::Target, + witness::{PartitionWitness, Witness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{CircuitConfig, CommonCircuitData}, + vars::{EvaluationTargets, EvaluationVars}, + }, + util::serialization::{Buffer, IoResult, Read, Write}, +}; -use crate::backends::plonky2::primitives::ec::{ - curve::{add_homog_offset, ECFieldExt}, - gates::{field::QuinticTensor, generic::SimpleGate}, +use crate::backends::plonky2::{ + basetypes::F, + primitives::ec::{ + curve::{add_homog, add_homog_offset, add_xu, ECFieldExt, Point}, + gates::field::{nnf_mul_ext, QuinticTensor}, + }, }; /// Gate computing the addition of two elliptic curve points in @@ -25,30 +49,835 @@ use crate::backends::plonky2::primitives::ec::{ /// operation when all its witness wire values are zero (so that when the gate is partially used, /// the unused slots still pass the constraints). This is the reason why this gate doesn't add the /// final offset: if it did, the constraints wouldn't pass on the zero witness values. -#[derive(Debug, Default, Clone)] -pub struct ECAddHomogOffset; +#[derive(Debug, Clone)] +pub struct ECAddHomogOffsetGate { + /// Number of (offset) EC additions performed by the gate. + pub num_ops: usize, +} -impl SimpleGate for ECAddHomogOffset { - type F = GoldilocksField; - const INPUTS_PER_OP: usize = 20; - const OUTPUTS_PER_OP: usize = 20; - const DEGREE: usize = 4; - const ID: &'static str = "ECAddHomog"; - fn eval( - wires: &[>::Extension], - ) -> Vec<>::Extension> +impl ECAddHomogOffsetGate { + pub const fn new_from_config(config: &CircuitConfig) -> Self { + Self { + num_ops: Self::num_ops(config), + } + } + + /// Determine the maximum number of operations that can fit in one gate for the given config. + pub(crate) const fn num_ops(config: &CircuitConfig) -> usize { + let wires_per_op = 40; + config.num_routed_wires / wires_per_op + } + + pub(crate) const fn wires_ith_addend_0(i: usize) -> Range { + 40 * i..40 * i + 10 + } + pub(crate) const fn wires_ith_addend_1(i: usize) -> Range { + 40 * i + 10..40 * i + 20 + } + pub(crate) const fn wires_ith_output(i: usize) -> Range { + 40 * i + 20..40 * (i + 1) + } + + pub fn apply( + builder: &mut CircuitBuilder, + targets: &[Target], + ) -> Vec where - Self::F: plonky2::field::extension::Extendable, + F: Extendable, + { + let gate = ECAddHomogOffsetGate::new_from_config(&builder.config); + let (row, op_num) = builder.find_slot(gate, &[], &[]); + let wires_a0 = Self::wires_ith_addend_0(op_num) + .map(|i| Target::wire(row, i)) + .collect::>(); + let wires_a1 = Self::wires_ith_addend_1(op_num) + .map(|i| Target::wire(row, i)) + .collect::>(); + let outputs = Self::wires_ith_output(op_num) + .map(|i| Target::wire(row, i)) + .collect::>(); + zip_eq(targets, [wires_a0, wires_a1].concat()).for_each(|(i, w)| builder.connect(*i, w)); + + outputs + } +} + +impl Gate for ECAddHomogOffsetGate +where + F: Extendable, +{ + fn id(&self) -> String { + format!("{self:?}") + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.num_ops) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let num_ops = src.read_usize()?; + Ok(Self { num_ops }) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec<>::Extension> { + let mut constraints = Vec::with_capacity(self.num_ops * 20); + let extract_point = |range: Range| -> (QuinticTensor, QuinticTensor) { + let components = vars.local_wires[range].to_vec(); + ( + QuinticTensor::from_base(array::from_fn::<_, 5, _>(|i| components[i])), + QuinticTensor::from_base(array::from_fn::<_, 5, _>(|i| components[i + 5])), + ) + }; + + for i in 0..self.num_ops { + let (a_0x, a_0u) = extract_point(Self::wires_ith_addend_0(i)); + let (a_1x, a_1u) = extract_point(Self::wires_ith_addend_1(i)); + let output_vec = vars.local_wires[Self::wires_ith_output(i)] + .iter() + .chunks(5) + .into_iter() + .map(|chunk| { + let chunk_vec = chunk.collect::>(); + QuinticTensor::from_base(array::from_fn(|i| *chunk_vec[i])) + }) + .collect::>>(); + let computed_output = add_homog_offset(a_0x, a_0u, a_1x, a_1u); + + let new_constraints = + zip_eq(output_vec, computed_output).flat_map(|(o, co)| (o - co).components); + + constraints.extend(new_constraints); + } + + constraints + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_ops * 20); + + let extract_point = + |range: Range| -> ([ExtensionTarget; 5], [ExtensionTarget; 5]) { + let components = vars.local_wires[range].to_vec(); + ( + array::from_fn::<_, 5, _>(|i| components[i]), + array::from_fn::<_, 5, _>(|i| components[i + 5]), + ) + }; + for i in 0..self.num_ops { + let (x1, u1) = extract_point(Self::wires_ith_addend_0(i)); + let (x2, u2) = extract_point(Self::wires_ith_addend_1(i)); + let computed_output = ec_target_add_homog_offset(builder, &x1, &u1, &x2, &u2) + .into_iter() + .flatten(); + + let output: [ExtensionTarget; 20] = vars.local_wires[Self::wires_ith_output(i)] + .try_into() + .unwrap(); + + let diffs = zip_eq(output, computed_output) + .map(|(o, co)| builder.sub_extension(o, co)) + .collect::>(); + constraints.extend(diffs); + } + + constraints + } + + fn generators(&self, row: usize, _local_constants: &[F]) -> Vec> { + (0..self.num_ops) + .map(|i| WitnessGeneratorRef::new(ECAddHomogOffsetGenerator { row, i }.adapter())) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_ops * 40 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 4 + } + + fn num_constraints(&self) -> usize { + self.num_ops * 20 + } +} + +#[derive(Clone, Debug, Default)] +pub struct ECAddHomogOffsetGenerator { + row: usize, + i: usize, +} + +impl SimpleGenerator for ECAddHomogOffsetGenerator +where + F: Extendable, +{ + fn id(&self) -> String { + "ECAddHomogOffsetGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + ECAddHomogOffsetGate::wires_ith_addend_0(self.i) + .chain(ECAddHomogOffsetGate::wires_ith_addend_1(self.i)) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once( + &self, + witness: &PartitionWitness, + out_buffer: &mut GeneratedValues, + ) -> anyhow::Result<()> { + let extract_point = |range: Range| -> anyhow::Result<_> { + let components = range + .map(|i| witness.get_target(Target::wire(self.row, i))) + .collect::>(); + Ok(( + QuinticExtension::from_basefield_array(array::from_fn::<_, 5, _>(|i| { + components[i] + })), + QuinticExtension::from_basefield_array(array::from_fn::<_, 5, _>(|i| { + components[i + 5] + })), + )) + }; + + let addend_0 = extract_point(ECAddHomogOffsetGate::wires_ith_addend_0(self.i))?; + let addend_1 = extract_point(ECAddHomogOffsetGate::wires_ith_addend_1(self.i))?; + + let output_targets: [Target; 20] = ECAddHomogOffsetGate::wires_ith_output(self.i) + .map(|i| Target::wire(self.row, i)) + .collect::>() + .try_into() + .map_err(|e| anyhow::anyhow!("{:?}", e))?; + + let computed_output = add_homog_offset(addend_0.0, addend_0.1, addend_1.0, addend_1.1) + .iter() + .flat_map( as FieldExtension<5>>::to_basefield_array) + .collect::>(); + + out_buffer.set_target_arr(&output_targets, &computed_output) + } + + fn serialize(&self, dst: &mut Vec, _common_data: &CommonCircuitData) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.i) + } + + fn deserialize(src: &mut Buffer, _common_data: &CommonCircuitData) -> IoResult { + let row = src.read_usize()?; + let i = src.read_usize()?; + Ok(Self { row, i }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct ECAddXuGenerator { + row: usize, +} + +impl SimpleGenerator for ECAddXuGenerator +where + F: Extendable, +{ + fn serialize( + &self, + dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + dst.write_usize(self.row) + } + + fn deserialize( + src: &mut Buffer, + _common_data: &CommonCircuitData, + ) -> IoResult + where + Self: Sized, + { + Ok(Self { + row: src.read_usize()?, + }) + } + + fn id(&self) -> String { + "ECAddXuGenerator".into() + } + + fn dependencies(&self) -> Vec { + (0..26).map(|i| Target::wire(self.row, i)).collect() + } + + fn run_once( + &self, + witness: &PartitionWitness, + out_buffer: &mut GeneratedValues, + ) -> anyhow::Result<()> { + let deps = self.dependencies(); + + let selectors_g: Vec = deps[0..3] + .iter() // binary selectors for g + .map(|&target| witness.get_target(target)) + .collect(); + + let selectors_y: Vec = deps[3..6] + .iter() // binary selectors for y + .map(|&target| witness.get_target(target)) + .collect(); + + // extract element of quintic extension of Goldilocks field from five consecutive targets + let extract_quintic = |start_idx: usize| -> QuinticExtension { + QuinticExtension::::from_basefield_array([ + witness.get_target(deps[start_idx]), + witness.get_target(deps[start_idx + 1]), + witness.get_target(deps[start_idx + 2]), + witness.get_target(deps[start_idx + 3]), + witness.get_target(deps[start_idx + 4]), + ]) + }; + + let g = Point::generator(); + + let g_x = g.x; + let g_u = g.u; + let y_x = extract_quintic(6); + let y_u = extract_quintic(11); + + let mut p_x = extract_quintic(16); + let mut p_u = extract_quintic(21); + + let mut write_quintic = + |start_wire: usize, value: &QuinticExtension| -> anyhow::Result<()> { + let array: [GoldilocksField; 5] = + QuinticExtension::::to_basefield_array(value); + for (j, &num) in array.iter().enumerate() { + out_buffer.set_target(Target::wire(self.row, start_wire + j), num)?; + } + Ok(()) + }; + + // Double and add three times. + // Write points from right to left so that the result of the fifth add + // lies on a routable wire and thus can be copied to the next row. + (0..3).try_for_each(|i| { + // Double, write to wires [106-30*i..116-30*i] + [p_x, p_u] = add_xu::<1, QuinticExtension>(p_x, p_u, p_x, p_u); + write_quintic(106 - 30 * i, &p_x)?; + write_quintic(111 - 30 * i, &p_u)?; + + // Possibly add g, depending on selector. Write to wires [96-30*i..106-30*i] + if selectors_g[i] == GoldilocksField::ONE { + [p_x, p_u] = add_xu::<1, QuinticExtension>(p_x, p_u, g_x, g_u); + } + write_quintic(96 - 30 * i, &p_x)?; + write_quintic(101 - 30 * i, &p_u)?; + + // Possibly add y, depending on selector. Write to wires [86-30*i..96-30*i] + if selectors_y[i] == GoldilocksField::ONE { + [p_x, p_u] = add_xu::<1, QuinticExtension>(p_x, p_u, y_x, y_u); + } + write_quintic(86 - 30 * i, &p_x)?; + write_quintic(91 - 30 * i, &p_u) + }) + } +} + +/// Gate that selectively carries out three rounds of the +/// double-and-add algorithm loop applied to the curve generator and a +/// given point. +#[derive(Clone)] +pub struct ECAddXuGate; + +impl ECAddXuGate { + const INPUTS_PER_OP: usize = 26; + const OUTPUTS_PER_OP: usize = 90; + const WIRES_PER_OP: usize = Self::INPUTS_PER_OP + Self::OUTPUTS_PER_OP; + const DEGREE: usize = 6; + const ID: &'static str = "ECAddXuGate"; + + pub fn apply( + builder: &mut CircuitBuilder, + targets: &[Target], + ) -> Vec + where + F: Extendable, + { + assert!(targets.len() == Self::INPUTS_PER_OP); + let (row, _) = builder.find_slot(ECAddXuGate::new(), &[], &[]); + for (i, &t) in targets.iter().enumerate() { + builder.connect(t, Target::wire(row, i)); + } + + (0..10) + .map(|i| Target::wire(row, Self::INPUTS_PER_OP + i)) + .collect() + } + + pub fn new() -> Self { + Self + } + + pub fn add_numerator_denominator( + wires: &[>::Extension], + ) -> Vec<>::Extension> + where + GoldilocksField: plonky2::field::extension::Extendable, { let mut ans = Vec::with_capacity(20); - let [x1, u1, x2, u2] = - array::from_fn(|j| QuinticTensor::from_base(array::from_fn(|i| wires[5 * j + i]))); - let out = add_homog_offset(x1, u1, x2, u2); + let x1 = QuinticTensor::from_base(wires[0..5].try_into().unwrap()); + let u1 = QuinticTensor::from_base(wires[5..10].try_into().unwrap()); + let x2 = QuinticTensor::from_base(wires[10..15].try_into().unwrap()); + let u2 = QuinticTensor::from_base(wires[15..20].try_into().unwrap()); + let out = add_homog(x1, u1, x2, u2); for v in out { ans.extend(v.to_base()); } ans } + + pub fn convert( + wires: &[>::Extension], + ) -> Vec<>::Extension> + where + GoldilocksField: plonky2::field::extension::Extendable, + { + let mut ans = Vec::with_capacity(10); + let x1 = QuinticTensor::from_base(wires[0..5].try_into().unwrap()); + let u1 = QuinticTensor::from_base(wires[5..10].try_into().unwrap()); + ans.extend(x1.to_base()); + ans.extend(u1.to_base()); + ans + } +} + +impl Gate for ECAddXuGate +where + F: Extendable, +{ + fn id(&self) -> String { + Self::ID.to_string() + } + + fn serialize( + &self, + _dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + Ok(()) + } + + fn deserialize( + _src: &mut Buffer<'_>, + _common_data: &CommonCircuitData, + ) -> IoResult + where + Self: Sized, + { + Ok(Self) + } + + fn eval_unfiltered( + &self, + vars: EvaluationVars<'_, GoldilocksField, D>, + ) -> Vec<>::Extension> { + let mut constraints = Vec::new(); + + let g = Point::generator(); + + let double_constraint = |i: usize, j: usize| { + let x1 = QuinticTensor::from_base(vars.local_wires[i..i + 5].try_into().unwrap()); + let u1 = QuinticTensor::from_base(vars.local_wires[i + 5..i + 10].try_into().unwrap()); + let [x, z, u, t] = add_homog(x1, u1, x1, u1); + let mut new_constraints: Vec< + >::Extension, + > = Vec::with_capacity(10); + let x2 = QuinticTensor::from_base(vars.local_wires[j..j + 5].try_into().unwrap()); + let u2 = QuinticTensor::from_base(vars.local_wires[j + 5..j + 10].try_into().unwrap()); + + let first_constraints: Vec<_> = (x2 * z - x).components.to_vec(); + let second_constraints: Vec<_> = (u2 * t - u).components.to_vec(); + + new_constraints.extend(&first_constraints); + new_constraints.extend(&second_constraints); + new_constraints + }; + + let select_and_add_constraint = |i: usize, j: usize, selector_index: usize, add_y: bool| { + let zero = + >::Extension::ZERO; + + let one = >::Extension::ONE; + let one_full = QuinticTensor::from_base([one, zero, zero, zero, zero]); + + let sel = vars.local_wires[selector_index]; + let sel_full = QuinticTensor::from_base([sel, zero, zero, zero, zero]); + + let x1 = QuinticTensor::from_base(vars.local_wires[i..i + 5].try_into().unwrap()); + let u1 = QuinticTensor::from_base(vars.local_wires[i + 5..i + 10].try_into().unwrap()); + + let (x2, u2); + if add_y { + // (using hardcoded location of y) + x2 = QuinticTensor::from_base(vars.local_wires[6..11].try_into().unwrap()); + u2 = QuinticTensor::from_base(vars.local_wires[11..16].try_into().unwrap()); + } else { + let mut base_array: [GoldilocksField; 5] = g.x.to_basefield_array(); + x2 = QuinticTensor::from_base(base_array.map(Into::into)); + base_array = g.u.to_basefield_array(); + u2 = QuinticTensor::from_base(base_array.map(Into::into)); + } + let [x, z, u, t] = add_homog(x1, u1, x2, u2); + + let mut new_constraints: Vec< + >::Extension, + > = Vec::with_capacity(10); + let x3 = QuinticTensor::from_base(vars.local_wires[j..j + 5].try_into().unwrap()); + let u3 = QuinticTensor::from_base(vars.local_wires[j + 5..j + 10].try_into().unwrap()); + + let first_constraints: Vec<_> = (x3 * z - sel_full * x + + (sel_full - one_full) * x1 * z) + .components + .to_vec(); + let second_constraints: Vec<_> = (u3 * t - sel_full * u + + (sel_full - one_full) * u1 * t) + .components + .to_vec(); + + new_constraints.extend_from_slice(&first_constraints[0..5]); + new_constraints.extend_from_slice(&second_constraints[0..5]); + new_constraints + }; + + constraints.extend(double_constraint(16, 106)); + constraints.extend(select_and_add_constraint(106, 96, 0, false)); + constraints.extend(select_and_add_constraint(96, 86, 3, true)); + + constraints.extend(double_constraint(86, 76)); + constraints.extend(select_and_add_constraint(76, 66, 1, false)); + constraints.extend(select_and_add_constraint(66, 56, 4, true)); + + constraints.extend(double_constraint(56, 46)); + constraints.extend(select_and_add_constraint(46, 36, 2, false)); + constraints.extend(select_and_add_constraint(36, 26, 5, true)); + + constraints + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets<'_, D>, + ) -> Vec> { + let mut constraints = Vec::new(); + + type Nnf = QuinticExtension; + + let zero = builder.zero_extension(); + + let g = Point::generator(); + let [g_x, g_u]: [[F; 5]; 2] = [g.x.to_basefield_array(), g.u.to_basefield_array()]; + let [g_x_ext_target, g_u_ext_target] = [ + array::from_fn(|i| builder.add_const_extension(zero, g_x[i])), + array::from_fn(|i| builder.add_const_extension(zero, g_u[i])), + ]; + + let double_constraint = + |builder: &mut CircuitBuilder, i: usize, j: usize| { + let x1 = array::from_fn(|k| vars.local_wires[i + k]); + let u1 = array::from_fn(|k| vars.local_wires[i + 5 + k]); + let [x, z, u, t] = homog_ec_target_add(builder, &x1, &u1, &x1, &u1); + + let mut new_constraints = Vec::>::with_capacity(10); + let x2 = array::from_fn(|k| vars.local_wires[j + k]); + let u2 = array::from_fn(|k| vars.local_wires[j + 5 + k]); + + let [expected_x, expected_u] = [ + nnf_mul_ext::(builder, &x2, &z), + nnf_mul_ext::(builder, &u2, &t), + ]; + let first_constraints = nnf_ext_target_sub::(builder, &expected_x, &x); + let second_constraints = nnf_ext_target_sub::(builder, &expected_u, &u); + + new_constraints.extend(&first_constraints); + new_constraints.extend(&second_constraints); + new_constraints + }; + + let select_and_add_constraint = |builder: &mut CircuitBuilder, + i: usize, + j: usize, + selector_index: usize, + add_y: bool| { + let one = builder.one_extension(); + let sel = vars.local_wires[selector_index]; + + let x1 = array::from_fn(|k| vars.local_wires[i + k]); + let u1 = array::from_fn(|k| vars.local_wires[i + 5 + k]); + + let (x2, u2) = if add_y { + // (using hardcoded location of y) + ( + array::from_fn(|k| vars.local_wires[6 + k]), + array::from_fn(|k| vars.local_wires[11 + k]), + ) + } else { + (g_x_ext_target, g_u_ext_target) + }; + + let [x, z, u, t] = homog_ec_target_add(builder, &x1, &u1, &x2, &u2); + + let mut new_constraints = Vec::>::with_capacity(10); + let x3 = array::from_fn(|k| vars.local_wires[j + k]); + let u3 = array::from_fn(|k| vars.local_wires[j + 5 + k]); + + let sel_minus_one = builder.sub_extension(sel, one); + let first_constraints = { + let term1 = nnf_mul_ext::(builder, &x3, &z); + let term2 = array::from_fn(|i| builder.mul_extension(sel, x[i])); + let term3_1 = array::from_fn(|i| builder.mul_extension(sel_minus_one, x1[i])); + let term3 = nnf_mul_ext::(builder, &term3_1, &z); + let partial_sum = nnf_ext_target_sub::(builder, &term1, &term2); + nnf_ext_target_add::(builder, &partial_sum, &term3) + }; + + let second_constraints = { + let term1 = nnf_mul_ext::(builder, &u3, &t); + let term2 = array::from_fn(|i| builder.mul_extension(sel, u[i])); + let term3_1 = array::from_fn(|i| builder.mul_extension(sel_minus_one, u1[i])); + let term3 = nnf_mul_ext::(builder, &term3_1, &t); + let partial_sum = nnf_ext_target_sub::(builder, &term1, &term2); + nnf_ext_target_add::(builder, &partial_sum, &term3) + }; + + new_constraints.extend(first_constraints); + new_constraints.extend(second_constraints); + new_constraints + }; + + constraints.extend(double_constraint(builder, 16, 106)); + constraints.extend(select_and_add_constraint(builder, 106, 96, 0, false)); + constraints.extend(select_and_add_constraint(builder, 96, 86, 3, true)); + + constraints.extend(double_constraint(builder, 86, 76)); + constraints.extend(select_and_add_constraint(builder, 76, 66, 1, false)); + constraints.extend(select_and_add_constraint(builder, 66, 56, 4, true)); + + constraints.extend(double_constraint(builder, 56, 46)); + constraints.extend(select_and_add_constraint(builder, 46, 36, 2, false)); + constraints.extend(select_and_add_constraint(builder, 36, 26, 5, true)); + + constraints + } + + fn generators( + &self, + row: usize, + _local_constants: &[GoldilocksField], + ) -> Vec> { + vec![WitnessGeneratorRef::new(ECAddXuGenerator { row }.adapter())] + } + + fn num_wires(&self) -> usize { + Self::WIRES_PER_OP + } + + fn degree(&self) -> usize { + Self::DEGREE + } + + fn num_ops(&self) -> usize { + 1 + } + + fn num_constants(&self) -> usize { + 0 + } + + fn num_constraints(&self) -> usize { + 90 + } +} + +// Useful auxiliary methods for defining the above gate follow. +fn nnf_ext_target_add>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + y: &[ExtensionTarget; DEG], +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + let sum_target = zip_eq(x, y) + .map(|(a, b)| builder.add_extension(*a, *b)) + .collect::>(); + array::from_fn(|i| sum_target[i]) +} + +fn nnf_ext_target_sub>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + y: &[ExtensionTarget; DEG], +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + let diff_target = zip_eq(x, y) + .map(|(a, b)| builder.sub_extension(*a, *b)) + .collect::>(); + array::from_fn(|i| diff_target[i]) +} + +fn nnf_ext_target_add_field_gen>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + factor: NNF::BaseField, +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + array::from_fn(|i| { + if i == 1 { + builder.add_const_extension(x[1], factor) + } else { + x[i] + } + }) +} + +fn nnf_ext_target_mul_field_gen>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + factor: NNF::BaseField, +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + array::from_fn(|i| { + if i == 0 { + builder.mul_const_extension(factor * NNF::W, x[DEG - 1]) + } else { + builder.mul_const_extension(factor, x[i - 1]) + } + }) +} + +fn nnf_ext_target_add_scalar>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + scal: NNF::BaseField, +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + array::from_fn(|i| { + if i == 0 { + builder.add_const_extension(x[0], scal) + } else { + x[i] + } + }) +} + +fn homog_ec_target_addition_terms( + builder: &mut CircuitBuilder, + x1: &[ExtensionTarget; 5], + u1: &[ExtensionTarget; 5], + x2: &[ExtensionTarget; 5], + u2: &[ExtensionTarget; 5], +) -> [[ExtensionTarget; 5]; 5] +where + F: Extendable, +{ + type Nnf = QuinticExtension; + + let t1 = nnf_mul_ext::(builder, x1, x2); + let t3 = nnf_mul_ext::(builder, u1, u2); + let t5 = nnf_ext_target_add::(builder, x1, x2); + let t6 = nnf_ext_target_add::(builder, u1, u2); + let t7 = nnf_ext_target_add_field_gen::(builder, &t1, Point::B1); + + let twice_t7 = nnf_ext_target_add::(builder, &t7, &t7); + let t5_mul_fg2b = + nnf_ext_target_mul_field_gen::(builder, &t5, Point::B1 + Point::B1); + let t5_mul_fg2b_plus_twice_t7 = + nnf_ext_target_add::(builder, &t5_mul_fg2b, &twice_t7); + let t9 = nnf_mul_ext::(builder, &t3, &t5_mul_fg2b_plus_twice_t7); + + let t5_plus_t7 = nnf_ext_target_add::(builder, &t5, &t7); + let twice_t3 = nnf_ext_target_add::(builder, &t3, &t3); + let twice_t3_plus_one = + nnf_ext_target_add_scalar::(builder, &twice_t3, GoldilocksField::ONE); + let t10 = nnf_mul_ext::(builder, &twice_t3_plus_one, &t5_plus_t7); + [t1, t6, t7, t9, t10] +} + +// TODO: Make this more generic? +/// Analogue of `add_homog` for extension targets. +fn homog_ec_target_add( + builder: &mut CircuitBuilder, + x1: &[ExtensionTarget; 5], + u1: &[ExtensionTarget; 5], + x2: &[ExtensionTarget; 5], + u2: &[ExtensionTarget; 5], +) -> [[ExtensionTarget; 5]; 4] +where + F: Extendable, +{ + type Nnf = QuinticExtension; + + let [t1, t6, t7, t9, t10] = homog_ec_target_addition_terms(builder, x1, u1, x2, u2); + + let t10_minus_t7 = nnf_ext_target_sub::(builder, &t10, &t7); + let x = nnf_ext_target_mul_field_gen::(builder, &t10_minus_t7, Point::B1); + + let z = nnf_ext_target_sub::(builder, &t7, &t9); + + let minus_t1 = array::from_fn(|i| builder.mul_const_extension(-F::ONE, t1[i])); + let minus_t1_plus_fgpb = + nnf_ext_target_add_field_gen::(builder, &minus_t1, Point::B1); + let u = nnf_mul_ext::(builder, &t6, &minus_t1_plus_fgpb); + + let t = nnf_ext_target_add::(builder, &t7, &t9); + + [x, z, u, t] +} + +/// Analogue of `add_homog_offset` for extension targets. +fn ec_target_add_homog_offset( + builder: &mut CircuitBuilder, + x1: &[ExtensionTarget; 5], + u1: &[ExtensionTarget; 5], + x2: &[ExtensionTarget; 5], + u2: &[ExtensionTarget; 5], +) -> [[ExtensionTarget; 5]; 4] +where + F: Extendable, +{ + type Nnf = QuinticExtension; + + let [t1, t6, t7, t9, t10] = homog_ec_target_addition_terms(builder, x1, u1, x2, u2); + + let t10_minus_t7 = nnf_ext_target_sub::(builder, &t10, &t7); + let x = nnf_ext_target_mul_field_gen::(builder, &t10_minus_t7, Point::B1); + + let z = nnf_ext_target_sub::(builder, &t1, &t9); + + let minus_t1 = array::from_fn(|i| builder.mul_const_extension(-F::ONE, t1[i])); + let minus_t1_plus_fgpb = + nnf_ext_target_add_field_gen::(builder, &minus_t1, Point::B1); + let u = nnf_mul_ext::(builder, &t6, &minus_t1_plus_fgpb); + + let t = nnf_ext_target_add::(builder, &t1, &t9); + + [x, z, u, t] } #[cfg(test)] @@ -58,40 +887,39 @@ mod test { plonk::{circuit_data::CircuitConfig, config::PoseidonGoldilocksConfig}, }; - use crate::backends::plonky2::primitives::ec::gates::{ - curve::ECAddHomogOffset, generic::GateAdapter, + use crate::backends::plonky2::primitives::ec::gates::curve::{ + ECAddHomogOffsetGate, ECAddXuGate, }; #[test] - fn test_recursion() -> Result<(), anyhow::Error> { + fn test_ec_add_gate() -> Result<(), anyhow::Error> { let config = CircuitConfig::standard_recursion_config(); - let gate = GateAdapter::::new_from_config(&config); + let gate = ECAddHomogOffsetGate::new_from_config(&config); test_eval_fns::<_, PoseidonGoldilocksConfig, _, 2>(gate) } #[test] - fn test_low_degree_orig() -> Result<(), anyhow::Error> { + fn test_ec_add_xu_gate() -> Result<(), anyhow::Error> { + let gate = ECAddXuGate::new(); + + test_eval_fns::<_, PoseidonGoldilocksConfig, _, 2>(gate) + } + + #[test] + fn test_ec_add_gate_low_degree() -> Result<(), anyhow::Error> { let config = CircuitConfig::standard_recursion_config(); - let gate = GateAdapter::::new_from_config(&config); + let gate = ECAddHomogOffsetGate::new_from_config(&config); test_low_degree::<_, _, 2>(gate); Ok(()) } #[test] - fn test_low_degree_recursive() -> Result<(), anyhow::Error> { - let config = CircuitConfig::standard_recursion_config(); - let orig_gate = GateAdapter::::new_from_config(&config); + fn test_ec_add_xu_gate_low_degree() -> Result<(), anyhow::Error> { + let gate = ECAddXuGate::new(); - test_low_degree::<_, _, 2>(orig_gate.recursive_gate()); + test_low_degree::<_, _, 2>(gate); Ok(()) } - - #[test] - fn test_double_recursion() -> Result<(), anyhow::Error> { - let config = CircuitConfig::standard_recursion_config(); - let orig_gate = GateAdapter::::new_from_config(&config); - test_eval_fns::<_, PoseidonGoldilocksConfig, _, 2>(orig_gate.recursive_gate()) - } } diff --git a/src/backends/plonky2/primitives/ec/gates/field.rs b/src/backends/plonky2/primitives/ec/gates/field.rs index 164f968..93c5d9b 100644 --- a/src/backends/plonky2/primitives/ec/gates/field.rs +++ b/src/backends/plonky2/primitives/ec/gates/field.rs @@ -1,19 +1,33 @@ use std::{ array, marker::PhantomData, - ops::{Add, Mul, Neg, Sub}, + ops::{Add, Mul, Neg, Range, Sub}, }; +use itertools::zip_eq; use plonky2::{ field::{ extension::{quintic::QuinticExtension, Extendable, FieldExtension, OEF}, goldilocks_field::GoldilocksField, types::Field, }, + gates::gate::Gate, hash::hash_types::RichField, + iop::{ + ext_target::ExtensionTarget, + generator::{GeneratedValues, SimpleGenerator, WitnessGeneratorRef}, + target::Target, + witness::{PartitionWitness, Witness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{CircuitConfig, CommonCircuitData}, + vars::{EvaluationTargets, EvaluationVars}, + }, + util::serialization::{Buffer, IoResult, Read, Write}, }; -use crate::backends::plonky2::primitives::ec::{curve::ECFieldExt, gates::generic::SimpleGate}; +use crate::backends::plonky2::primitives::ec::curve::ECFieldExt; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct TensorProduct @@ -137,42 +151,284 @@ where } } -#[derive(Debug, Clone, Copy, Default)] -pub struct NNFMulSimple> { - _phantom_data: PhantomData NNF>, +/// A gate which can perform a multiplication on OEF. +/// If the config has enough routed wires, it can support several such operations in one gate. +#[derive(Debug, Clone)] +pub struct NNFMulGate> { + /// Number of multiplications performed by the gate. + pub num_ops: usize, + _phantom_data: PhantomData, } -impl> NNFMulSimple { - pub fn new() -> Self { +impl> NNFMulGate { + pub const fn new_from_config(config: &CircuitConfig) -> Self { Self { + num_ops: Self::num_ops(config), _phantom_data: PhantomData, } } + + /// Determine the maximum number of operations that can fit in one gate for the given config. + pub(crate) const fn num_ops(config: &CircuitConfig) -> usize { + let wires_per_op = 3 * DEG; + config.num_routed_wires / wires_per_op + } + + pub(crate) const fn wires_ith_multiplicand_0(i: usize) -> Range { + 3 * DEG * i..3 * DEG * i + DEG + } + pub(crate) const fn wires_ith_multiplicand_1(i: usize) -> Range { + 3 * DEG * i + DEG..3 * DEG * i + 2 * DEG + } + pub(crate) const fn wires_ith_output(i: usize) -> Range { + 3 * DEG * i + 2 * DEG..3 * DEG * (i + 1) + } } -impl SimpleGate for NNFMulSimple +impl> Gate + for NNFMulGate where - NNF: OEF, - NNF::BaseField: RichField + Extendable<1>, + NNF::BaseField: RichField + Extendable, { - type F = NNF::BaseField; - const INPUTS_PER_OP: usize = 2 * NNF_DEG; - const OUTPUTS_PER_OP: usize = NNF_DEG; - const DEGREE: usize = 2; - const ID: &'static str = "NNFSimpleGate"; - - fn eval( - wires: &[>::Extension], - ) -> Vec<>::Extension> - where - Self::F: Extendable, - { - let x: TensorProduct>::Extension> = - TensorProduct::new(array::from_fn(|i| wires[i])); - let y = TensorProduct::new(array::from_fn(|i| wires[NNF_DEG + i])); - let prod = x * y; - prod.components.into() + fn id(&self) -> String { + format!("{self:?}") } + + fn serialize( + &self, + dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + dst.write_usize(self.num_ops) + } + + fn deserialize( + src: &mut Buffer, + _common_data: &CommonCircuitData, + ) -> IoResult { + let num_ops = src.read_usize()?; + Ok(Self { + num_ops, + _phantom_data: PhantomData, + }) + } + + fn eval_unfiltered( + &self, + vars: EvaluationVars, + ) -> Vec<>::Extension> { + let mut constraints = Vec::with_capacity(self.num_ops * DEG); + for i in 0..self.num_ops { + let multiplicand_0: TensorProduct = TensorProduct::new( + vars.local_wires[Self::wires_ith_multiplicand_0(i)] + .try_into() + .unwrap(), + ); + let multiplicand_1 = TensorProduct::new( + vars.local_wires[Self::wires_ith_multiplicand_1(i)] + .try_into() + .unwrap(), + ); + let output = TensorProduct::new( + vars.local_wires[Self::wires_ith_output(i)] + .try_into() + .unwrap(), + ); + let computed_output = multiplicand_0 * multiplicand_1; + + constraints.extend((output - computed_output).components); + } + + constraints + } + + fn eval_unfiltered_circuit( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_ops * DEG); + for i in 0..self.num_ops { + let multiplicand_0: [ExtensionTarget; DEG] = vars.local_wires + [Self::wires_ith_multiplicand_0(i)] + .try_into() + .unwrap(); + let multiplicand_1: [ExtensionTarget; DEG] = vars.local_wires + [Self::wires_ith_multiplicand_1(i)] + .try_into() + .unwrap(); + let output: [ExtensionTarget; DEG] = vars.local_wires[Self::wires_ith_output(i)] + .try_into() + .unwrap(); + let computed_output = + nnf_mul_ext::<_, DEG, NNF>(builder, &multiplicand_0, &multiplicand_1); + + let diffs = zip_eq(output, computed_output) + .map(|(o, co)| builder.sub_extension(o, co)) + .collect::>(); + constraints.extend(diffs); + } + + constraints + } + + fn generators( + &self, + row: usize, + _local_constants: &[NNF::BaseField], + ) -> Vec> { + (0..self.num_ops) + .map(|i| { + WitnessGeneratorRef::new( + NNFMulGenerator:: { + row, + i, + phantom_data: PhantomData, + } + .adapter(), + ) + }) + .collect() + } + + fn num_wires(&self) -> usize { + self.num_ops * 3 * DEG + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + self.num_ops * DEG + } +} + +#[derive(Clone, Debug, Default)] +pub struct NNFMulGenerator> { + row: usize, + i: usize, + phantom_data: PhantomData, +} + +impl> SimpleGenerator + for NNFMulGenerator +where + NNF::BaseField: RichField + Extendable, +{ + fn id(&self) -> String { + "NNFMulGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + NNFMulGate::::wires_ith_multiplicand_0(self.i) + .chain(NNFMulGate::::wires_ith_multiplicand_1(self.i)) + .map(|i| Target::wire(self.row, i)) + .collect() + } + + fn run_once( + &self, + witness: &PartitionWitness, + out_buffer: &mut GeneratedValues, + ) -> anyhow::Result<()> { + let extract_nnf = |range: Range| -> anyhow::Result { + let components: [NNF::BaseField; DEG] = range + .map(|i| witness.get_target(Target::wire(self.row, i))) + .collect::>() + .try_into() + .map_err(|e| anyhow::anyhow!("{:?}", e))?; + Ok(NNF::from_basefield_array(components)) + }; + + let multiplicand_0 = + extract_nnf(NNFMulGate::::wires_ith_multiplicand_0(self.i))?; + let multiplicand_1 = + extract_nnf(NNFMulGate::::wires_ith_multiplicand_1(self.i))?; + + let output_targets: [Target; DEG] = NNFMulGate::::wires_ith_output(self.i) + .map(|i| Target::wire(self.row, i)) + .collect::>() + .try_into() + .map_err(|e| anyhow::anyhow!("{:?}", e))?; + + let computed_output = multiplicand_0 * multiplicand_1; + + out_buffer.set_target_arr(&output_targets, &computed_output.to_basefield_array()) + } + + fn serialize( + &self, + dst: &mut Vec, + _common_data: &CommonCircuitData, + ) -> IoResult<()> { + dst.write_usize(self.row)?; + dst.write_usize(self.i) + } + + fn deserialize( + src: &mut Buffer, + _common_data: &CommonCircuitData, + ) -> IoResult { + let row = src.read_usize()?; + let i = src.read_usize()?; + Ok(Self { + row, + i, + phantom_data: PhantomData, + }) + } +} + +pub(crate) fn nnf_mul_ext>( + builder: &mut CircuitBuilder, + x: &[ExtensionTarget; DEG], + y: &[ExtensionTarget; DEG], +) -> [ExtensionTarget; DEG] +where + NNF::BaseField: RichField + Extendable, +{ + let zero = builder.zero_extension(); + let mul_targets = (0..DEG - 1) + .map(|k| { + let term1 = (0..=k) + .map(|i| builder.mul_extension(x[i], y[k - i])) + .collect::>() + .into_iter() + .reduce(|sum, summand| builder.add_extension(sum, summand)) + .expect("Missing summands"); + let term2 = (k + 1..DEG) + .map(|i| { + builder.arithmetic_extension( + NNF::W, + NNF::BaseField::ZERO, + x[i], + y[DEG + k - i], + zero, + ) + }) + .collect::>() + .into_iter() + .reduce(|sum, summand| builder.add_extension(sum, summand)) + .expect("Missing summands"); + builder.add_extension(term1, term2) + }) + .collect::>() + .into_iter() + .chain(std::iter::once( + (0..DEG) + .map(|i| builder.mul_extension(x[i], y[DEG - 1 - i])) + .collect::>() + .into_iter() + .reduce(|sum, summand| builder.add_extension(sum, summand)) + .expect("Missing summands"), + )) + .collect::>(); + std::array::from_fn(|i| mul_targets[i]) } #[cfg(test)] @@ -183,52 +439,22 @@ mod test { plonk::{circuit_data::CircuitConfig, config::PoseidonGoldilocksConfig}, }; - use crate::backends::plonky2::primitives::ec::gates::{ - field::NNFMulSimple, generic::GateAdapter, - }; + use crate::backends::plonky2::{basetypes::D, primitives::ec::gates::field::NNFMulGate}; #[test] - fn test_recursion() -> Result<(), anyhow::Error> { + fn test_nnf_mul_gate() -> Result<(), anyhow::Error> { let config = CircuitConfig::standard_recursion_config(); - let gate = - GateAdapter::>>::new_from_config( - &config, - ); + let gate = NNFMulGate::>::new_from_config(&config); test_eval_fns::<_, PoseidonGoldilocksConfig, _, 2>(gate) } #[test] - fn test_low_degree_orig() -> Result<(), anyhow::Error> { + fn test_nnf_mul_gate_low_degree() -> Result<(), anyhow::Error> { let config = CircuitConfig::standard_recursion_config(); - let gate = - GateAdapter::>>::new_from_config( - &config, - ); + let gate = NNFMulGate::>::new_from_config(&config); test_low_degree::<_, _, 2>(gate); Ok(()) } - - #[test] - fn test_low_degree_recursive() -> Result<(), anyhow::Error> { - let config = CircuitConfig::standard_recursion_config(); - let orig_gate = - GateAdapter::>>::new_from_config( - &config, - ); - - test_low_degree::<_, _, 2>(orig_gate.recursive_gate()); - Ok(()) - } - - #[test] - fn test_double_recursion() -> Result<(), anyhow::Error> { - let config = CircuitConfig::standard_recursion_config(); - let orig_gate = - GateAdapter::>>::new_from_config( - &config, - ); - test_eval_fns::<_, PoseidonGoldilocksConfig, _, 2>(orig_gate.recursive_gate()) - } } diff --git a/src/backends/plonky2/primitives/ec/schnorr.rs b/src/backends/plonky2/primitives/ec/schnorr.rs index df3336e..f6b51a3 100644 --- a/src/backends/plonky2/primitives/ec/schnorr.rs +++ b/src/backends/plonky2/primitives/ec/schnorr.rs @@ -30,7 +30,7 @@ use crate::{ deserialize_bytes, primitives::ec::{ bits::{BigUInt320Target, CircuitBuilderBits}, - curve::{CircuitBuilderElliptic, PointTarget, WitnessWriteCurve, GROUP_ORDER}, + curve::{CircuitBuilderSignature, PointTarget, WitnessWriteCurve, GROUP_ORDER}, }, serialize_bytes, Error, }, @@ -155,14 +155,14 @@ impl SignatureTarget { msg: HashOutTarget, public_key: &PointTarget, ) -> BoolTarget { - let g = builder.constant_point(Point::generator()); let sig1_bits = self.s.bits; let sig2_bits = self.e.bits; - let r = builder.linear_combination_points(&sig1_bits, &sig2_bits, &g, public_key); + let r = builder.linear_combination_point_gen(&sig1_bits, &sig2_bits, public_key); let u_arr = r.u.components; let inputs = u_arr.into_iter().chain(msg.elements).collect::>(); let e_hash = hash_array_circuit(builder, &inputs); let e = builder.field_elements_to_biguint(&e_hash); + builder.is_equal_slice(&self.e.limbs, &e.limbs) } } @@ -405,22 +405,38 @@ mod test { #[test] fn test_verify_signature_circuit() -> Result<(), anyhow::Error> { - let (public_key, msg, sig) = gen_signed_message(); + let (public_key, msg, sig) = gen_signed_message(); // gets signed message let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); + let key_t = builder.add_virtual_point_target(); let msg_t = builder.add_virtual_hash(); let sig_t = SignatureTarget::add_virtual_target(&mut builder); + let verified = sig_t.verify(&mut builder, msg_t, &key_t); + builder.assert_one(verified.target); let mut pw = PartialWitness::new(); pw.set_point_target(&key_t, &public_key)?; pw.set_hash_target(msg_t, msg.0.into())?; pw.set_biguint320_target(&sig_t.s, &sig.s)?; pw.set_biguint320_target(&sig_t.e, &sig.e)?; + + println!("Building circuit..."); + let start = std::time::Instant::now(); let data = builder.build::(); + println!("Circuit built in {:?}", start.elapsed()); + + println!("Generating proof..."); + let start = std::time::Instant::now(); let proof = data.prove(pw)?; + println!("Proof generated in {:?}", start.elapsed()); + + println!("Verifying proof..."); + let start = std::time::Instant::now(); data.verify(proof)?; + println!("Proof verified in {:?}", start.elapsed()); + Ok(()) } diff --git a/src/backends/plonky2/recursion/circuit.rs b/src/backends/plonky2/recursion/circuit.rs index 8155307..d7cf846 100644 --- a/src/backends/plonky2/recursion/circuit.rs +++ b/src/backends/plonky2/recursion/circuit.rs @@ -38,9 +38,6 @@ use crate::{ backends::plonky2::{ basetypes::{C, D}, error::Result, - primitives::ec::gates::{ - curve::ECAddHomogOffset, field::NNFMulSimple, generic::GateAdapter, - }, }, measure_gates_begin, measure_gates_end, middleware::F, @@ -364,9 +361,6 @@ fn coset_interpolation_gate( /// NOTE: The overhead between verifying any proof with just the `NoopGate` and verifying a proof /// with all these standard gates is about 400 num_gates (rows), no matter the circuit size. fn standard_gates(config: &CircuitConfig) -> Vec> { - let nnf_mul_simple = - GateAdapter::>>::new_from_config(config); - let ec_add_homog_offset = GateAdapter::::new_from_config(config); vec![ GateRef::new(plonky2::gates::noop::NoopGate {}), GateRef::new(plonky2::gates::constant::ConstantGate::new( @@ -391,10 +385,19 @@ fn standard_gates(config: &CircuitConfig) -> Vec> { GateRef::new(plonky2::gates::random_access::RandomAccessGate::new_from_config(config, 4)), GateRef::new(plonky2::gates::random_access::RandomAccessGate::new_from_config(config, 5)), GateRef::new(plonky2::gates::random_access::RandomAccessGate::new_from_config(config, 6)), - GateRef::new(nnf_mul_simple.recursive_gate()), - GateRef::new(nnf_mul_simple), - GateRef::new(ec_add_homog_offset.recursive_gate()), - GateRef::new(ec_add_homog_offset), + GateRef::new( + crate::backends::plonky2::primitives::ec::gates::field::NNFMulGate::< + D, + 5, + QuinticExtension, + >::new_from_config(config), + ), + GateRef::new( + crate::backends::plonky2::primitives::ec::gates::curve::ECAddXuGate::new(), + ), + GateRef::new( + crate::backends::plonky2::primitives::ec::gates::curve::ECAddHomogOffsetGate::new_from_config(config), + ), GateRef::new(plonky2::gates::exponentiation::ExponentiationGate::new_from_config(config)), // It would be better do `CosetInterpolationGate::with_max_degree(4, 6)` but unfortunately // that plonk2 method is `pub(crate)`, so we need to get around that somehow. diff --git a/src/backends/plonky2/serialization.rs b/src/backends/plonky2/serialization.rs index 567ba9b..bfe534a 100644 --- a/src/backends/plonky2/serialization.rs +++ b/src/backends/plonky2/serialization.rs @@ -27,9 +27,10 @@ use crate::backends::plonky2::{ curve::PointSquareRootGenerator, field::QuotientGeneratorOEF, gates::{ - curve::ECAddHomogOffset, - field::NNFMulSimple, - generic::{GateAdapter, RecursiveGateAdapter, RecursiveGenerator}, + curve::{ + ECAddHomogOffsetGate, ECAddHomogOffsetGenerator, ECAddXuGate, ECAddXuGenerator, + }, + field::{NNFMulGate, NNFMulGenerator}, }, }, }; @@ -56,10 +57,9 @@ impl GateSerializer for Pod2GateSerializer { ReducingExtensionGate, ReducingGate, // pod2 custom gates - GateAdapter::>>, - RecursiveGateAdapter::>>, - GateAdapter::, - RecursiveGateAdapter::, + NNFMulGate::>, + ECAddXuGate, + ECAddHomogOffsetGate, ComparisonGate:: } } @@ -127,10 +127,9 @@ impl WitnessGeneratorSerializer for Pod2GeneratorSerializer { QuotientGeneratorOEF<5, QuinticExtension>, PointSquareRootGenerator, ConditionalZeroGenerator, - RecursiveGenerator>>, - RecursiveGenerator<1, NNFMulSimple<5, QuinticExtension>>, - RecursiveGenerator, - RecursiveGenerator<1, ECAddHomogOffset>, + NNFMulGenerator::>, + ECAddXuGenerator, + ECAddHomogOffsetGenerator, ComparisonGenerator, TableGetGenerator }