diff --git a/plonky2/src/gadgets/curve.rs b/plonky2/src/gadgets/curve.rs index c2af0104..d56c7650 100644 --- a/plonky2/src/gadgets/curve.rs +++ b/plonky2/src/gadgets/curve.rs @@ -230,7 +230,7 @@ impl, const D: usize> CircuitBuilder { let windows = self.split_nonnative_to_4_bit_limbs(n); let m = C::ScalarField::BITS / WINDOW_SIZE; - for i in m..0 { + for i in (0..m).rev() { result = self.curve_double(&result); let window = windows[i]; @@ -411,6 +411,38 @@ mod tests { verify(proof, &data.verifier_only, &data.common) } + #[test] + fn test_curve_mul_windowed() -> 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 g = Secp256K1::GENERATOR_AFFINE; + let five = Secp256K1Scalar::from_canonical_usize(5); + let neg_five = five.neg(); + let neg_five_scalar = CurveScalar::(neg_five); + let neg_five_g = (neg_five_scalar * g.to_projective()).to_affine(); + let neg_five_g_expected = builder.constant_affine_point(neg_five_g); + builder.curve_assert_valid(&neg_five_g_expected); + + let g_target = builder.constant_affine_point(g); + let neg_five_target = builder.constant_nonnative(neg_five); + let neg_five_g_actual = builder.curve_scalar_mul_windowed(&g_target, &neg_five_target); + /*builder.curve_assert_valid(&neg_five_g_actual); + + builder.connect_affine_point(&neg_five_g_expected, &neg_five_g_actual);*/ + + let data = builder.build::(); + let proof = data.prove(pw).unwrap(); + + verify(proof, &data.verifier_only, &data.common) + } + #[test] #[ignore] fn test_curve_random() -> Result<()> { diff --git a/plonky2/src/gadgets/split_nonnative.rs b/plonky2/src/gadgets/split_nonnative.rs index d1f16b65..88c693c0 100644 --- a/plonky2/src/gadgets/split_nonnative.rs +++ b/plonky2/src/gadgets/split_nonnative.rs @@ -1,8 +1,11 @@ +use std::marker::PhantomData; + use itertools::Itertools; use plonky2_field::extension_field::Extendable; use plonky2_field::field_types::Field; use crate::gadgets::arithmetic_u32::U32Target; +use crate::gadgets::biguint::BigUintTarget; use crate::gadgets::nonnative::NonNativeTarget; use crate::hash::hash_types::RichField; use crate::iop::target::Target; @@ -10,7 +13,7 @@ use crate::plonk::circuit_builder::CircuitBuilder; impl, const D: usize> CircuitBuilder { pub fn split_u32_to_4_bit_limbs(&mut self, val: U32Target) -> Vec { - let two_bit_limbs = self.split_le_base::<2>(val.0, 16); + let two_bit_limbs = self.split_le_base::<4>(val.0, 16); let four = self.constant(F::from_canonical_usize(4)); let combined_limbs = two_bit_limbs .iter() @@ -31,4 +34,64 @@ impl, const D: usize> CircuitBuilder { .flat_map(|&l| self.split_u32_to_4_bit_limbs(l)) .collect() } + + // Note: assumes its inputs are 4-bit limbs, and does not range-check. + pub fn recombine_nonnative_4_bit_limbs( + &mut self, + limbs: Vec, + ) -> NonNativeTarget { + let base = self.constant_u32(1 << 4); + let u32_limbs = limbs + .chunks(8) + .map(|chunk| { + let mut combined_chunk = self.zero_u32(); + for i in (0..8).rev() { + let (low, _high) = self.mul_add_u32(combined_chunk, base, U32Target(chunk[i])); + combined_chunk = low; + } + combined_chunk + }) + .collect(); + + NonNativeTarget { + value: BigUintTarget { limbs: u32_limbs }, + _phantom: PhantomData, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::field_types::Field; + use plonky2_field::secp256k1_scalar::Secp256K1Scalar; + + use crate::gadgets::nonnative::NonNativeTarget; + 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] + fn test_split_nonnative() -> Result<()> { + type FF = Secp256K1Scalar; + 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 x = FF::rand(); + let x_target = builder.constant_nonnative(x); + let split = builder.split_nonnative_to_4_bit_limbs(&x_target); + let combined: NonNativeTarget = builder.recombine_nonnative_4_bit_limbs(split); + builder.connect_nonnative(&x_target, &combined); + + let data = builder.build::(); + let proof = data.prove(pw).unwrap(); + verify(proof, &data.verifier_only, &data.common) + } }