diff --git a/halo2-base/src/lib.rs b/halo2-base/src/lib.rs index 1b922913..8b86c582 100644 --- a/halo2-base/src/lib.rs +++ b/halo2-base/src/lib.rs @@ -137,6 +137,12 @@ pub struct AssignedValue { pub cell: Option, } +impl<'a, F: ScalarField> From<&'a AssignedValue> for AssignedValue { + fn from(a: &'a AssignedValue) -> Self { + Self { value: a.value, cell: a.cell } + } +} + impl AssignedValue { /// Returns an immutable reference to the underlying value of an AssignedValue. /// diff --git a/halo2-ecc/src/fields/mod.rs b/halo2-ecc/src/fields/mod.rs index 5b3bde39..10e1ae6f 100644 --- a/halo2-ecc/src/fields/mod.rs +++ b/halo2-ecc/src/fields/mod.rs @@ -11,6 +11,7 @@ use std::fmt::Debug; pub mod fp; pub mod fp12; pub mod fp2; +pub mod native_fp; pub mod vector; #[cfg(test)] diff --git a/halo2-ecc/src/fields/native_fp.rs b/halo2-ecc/src/fields/native_fp.rs new file mode 100644 index 00000000..7391d1db --- /dev/null +++ b/halo2-ecc/src/fields/native_fp.rs @@ -0,0 +1,224 @@ +use super::{BigPrimeField, FieldChip, Selectable}; +use halo2_base::gates::RangeChip; +use halo2_base::QuantumCell::Constant; +use halo2_base::{ + gates::GateInstructions, gates::RangeInstructions, utils::modulus, AssignedValue, Context, +}; +use num_bigint::BigUint; +use std::marker::PhantomData; + +// native field chip which implements FieldChip, use GateInstructions for basic arithmetic operations +#[derive(Clone, Debug)] +pub struct NativeFieldChip<'range, F: BigPrimeField> { + pub range: &'range RangeChip, + pub native_modulus: BigUint, + _marker: PhantomData, +} + +impl<'range, F: BigPrimeField> NativeFieldChip<'range, F> { + pub fn new(range: &'range RangeChip) -> Self { + let native_modulus = modulus::(); + Self { range, native_modulus, _marker: PhantomData } + } +} + +impl<'range, F: BigPrimeField> FieldChip for NativeFieldChip<'range, F> { + const PRIME_FIELD_NUM_BITS: u32 = F::NUM_BITS; + type UnsafeFieldPoint = AssignedValue; + type FieldPoint = AssignedValue; + type ReducedFieldPoint = AssignedValue; + type FieldType = F; + type RangeChip = RangeChip; + + fn native_modulus(&self) -> &BigUint { + &self.native_modulus + } + fn range(&self) -> &'range Self::RangeChip { + self.range + } + fn limb_bits(&self) -> usize { + F::NUM_BITS as usize + } + + fn get_assigned_value(&self, x: &AssignedValue) -> F { + *x.value() + } + + fn load_private(&self, ctx: &mut Context, a: F) -> AssignedValue { + ctx.load_witness(a) + } + + fn load_constant(&self, ctx: &mut Context, a: F) -> AssignedValue { + ctx.load_constant(a) + } + + // signed overflow BigInt functions + fn add_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + self.gate().add(ctx, a.into(), b.into()) + } + + fn add_constant_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + c: F, + ) -> AssignedValue { + self.gate().add(ctx, a.into(), Constant(c)) + } + + fn sub_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + self.gate().sub(ctx, a.into(), b.into()) + } + + // Input: a + // Output: p - a if a != 0, else a + fn negate(&self, ctx: &mut Context, a: AssignedValue) -> AssignedValue { + self.gate().neg(ctx, a) + } + + fn scalar_mul_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + c: i64, + ) -> AssignedValue { + let c_f = if c >= 0 { + let c_abs = u64::try_from(c).unwrap(); + F::from(c_abs) + } else { + let c_abs = u64::try_from(-c).unwrap(); + -F::from(c_abs) + }; + + self.gate().mul(ctx, a.into(), Constant(c_f)) + } + + fn scalar_mul_and_add_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + c: i64, + ) -> AssignedValue { + let c_f = if c >= 0 { + let c_abs = u64::try_from(c).unwrap(); + F::from(c_abs) + } else { + let c_abs = u64::try_from(-c).unwrap(); + -F::from(c_abs) + }; + + self.gate().mul_add(ctx, a.into(), Constant(c_f), b.into()) + } + + fn mul_no_carry( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) -> AssignedValue { + self.gate().mul(ctx, a.into(), b.into()) + } + + fn check_carry_mod_to_zero(&self, ctx: &mut Context, a: AssignedValue) { + self.gate().assert_is_const(ctx, &a, &F::ZERO); + } + + // noop + fn carry_mod(&self, _ctx: &mut Context, a: AssignedValue) -> AssignedValue { + a + } + + fn range_check( + &self, + ctx: &mut Context, + a: impl Into>, + max_bits: usize, // the maximum bits that a.value could take + ) { + // skip range chek if max_bits >= F::NUM_BITS + if max_bits < F::NUM_BITS as usize { + let a: AssignedValue = a.into(); + self.range().range_check(ctx, a, max_bits); + } + } + + fn enforce_less_than(&self, _ctx: &mut Context, a: AssignedValue) -> AssignedValue { + a + } + + /// Returns 1 iff `a` is 0 as a BigUint. + fn is_soft_zero( + &self, + ctx: &mut Context, + a: impl Into>, + ) -> AssignedValue { + let a = a.into(); + self.gate().is_zero(ctx, a) + } + + fn is_soft_nonzero( + &self, + ctx: &mut Context, + a: impl Into>, + ) -> AssignedValue { + let a = a.into(); + let is_soft_zero = self.is_soft_zero(ctx, a); + self.gate().neg(ctx, is_soft_zero) + } + + fn is_zero(&self, ctx: &mut Context, a: impl Into>) -> AssignedValue { + self.is_soft_zero(ctx, a) + } + + fn is_equal_unenforced( + &self, + ctx: &mut Context, + a: AssignedValue, + b: AssignedValue, + ) -> AssignedValue { + self.gate().is_equal(ctx, a, b) + } + + fn assert_equal( + &self, + ctx: &mut Context, + a: impl Into>, + b: impl Into>, + ) { + ctx.constrain_equal(&a.into(), &b.into()); + } +} + +impl<'range, F: BigPrimeField> Selectable> for NativeFieldChip<'range, F> { + fn select( + &self, + ctx: &mut Context, + a: AssignedValue, + b: AssignedValue, + sel: AssignedValue, + ) -> AssignedValue { + let gate = self.gate(); + GateInstructions::select(gate, ctx, a, b, sel) + } + + fn select_by_indicator( + &self, + ctx: &mut Context, + a: &impl AsRef<[AssignedValue]>, + coeffs: &[AssignedValue], + ) -> AssignedValue { + let a = a.as_ref().to_vec(); + let gate = self.gate(); + GateInstructions::select_by_indicator(gate, ctx, a, coeffs.to_vec()) + } +} diff --git a/halo2-ecc/src/grumpkin/mod.rs b/halo2-ecc/src/grumpkin/mod.rs new file mode 100644 index 00000000..6d9732ce --- /dev/null +++ b/halo2-ecc/src/grumpkin/mod.rs @@ -0,0 +1,8 @@ +use crate::ecc; +use crate::fields::fp; +use crate::halo2_proofs::halo2curves::grumpkin::{Fq, Fr}; + +pub type GrumpkinFrChip<'chip, F> = ecc::EccChip<'chip, F, fp::FpChip<'chip, Fq, Fr>>; + +#[cfg(test)] +mod tests; diff --git a/halo2-ecc/src/grumpkin/tests/mod.rs b/halo2-ecc/src/grumpkin/tests/mod.rs new file mode 100644 index 00000000..fece9c45 --- /dev/null +++ b/halo2-ecc/src/grumpkin/tests/mod.rs @@ -0,0 +1,105 @@ +#![allow(non_snake_case)] +use std::fs::File; + +use crate::ff::Field; +use crate::group::Curve; +use halo2_base::{ + gates::RangeChip, + halo2_proofs::halo2curves::grumpkin::{Fq, Fr, G1Affine}, + utils::{biguint_to_fe, fe_to_biguint, testing::base_test}, + Context, +}; +use num_bigint::BigUint; +use rand::rngs::StdRng; +use rand_core::SeedableRng; +use serde::{Deserialize, Serialize}; + +use crate::{ + ecc::EccChip, + fields::{fp::FpChip, native_fp::NativeFieldChip, FieldChip, FpStrategy}, +}; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct CircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, +} + +fn sm_test( + ctx: &mut Context, + range: &RangeChip, + params: CircuitParams, + base: G1Affine, + scalar: Fr, + window_bits: usize, +) { + let fp_chip = NativeFieldChip::::new(range); + let fq_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let ecc_chip = EccChip::>::new(&fp_chip); + + let s = fq_chip.load_private(ctx, scalar); + let P = ecc_chip.assign_point(ctx, base); + + let sm = ecc_chip.scalar_mult::( + ctx, + P, + s.limbs().to_vec(), + fq_chip.limb_bits, + window_bits, + ); + + let sm_answer = (base * scalar).to_affine(); + + let sm_x = sm.x.value(); + let sm_y = sm.y.value(); + assert_eq!(*sm_x, sm_answer.x); + assert_eq!(*sm_y, sm_answer.y); +} + +fn run_test(base: G1Affine, scalar: Fr) { + let path = "configs/secp256k1/ecdsa_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + sm_test(ctx, range, params, base, scalar, 4); + }); +} + +#[test] +fn test_grumpkin_sm_random() { + let mut rng = StdRng::seed_from_u64(0); + run_test(G1Affine::random(&mut rng), Fr::random(&mut rng)); +} + +#[test] +fn test_grumpkin_sm_minus_1() { + let rng = StdRng::seed_from_u64(0); + let base = G1Affine::random(rng); + let mut s = -Fr::one(); + let mut n = fe_to_biguint(&s); + loop { + run_test(base, s); + if &n % BigUint::from(2usize) == BigUint::from(0usize) { + break; + } + n /= 2usize; + s = biguint_to_fe(&n); + } +} + +#[test] +fn test_grumpkin_sm_0_1() { + let rng = StdRng::seed_from_u64(0); + let base = G1Affine::random(rng); + run_test(base, Fr::ZERO); + run_test(base, Fr::ONE); +} diff --git a/halo2-ecc/src/lib.rs b/halo2-ecc/src/lib.rs index c4a47c15..5b3f191a 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -8,6 +8,7 @@ pub mod ecc; pub mod fields; pub mod bn254; +pub mod grumpkin; pub mod secp256k1; pub use halo2_base;