Skip to content

Commit

Permalink
add native field chip using native arithmetic operations & grumpkin c…
Browse files Browse the repository at this point in the history
…urve chip (#164)
  • Loading branch information
odyssey2077 committed Sep 21, 2023
1 parent 72d8f1a commit 980b39b
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 0 deletions.
6 changes: 6 additions & 0 deletions halo2-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ pub struct AssignedValue<F: crate::ff::Field> {
pub cell: Option<ContextCell>,
}

impl<'a, F: ScalarField> From<&'a AssignedValue<F>> for AssignedValue<F> {
fn from(a: &'a AssignedValue<F>) -> Self {
Self { value: a.value, cell: a.cell }
}
}

impl<F: ScalarField> AssignedValue<F> {
/// Returns an immutable reference to the underlying value of an AssignedValue<F>.
///
Expand Down
1 change: 1 addition & 0 deletions halo2-ecc/src/fields/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
224 changes: 224 additions & 0 deletions halo2-ecc/src/fields/native_fp.rs
Original file line number Diff line number Diff line change
@@ -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<F>,
pub native_modulus: BigUint,
_marker: PhantomData<F>,
}

impl<'range, F: BigPrimeField> NativeFieldChip<'range, F> {
pub fn new(range: &'range RangeChip<F>) -> Self {
let native_modulus = modulus::<F>();
Self { range, native_modulus, _marker: PhantomData }
}
}

impl<'range, F: BigPrimeField> FieldChip<F> for NativeFieldChip<'range, F> {
const PRIME_FIELD_NUM_BITS: u32 = F::NUM_BITS;
type UnsafeFieldPoint = AssignedValue<F>;
type FieldPoint = AssignedValue<F>;
type ReducedFieldPoint = AssignedValue<F>;
type FieldType = F;
type RangeChip = RangeChip<F>;

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>) -> F {
*x.value()
}

fn load_private(&self, ctx: &mut Context<F>, a: F) -> AssignedValue<F> {
ctx.load_witness(a)
}

fn load_constant(&self, ctx: &mut Context<F>, a: F) -> AssignedValue<F> {
ctx.load_constant(a)
}

// signed overflow BigInt functions
fn add_no_carry(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
b: impl Into<AssignedValue<F>>,
) -> AssignedValue<F> {
self.gate().add(ctx, a.into(), b.into())
}

fn add_constant_no_carry(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
c: F,
) -> AssignedValue<F> {
self.gate().add(ctx, a.into(), Constant(c))
}

fn sub_no_carry(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
b: impl Into<AssignedValue<F>>,
) -> AssignedValue<F> {
self.gate().sub(ctx, a.into(), b.into())
}

// Input: a
// Output: p - a if a != 0, else a
fn negate(&self, ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
self.gate().neg(ctx, a)
}

fn scalar_mul_no_carry(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
c: i64,
) -> AssignedValue<F> {
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<F>,
a: impl Into<AssignedValue<F>>,
b: impl Into<AssignedValue<F>>,
c: i64,
) -> AssignedValue<F> {
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<F>,
a: impl Into<AssignedValue<F>>,
b: impl Into<AssignedValue<F>>,
) -> AssignedValue<F> {
self.gate().mul(ctx, a.into(), b.into())
}

fn check_carry_mod_to_zero(&self, ctx: &mut Context<F>, a: AssignedValue<F>) {
self.gate().assert_is_const(ctx, &a, &F::ZERO);
}

// noop
fn carry_mod(&self, _ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
a
}

fn range_check(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
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<F> = a.into();
self.range().range_check(ctx, a, max_bits);
}
}

fn enforce_less_than(&self, _ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
a
}

/// Returns 1 iff `a` is 0 as a BigUint.
fn is_soft_zero(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
) -> AssignedValue<F> {
let a = a.into();
self.gate().is_zero(ctx, a)
}

fn is_soft_nonzero(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
) -> AssignedValue<F> {
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<F>, a: impl Into<AssignedValue<F>>) -> AssignedValue<F> {
self.is_soft_zero(ctx, a)
}

fn is_equal_unenforced(
&self,
ctx: &mut Context<F>,
a: AssignedValue<F>,
b: AssignedValue<F>,
) -> AssignedValue<F> {
self.gate().is_equal(ctx, a, b)
}

fn assert_equal(
&self,
ctx: &mut Context<F>,
a: impl Into<AssignedValue<F>>,
b: impl Into<AssignedValue<F>>,
) {
ctx.constrain_equal(&a.into(), &b.into());
}
}

impl<'range, F: BigPrimeField> Selectable<F, AssignedValue<F>> for NativeFieldChip<'range, F> {
fn select(
&self,
ctx: &mut Context<F>,
a: AssignedValue<F>,
b: AssignedValue<F>,
sel: AssignedValue<F>,
) -> AssignedValue<F> {
let gate = self.gate();
GateInstructions::select(gate, ctx, a, b, sel)
}

fn select_by_indicator(
&self,
ctx: &mut Context<F>,
a: &impl AsRef<[AssignedValue<F>]>,
coeffs: &[AssignedValue<F>],
) -> AssignedValue<F> {
let a = a.as_ref().to_vec();
let gate = self.gate();
GateInstructions::select_by_indicator(gate, ctx, a, coeffs.to_vec())
}
}
8 changes: 8 additions & 0 deletions halo2-ecc/src/grumpkin/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
105 changes: 105 additions & 0 deletions halo2-ecc/src/grumpkin/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Fq>,
range: &RangeChip<Fq>,
params: CircuitParams,
base: G1Affine,
scalar: Fr,
window_bits: usize,
) {
let fp_chip = NativeFieldChip::<Fq>::new(range);
let fq_chip = FpChip::<Fq, Fr>::new(range, params.limb_bits, params.num_limbs);
let ecc_chip = EccChip::<Fq, NativeFieldChip<Fq>>::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::<G1Affine>(
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);
}
1 change: 1 addition & 0 deletions halo2-ecc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod ecc;
pub mod fields;

pub mod bn254;
pub mod grumpkin;
pub mod secp256k1;

pub use halo2_base;
Expand Down

0 comments on commit 980b39b

Please sign in to comment.