Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add native field chip using native arithmetic operations & grumpkin curve chip #164

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading