diff --git a/plonky2/src/curve/glv.rs b/plonky2/src/curve/glv.rs index 11172be0..aeeb463e 100644 --- a/plonky2/src/curve/glv.rs +++ b/plonky2/src/curve/glv.rs @@ -8,14 +8,14 @@ use crate::curve::curve_msm::msm_parallel; use crate::curve::curve_types::{AffinePoint, ProjectivePoint}; use crate::curve::secp256k1::Secp256K1; -pub const BETA: Secp256K1Base = Secp256K1Base([ +pub const GLV_BETA: Secp256K1Base = Secp256K1Base([ 13923278643952681454, 11308619431505398165, 7954561588662645993, 8856726876819556112, ]); -const S: Secp256K1Scalar = Secp256K1Scalar([ +pub const GLV_S: Secp256K1Scalar = Secp256K1Scalar([ 16069571880186789234, 1310022930574435960, 11900229862571533402, @@ -52,7 +52,7 @@ pub fn decompose_secp256k1_scalar( let k1_raw = k - c1 * A1 - c2 * A2; let k2_raw = c1 * MINUS_B1 - c2 * B2; - debug_assert!(k1_raw + S * k2_raw == k); + debug_assert!(k1_raw + GLV_S * k2_raw == k); let two = BigUint::from_slice(&[2]); let k1_neg = k1_raw.to_canonical_biguint() > p.clone() / two.clone(); @@ -80,7 +80,7 @@ pub fn glv_mul(p: ProjectivePoint, k: Secp256K1Scalar) -> ProjectiveP let p_affine = p.to_affine(); let sp = AffinePoint:: { - x: p_affine.x * BETA, + x: p_affine.x * GLV_BETA, y: p_affine.y, zero: p_affine.zero, }; @@ -102,7 +102,7 @@ mod tests { use plonky2_field::secp256k1_scalar::Secp256K1Scalar; use crate::curve::curve_types::{Curve, CurveScalar}; - use crate::curve::glv::{decompose_secp256k1_scalar, glv_mul, S}; + use crate::curve::glv::{decompose_secp256k1_scalar, glv_mul, GLV_S}; use crate::curve::secp256k1::Secp256K1; #[test] @@ -113,7 +113,7 @@ mod tests { let m1 = if k1_neg { -one } else { one }; let m2 = if k2_neg { -one } else { one }; - assert!(k1 * m1 + S * k2 * m2 == k); + assert!(k1 * m1 + GLV_S * k2 * m2 == k); Ok(()) } diff --git a/plonky2/src/gadgets/curve.rs b/plonky2/src/gadgets/curve.rs index aa8c4112..e4e66a4e 100644 --- a/plonky2/src/gadgets/curve.rs +++ b/plonky2/src/gadgets/curve.rs @@ -76,17 +76,10 @@ impl, const D: usize> CircuitBuilder { p: &AffinePointTarget, b: BoolTarget, ) -> AffinePointTarget { - let not_b = self.not(b); - let neg = self.curve_neg(p); - let x_if_true = self.mul_nonnative_by_bool(&neg.x, b); - let y_if_true = self.mul_nonnative_by_bool(&neg.y, b); - let x_if_false = self.mul_nonnative_by_bool(&p.x, not_b); - let y_if_false = self.mul_nonnative_by_bool(&p.y, not_b); - - let x = self.add_nonnative(&x_if_true, &x_if_false); - let y = self.add_nonnative(&y_if_true, &y_if_false); - - AffinePointTarget { x, y } + AffinePointTarget { + x: p.x.clone(), + y: self.nonnative_conditional_neg(&p.y, b), + } } pub fn curve_double(&mut self, p: &AffinePointTarget) -> AffinePointTarget { diff --git a/plonky2/src/gadgets/curve_fixed_base.rs b/plonky2/src/gadgets/curve_fixed_base.rs new file mode 100644 index 00000000..f28e45d1 --- /dev/null +++ b/plonky2/src/gadgets/curve_fixed_base.rs @@ -0,0 +1,110 @@ +use num::BigUint; +use plonky2_field::extension_field::Extendable; + +use crate::curve::curve_types::{AffinePoint, Curve, CurveScalar}; +use crate::field::field_types::Field; +use crate::gadgets::curve::AffinePointTarget; +use crate::gadgets::nonnative::NonNativeTarget; +use crate::hash::hash_types::RichField; +use crate::hash::keccak::KeccakHash; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{GenericHashOut, Hasher}; + +impl, const D: usize> CircuitBuilder { + /// Compute windowed fixed-base scalar multiplication, using a 4-bit window. + pub fn fixed_base_curve_mul( + &mut self, + base: AffinePoint, + scalar: &NonNativeTarget, + ) -> AffinePointTarget { + // Holds `(16^i) * base` for `i=0..scalar.value.limbs.len() * 8`. + let scaled_base = (0..scalar.value.limbs.len() * 8).scan(base, |acc, _| { + let tmp = *acc; + for _ in 0..4 { + *acc = acc.double(); + } + Some(tmp) + }); + + let limbs = self.split_nonnative_to_4_bit_limbs(scalar); + + let hash_0 = KeccakHash::<32>::hash_no_pad(&[F::ZERO]); + let hash_0_scalar = C::ScalarField::from_biguint(BigUint::from_bytes_le( + &GenericHashOut::::to_bytes(&hash_0), + )); + let rando = (CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE).to_affine(); + + let zero = self.zero(); + let mut result = self.constant_affine_point(rando); + // `s * P = sum s_i * P_i` with `P_i = (16^i) * P` and `s = sum s_i * (16^i)`. + for (limb, point) in limbs.into_iter().zip(scaled_base) { + // `muls_point[t] = t * P_i` for `t=0..16`. + let muls_point = (0..16) + .scan(AffinePoint::ZERO, |acc, _| { + let tmp = *acc; + *acc = (point + *acc).to_affine(); + Some(tmp) + }) + .map(|p| self.constant_affine_point(p)) + .collect::>(); + let is_zero = self.is_equal(limb, zero); + let should_add = self.not(is_zero); + // `r = s_i * P_i` + let r = self.random_access_curve_points(limb, muls_point); + result = self.curve_conditional_add(&result, &r, should_add); + } + + let to_add = self.constant_affine_point(-rando); + self.curve_add(&result, &to_add) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::field_types::PrimeField; + use plonky2_field::secp256k1_scalar::Secp256K1Scalar; + + use crate::curve::curve_types::{Curve, CurveScalar}; + use crate::curve::secp256k1::Secp256K1; + use crate::field::field_types::Field; + use crate::iop::witness::{PartialWitness, Witness}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + #[test] + #[ignore] + fn test_fixed_base() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_ecc_config(); + + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let g = Secp256K1::GENERATOR_AFFINE; + let n = Secp256K1Scalar::rand(); + + let res = (CurveScalar(n) * g.to_projective()).to_affine(); + let res_expected = builder.constant_affine_point(res); + builder.curve_assert_valid(&res_expected); + + let n_target = builder.add_virtual_nonnative_target::(); + pw.set_biguint_target(&n_target.value, &n.to_canonical_biguint()); + + let res_target = builder.fixed_base_curve_mul(g, &n_target); + builder.curve_assert_valid(&res_target); + + builder.connect_affine_point(&res_target, &res_expected); + + dbg!(builder.num_gates()); + let data = builder.build::(); + let proof = data.prove(pw).unwrap(); + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/gadgets/curve_msm.rs b/plonky2/src/gadgets/curve_msm.rs new file mode 100644 index 00000000..fba7c229 --- /dev/null +++ b/plonky2/src/gadgets/curve_msm.rs @@ -0,0 +1,133 @@ +use num::BigUint; +use plonky2_field::extension_field::Extendable; + +use crate::curve::curve_types::{Curve, CurveScalar}; +use crate::field::field_types::Field; +use crate::gadgets::curve::AffinePointTarget; +use crate::gadgets::nonnative::NonNativeTarget; +use crate::hash::hash_types::RichField; +use crate::hash::keccak::KeccakHash; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::config::{GenericHashOut, Hasher}; + +impl, const D: usize> CircuitBuilder { + /// Computes `n*p + m*q` using windowed MSM, with a 2-bit window. + /// See Algorithm 9.23 in Handbook of Elliptic and Hyperelliptic Curve Cryptography for a + /// description. + /// Note: Doesn't work if `p == q`. + pub fn curve_msm( + &mut self, + p: &AffinePointTarget, + q: &AffinePointTarget, + n: &NonNativeTarget, + m: &NonNativeTarget, + ) -> AffinePointTarget { + let limbs_n = self.split_nonnative_to_2_bit_limbs(n); + let limbs_m = self.split_nonnative_to_2_bit_limbs(m); + assert_eq!(limbs_n.len(), limbs_m.len()); + let num_limbs = limbs_n.len(); + + let hash_0 = KeccakHash::<32>::hash_no_pad(&[F::ZERO]); + let hash_0_scalar = C::ScalarField::from_biguint(BigUint::from_bytes_le( + &GenericHashOut::::to_bytes(&hash_0), + )); + let rando = (CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE).to_affine(); + let rando_t = self.constant_affine_point(rando); + let neg_rando = self.constant_affine_point(-rando); + + // Precomputes `precomputation[i + 4*j] = i*p + j*q` for `i,j=0..4`. + let mut precomputation = vec![p.clone(); 16]; + let mut cur_p = rando_t.clone(); + let mut cur_q = rando_t.clone(); + for i in 0..4 { + precomputation[i] = cur_p.clone(); + precomputation[4 * i] = cur_q.clone(); + cur_p = self.curve_add(&cur_p, p); + cur_q = self.curve_add(&cur_q, q); + } + for i in 1..4 { + precomputation[i] = self.curve_add(&precomputation[i], &neg_rando); + precomputation[4 * i] = self.curve_add(&precomputation[4 * i], &neg_rando); + } + for i in 1..4 { + for j in 1..4 { + precomputation[i + 4 * j] = + self.curve_add(&precomputation[i], &precomputation[4 * j]); + } + } + + let four = self.constant(F::from_canonical_usize(4)); + + let zero = self.zero(); + let mut result = rando_t; + for (limb_n, limb_m) in limbs_n.into_iter().zip(limbs_m).rev() { + result = self.curve_repeated_double(&result, 2); + let index = self.mul_add(four, limb_m, limb_n); + let r = self.random_access_curve_points(index, precomputation.clone()); + let is_zero = self.is_equal(index, zero); + let should_add = self.not(is_zero); + result = self.curve_conditional_add(&result, &r, should_add); + } + let starting_point_multiplied = (0..2 * num_limbs).fold(rando, |acc, _| acc.double()); + let to_add = self.constant_affine_point(-starting_point_multiplied); + result = self.curve_add(&result, &to_add); + + result + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::secp256k1_scalar::Secp256K1Scalar; + + use crate::curve::curve_types::{Curve, CurveScalar}; + use crate::curve::secp256k1::Secp256K1; + use crate::field::field_types::Field; + use crate::iop::witness::PartialWitness; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::CircuitConfig; + use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::plonk::verifier::verify; + + #[test] + #[ignore] + fn test_curve_msm() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_ecc_config(); + + let pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let p = + (CurveScalar(Secp256K1Scalar::rand()) * Secp256K1::GENERATOR_PROJECTIVE).to_affine(); + let q = + (CurveScalar(Secp256K1Scalar::rand()) * Secp256K1::GENERATOR_PROJECTIVE).to_affine(); + let n = Secp256K1Scalar::rand(); + let m = Secp256K1Scalar::rand(); + + let res = + (CurveScalar(n) * p.to_projective() + CurveScalar(m) * q.to_projective()).to_affine(); + let res_expected = builder.constant_affine_point(res); + builder.curve_assert_valid(&res_expected); + + let p_target = builder.constant_affine_point(p); + let q_target = builder.constant_affine_point(q); + let n_target = builder.constant_nonnative(n); + let m_target = builder.constant_nonnative(m); + + let res_target = builder.curve_msm(&p_target, &q_target, &n_target, &m_target); + builder.curve_assert_valid(&res_target); + + builder.connect_affine_point(&res_target, &res_expected); + + dbg!(builder.num_gates()); + let data = builder.build::(); + let proof = data.prove(pw).unwrap(); + + verify(proof, &data.verifier_only, &data.common) + } +} diff --git a/plonky2/src/gadgets/curve_windowed_mul.rs b/plonky2/src/gadgets/curve_windowed_mul.rs index 879e1ade..46c663a0 100644 --- a/plonky2/src/gadgets/curve_windowed_mul.rs +++ b/plonky2/src/gadgets/curve_windowed_mul.rs @@ -44,12 +44,21 @@ impl, const D: usize> CircuitBuilder { access_index: Target, v: Vec>, ) -> AffinePointTarget { - let num_limbs = v[0].x.value.num_limbs(); + let num_limbs = C::BaseField::BITS / 32; + let zero = self.zero_u32(); let x_limbs: Vec> = (0..num_limbs) - .map(|i| v.iter().map(|p| p.x.value.limbs[i].0).collect()) + .map(|i| { + v.iter() + .map(|p| p.x.value.limbs.get(i).unwrap_or(&zero).0) + .collect() + }) .collect(); let y_limbs: Vec> = (0..num_limbs) - .map(|i| v.iter().map(|p| p.y.value.limbs[i].0).collect()) + .map(|i| { + v.iter() + .map(|p| p.y.value.limbs.get(i).unwrap_or(&zero).0) + .collect() + }) .collect(); let selected_x_limbs: Vec<_> = x_limbs diff --git a/plonky2/src/gadgets/ecdsa.rs b/plonky2/src/gadgets/ecdsa.rs index 0a95e189..a376e56a 100644 --- a/plonky2/src/gadgets/ecdsa.rs +++ b/plonky2/src/gadgets/ecdsa.rs @@ -1,6 +1,9 @@ use std::marker::PhantomData; +use plonky2_field::secp256k1_scalar::Secp256K1Scalar; + use crate::curve::curve_types::Curve; +use crate::curve::secp256k1::Secp256K1; use crate::field::extension_field::Extendable; use crate::gadgets::curve::AffinePointTarget; use crate::gadgets::nonnative::NonNativeTarget; @@ -20,11 +23,11 @@ pub struct ECDSASignatureTarget { } impl, const D: usize> CircuitBuilder { - pub fn verify_message( + pub fn verify_message( &mut self, - msg: NonNativeTarget, - sig: ECDSASignatureTarget, - pk: ECDSAPublicKeyTarget, + msg: NonNativeTarget, + sig: ECDSASignatureTarget, + pk: ECDSAPublicKeyTarget, ) { let ECDSASignatureTarget { r, s } = sig; @@ -34,12 +37,11 @@ impl, const D: usize> CircuitBuilder { let u1 = self.mul_nonnative(&msg, &c); let u2 = self.mul_nonnative(&r, &c); - let g = self.constant_affine_point(C::GENERATOR_AFFINE); - let point1 = self.curve_scalar_mul(&g, &u1); - let point2 = self.curve_scalar_mul(&pk.0, &u2); + let point1 = self.fixed_base_curve_mul(Secp256K1::GENERATOR_AFFINE, &u1); + let point2 = self.glv_mul(&pk.0, &u2); let point = self.curve_add(&point1, &point2); - let x = NonNativeTarget:: { + let x = NonNativeTarget:: { value: point.x.value, _phantom: PhantomData, }; @@ -63,17 +65,13 @@ mod tests { use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use crate::plonk::verifier::verify; - #[test] - #[ignore] - fn test_ecdsa_circuit() -> Result<()> { + fn test_ecdsa_circuit_with_config(config: CircuitConfig) -> Result<()> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = >::F; type Curve = Secp256K1; - let config = CircuitConfig::standard_ecc_config(); - let pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); @@ -97,8 +95,21 @@ mod tests { builder.verify_message(msg_target, sig_target, pk_target); + dbg!(builder.num_gates()); let data = builder.build::(); let proof = data.prove(pw).unwrap(); verify(proof, &data.verifier_only, &data.common) } + + #[test] + #[ignore] + fn test_ecdsa_circuit_narrow() -> Result<()> { + test_ecdsa_circuit_with_config(CircuitConfig::standard_ecc_config()) + } + + #[test] + #[ignore] + fn test_ecdsa_circuit_wide() -> Result<()> { + test_ecdsa_circuit_with_config(CircuitConfig::wide_ecc_config()) + } } diff --git a/plonky2/src/gadgets/glv.rs b/plonky2/src/gadgets/glv.rs index 89013dd1..8a0179ec 100644 --- a/plonky2/src/gadgets/glv.rs +++ b/plonky2/src/gadgets/glv.rs @@ -4,7 +4,7 @@ use plonky2_field::extension_field::Extendable; use plonky2_field::secp256k1_base::Secp256K1Base; use plonky2_field::secp256k1_scalar::Secp256K1Scalar; -use crate::curve::glv::{decompose_secp256k1_scalar, BETA}; +use crate::curve::glv::{decompose_secp256k1_scalar, GLV_BETA, GLV_S}; use crate::curve::secp256k1::Secp256K1; use crate::gadgets::curve::AffinePointTarget; use crate::gadgets::nonnative::NonNativeTarget; @@ -16,7 +16,7 @@ use crate::plonk::circuit_builder::CircuitBuilder; impl, const D: usize> CircuitBuilder { pub fn secp256k1_glv_beta(&mut self) -> NonNativeTarget { - self.constant_nonnative(BETA) + self.constant_nonnative(GLV_BETA) } pub fn decompose_secp256k1_scalar( @@ -42,6 +42,14 @@ impl, const D: usize> CircuitBuilder { _phantom: PhantomData, }); + // Check that `k1_raw + GLV_S * k2_raw == k`. + let k1_raw = self.nonnative_conditional_neg(&k1, k1_neg); + let k2_raw = self.nonnative_conditional_neg(&k2, k2_neg); + let s = self.constant_nonnative(GLV_S); + let mut should_be_k = self.mul_nonnative(&s, &k2_raw); + should_be_k = self.add_nonnative(&should_be_k, &k1_raw); + self.connect_nonnative(&should_be_k, k); + (k1, k2, k1_neg, k2_neg) } @@ -59,12 +67,9 @@ impl, const D: usize> CircuitBuilder { y: p.y.clone(), }; - let part1 = self.curve_scalar_mul_windowed(p, &k1); - let part1_neg = self.curve_conditional_neg(&part1, k1_neg); - let part2 = self.curve_scalar_mul_windowed(&sp, &k2); - let part2_neg = self.curve_conditional_neg(&part2, k2_neg); - - self.curve_add(&part1_neg, &part2_neg) + let p_neg = self.curve_conditional_neg(p, k1_neg); + let sp_neg = self.curve_conditional_neg(&sp, k2_neg); + self.curve_msm(&p_neg, &sp_neg, &k1, &k2) } } @@ -134,6 +139,7 @@ mod tests { let actual = builder.glv_mul(&randot, &scalar_target); builder.connect_affine_point(&expected, &actual); + dbg!(builder.num_gates()); let data = builder.build::(); let proof = data.prove(pw).unwrap(); diff --git a/plonky2/src/gadgets/mod.rs b/plonky2/src/gadgets/mod.rs index d9c93db3..50fc0437 100644 --- a/plonky2/src/gadgets/mod.rs +++ b/plonky2/src/gadgets/mod.rs @@ -3,8 +3,9 @@ pub mod arithmetic_extension; pub mod arithmetic_u32; pub mod biguint; pub mod curve; +pub mod curve_fixed_base; +pub mod curve_msm; pub mod curve_windowed_mul; -// pub mod curve_msm; pub mod ecdsa; pub mod glv; pub mod hash; diff --git a/plonky2/src/gadgets/nonnative.rs b/plonky2/src/gadgets/nonnative.rs index 910915d0..6c483a86 100644 --- a/plonky2/src/gadgets/nonnative.rs +++ b/plonky2/src/gadgets/nonnative.rs @@ -338,6 +338,19 @@ impl, const D: usize> CircuitBuilder { result } + + pub fn nonnative_conditional_neg( + &mut self, + x: &NonNativeTarget, + b: BoolTarget, + ) -> NonNativeTarget { + let not_b = self.not(b); + let neg = self.neg_nonnative(x); + let x_if_true = self.mul_nonnative_by_bool(&neg, b); + let x_if_false = self.mul_nonnative_by_bool(x, not_b); + + self.add_nonnative(&x_if_true, &x_if_false) + } } #[derive(Debug)] @@ -454,7 +467,7 @@ impl, const D: usize, FF: PrimeField> SimpleGenerat let b_biguint = b.to_canonical_biguint(); let modulus = FF::order(); - let (diff_biguint, overflow) = if a_biguint > b_biguint { + let (diff_biguint, overflow) = if a_biguint >= b_biguint { (a_biguint - b_biguint, false) } else { (modulus + a_biguint - b_biguint, true) diff --git a/plonky2/src/gadgets/split_nonnative.rs b/plonky2/src/gadgets/split_nonnative.rs index 70661506..18fc0264 100644 --- a/plonky2/src/gadgets/split_nonnative.rs +++ b/plonky2/src/gadgets/split_nonnative.rs @@ -35,6 +35,17 @@ impl, const D: usize> CircuitBuilder { .collect() } + pub fn split_nonnative_to_2_bit_limbs( + &mut self, + val: &NonNativeTarget, + ) -> Vec { + val.value + .limbs + .iter() + .flat_map(|&l| self.split_le_base::<4>(l.0, 16)) + .collect() + } + // Note: assumes its inputs are 4-bit limbs, and does not range-check. pub fn recombine_nonnative_4_bit_limbs( &mut self, diff --git a/plonky2/src/iop/witness.rs b/plonky2/src/iop/witness.rs index de6d4a05..a013f811 100644 --- a/plonky2/src/iop/witness.rs +++ b/plonky2/src/iop/witness.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::iter::repeat; use itertools::Itertools; use num::{BigUint, FromPrimitive, Zero}; @@ -160,7 +161,11 @@ pub trait Witness { } fn set_biguint_target(&mut self, target: &BigUintTarget, value: &BigUint) { - for (<, &l) in target.limbs.iter().zip(&value.to_u32_digits()) { + for (<, l) in target + .limbs + .iter() + .zip(value.to_u32_digits().into_iter().chain(repeat(0))) + { self.set_u32_target(lt, l); } } diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 63f45fec..e0d05c24 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -70,7 +70,7 @@ pub struct CircuitBuilder, const D: usize> { marked_targets: Vec>, /// Generators used to generate the witness. - generators: Vec>>, + pub generators: Vec>>, constants_to_targets: HashMap, targets_to_constants: HashMap, diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 3d4ee2df..34b38fcf 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -86,6 +86,13 @@ impl CircuitConfig { } } + pub fn wide_ecc_config() -> Self { + Self { + num_wires: 234, + ..Self::standard_recursion_config() + } + } + pub fn standard_recursion_zk_config() -> Self { CircuitConfig { zero_knowledge: true,