use alloc::vec; use num::BigUint; use plonky2::field::extension::Extendable; use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::hash::keccak::KeccakHash; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::config::{GenericHashOut, Hasher}; use crate::curve::curve_types::{Curve, CurveScalar}; use crate::gadgets::curve::{AffinePointTarget, CircuitBuilderCurve}; use crate::gadgets::curve_windowed_mul::CircuitBuilderWindowedMul; use crate::gadgets::nonnative::NonNativeTarget; use crate::gadgets::split_nonnative::CircuitBuilderSplit; /// 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_circuit, const D: usize>( builder: &mut CircuitBuilder, p: &AffinePointTarget, q: &AffinePointTarget, n: &NonNativeTarget, m: &NonNativeTarget, ) -> AffinePointTarget { let limbs_n = builder.split_nonnative_to_2_bit_limbs(n); let limbs_m = builder.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_noncanonical_biguint(BigUint::from_bytes_le( &GenericHashOut::::to_bytes(&hash_0), )); let rando = (CurveScalar(hash_0_scalar) * C::GENERATOR_PROJECTIVE).to_affine(); let rando_t = builder.constant_affine_point(rando); let neg_rando = builder.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 = builder.curve_add(&cur_p, p); cur_q = builder.curve_add(&cur_q, q); } for i in 1..4 { precomputation[i] = builder.curve_add(&precomputation[i], &neg_rando); precomputation[4 * i] = builder.curve_add(&precomputation[4 * i], &neg_rando); } for i in 1..4 { for j in 1..4 { precomputation[i + 4 * j] = builder.curve_add(&precomputation[i], &precomputation[4 * j]); } } let four = builder.constant(F::from_canonical_usize(4)); let zero = builder.zero(); let mut result = rando_t; for (limb_n, limb_m) in limbs_n.into_iter().zip(limbs_m).rev() { result = builder.curve_repeated_double(&result, 2); let index = builder.mul_add(four, limb_m, limb_n); let r = builder.random_access_curve_points(index, precomputation.clone()); let is_zero = builder.is_equal(index, zero); let should_add = builder.not(is_zero); result = builder.curve_conditional_add(&result, &r, should_add); } let starting_point_multiplied = (0..2 * num_limbs).fold(rando, |acc, _| acc.double()); let to_add = builder.constant_affine_point(-starting_point_multiplied); result = builder.curve_add(&result, &to_add); result } #[cfg(test)] mod tests { use anyhow::Result; use plonky2::field::secp256k1_scalar::Secp256K1Scalar; use plonky2::field::types::Sample; use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use crate::curve::curve_types::{Curve, CurveScalar}; use crate::curve::secp256k1::Secp256K1; use crate::gadgets::curve::CircuitBuilderCurve; use crate::gadgets::curve_msm::curve_msm_circuit; use crate::gadgets::nonnative::CircuitBuilderNonNative; #[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 = curve_msm_circuit(&mut builder, &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(); data.verify(proof) } }