-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add native field chip using native arithmetic operations & grumpkin c…
…urve chip
- Loading branch information
1 parent
72d8f1a
commit 61a0693
Showing
6 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ pub mod ecc; | |
pub mod fields; | ||
|
||
pub mod bn254; | ||
pub mod grumpkin; | ||
pub mod secp256k1; | ||
|
||
pub use halo2_base; | ||
|