Skip to content

Commit 980b39b

Browse files
authored
add native field chip using native arithmetic operations & grumpkin curve chip (#164)
1 parent 72d8f1a commit 980b39b

File tree

6 files changed

+345
-0
lines changed

6 files changed

+345
-0
lines changed

halo2-base/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ pub struct AssignedValue<F: crate::ff::Field> {
137137
pub cell: Option<ContextCell>,
138138
}
139139

140+
impl<'a, F: ScalarField> From<&'a AssignedValue<F>> for AssignedValue<F> {
141+
fn from(a: &'a AssignedValue<F>) -> Self {
142+
Self { value: a.value, cell: a.cell }
143+
}
144+
}
145+
140146
impl<F: ScalarField> AssignedValue<F> {
141147
/// Returns an immutable reference to the underlying value of an AssignedValue<F>.
142148
///

halo2-ecc/src/fields/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::fmt::Debug;
1111
pub mod fp;
1212
pub mod fp12;
1313
pub mod fp2;
14+
pub mod native_fp;
1415
pub mod vector;
1516

1617
#[cfg(test)]

halo2-ecc/src/fields/native_fp.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
use super::{BigPrimeField, FieldChip, Selectable};
2+
use halo2_base::gates::RangeChip;
3+
use halo2_base::QuantumCell::Constant;
4+
use halo2_base::{
5+
gates::GateInstructions, gates::RangeInstructions, utils::modulus, AssignedValue, Context,
6+
};
7+
use num_bigint::BigUint;
8+
use std::marker::PhantomData;
9+
10+
// native field chip which implements FieldChip, use GateInstructions for basic arithmetic operations
11+
#[derive(Clone, Debug)]
12+
pub struct NativeFieldChip<'range, F: BigPrimeField> {
13+
pub range: &'range RangeChip<F>,
14+
pub native_modulus: BigUint,
15+
_marker: PhantomData<F>,
16+
}
17+
18+
impl<'range, F: BigPrimeField> NativeFieldChip<'range, F> {
19+
pub fn new(range: &'range RangeChip<F>) -> Self {
20+
let native_modulus = modulus::<F>();
21+
Self { range, native_modulus, _marker: PhantomData }
22+
}
23+
}
24+
25+
impl<'range, F: BigPrimeField> FieldChip<F> for NativeFieldChip<'range, F> {
26+
const PRIME_FIELD_NUM_BITS: u32 = F::NUM_BITS;
27+
type UnsafeFieldPoint = AssignedValue<F>;
28+
type FieldPoint = AssignedValue<F>;
29+
type ReducedFieldPoint = AssignedValue<F>;
30+
type FieldType = F;
31+
type RangeChip = RangeChip<F>;
32+
33+
fn native_modulus(&self) -> &BigUint {
34+
&self.native_modulus
35+
}
36+
fn range(&self) -> &'range Self::RangeChip {
37+
self.range
38+
}
39+
fn limb_bits(&self) -> usize {
40+
F::NUM_BITS as usize
41+
}
42+
43+
fn get_assigned_value(&self, x: &AssignedValue<F>) -> F {
44+
*x.value()
45+
}
46+
47+
fn load_private(&self, ctx: &mut Context<F>, a: F) -> AssignedValue<F> {
48+
ctx.load_witness(a)
49+
}
50+
51+
fn load_constant(&self, ctx: &mut Context<F>, a: F) -> AssignedValue<F> {
52+
ctx.load_constant(a)
53+
}
54+
55+
// signed overflow BigInt functions
56+
fn add_no_carry(
57+
&self,
58+
ctx: &mut Context<F>,
59+
a: impl Into<AssignedValue<F>>,
60+
b: impl Into<AssignedValue<F>>,
61+
) -> AssignedValue<F> {
62+
self.gate().add(ctx, a.into(), b.into())
63+
}
64+
65+
fn add_constant_no_carry(
66+
&self,
67+
ctx: &mut Context<F>,
68+
a: impl Into<AssignedValue<F>>,
69+
c: F,
70+
) -> AssignedValue<F> {
71+
self.gate().add(ctx, a.into(), Constant(c))
72+
}
73+
74+
fn sub_no_carry(
75+
&self,
76+
ctx: &mut Context<F>,
77+
a: impl Into<AssignedValue<F>>,
78+
b: impl Into<AssignedValue<F>>,
79+
) -> AssignedValue<F> {
80+
self.gate().sub(ctx, a.into(), b.into())
81+
}
82+
83+
// Input: a
84+
// Output: p - a if a != 0, else a
85+
fn negate(&self, ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
86+
self.gate().neg(ctx, a)
87+
}
88+
89+
fn scalar_mul_no_carry(
90+
&self,
91+
ctx: &mut Context<F>,
92+
a: impl Into<AssignedValue<F>>,
93+
c: i64,
94+
) -> AssignedValue<F> {
95+
let c_f = if c >= 0 {
96+
let c_abs = u64::try_from(c).unwrap();
97+
F::from(c_abs)
98+
} else {
99+
let c_abs = u64::try_from(-c).unwrap();
100+
-F::from(c_abs)
101+
};
102+
103+
self.gate().mul(ctx, a.into(), Constant(c_f))
104+
}
105+
106+
fn scalar_mul_and_add_no_carry(
107+
&self,
108+
ctx: &mut Context<F>,
109+
a: impl Into<AssignedValue<F>>,
110+
b: impl Into<AssignedValue<F>>,
111+
c: i64,
112+
) -> AssignedValue<F> {
113+
let c_f = if c >= 0 {
114+
let c_abs = u64::try_from(c).unwrap();
115+
F::from(c_abs)
116+
} else {
117+
let c_abs = u64::try_from(-c).unwrap();
118+
-F::from(c_abs)
119+
};
120+
121+
self.gate().mul_add(ctx, a.into(), Constant(c_f), b.into())
122+
}
123+
124+
fn mul_no_carry(
125+
&self,
126+
ctx: &mut Context<F>,
127+
a: impl Into<AssignedValue<F>>,
128+
b: impl Into<AssignedValue<F>>,
129+
) -> AssignedValue<F> {
130+
self.gate().mul(ctx, a.into(), b.into())
131+
}
132+
133+
fn check_carry_mod_to_zero(&self, ctx: &mut Context<F>, a: AssignedValue<F>) {
134+
self.gate().assert_is_const(ctx, &a, &F::ZERO);
135+
}
136+
137+
// noop
138+
fn carry_mod(&self, _ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
139+
a
140+
}
141+
142+
fn range_check(
143+
&self,
144+
ctx: &mut Context<F>,
145+
a: impl Into<AssignedValue<F>>,
146+
max_bits: usize, // the maximum bits that a.value could take
147+
) {
148+
// skip range chek if max_bits >= F::NUM_BITS
149+
if max_bits < F::NUM_BITS as usize {
150+
let a: AssignedValue<F> = a.into();
151+
self.range().range_check(ctx, a, max_bits);
152+
}
153+
}
154+
155+
fn enforce_less_than(&self, _ctx: &mut Context<F>, a: AssignedValue<F>) -> AssignedValue<F> {
156+
a
157+
}
158+
159+
/// Returns 1 iff `a` is 0 as a BigUint.
160+
fn is_soft_zero(
161+
&self,
162+
ctx: &mut Context<F>,
163+
a: impl Into<AssignedValue<F>>,
164+
) -> AssignedValue<F> {
165+
let a = a.into();
166+
self.gate().is_zero(ctx, a)
167+
}
168+
169+
fn is_soft_nonzero(
170+
&self,
171+
ctx: &mut Context<F>,
172+
a: impl Into<AssignedValue<F>>,
173+
) -> AssignedValue<F> {
174+
let a = a.into();
175+
let is_soft_zero = self.is_soft_zero(ctx, a);
176+
self.gate().neg(ctx, is_soft_zero)
177+
}
178+
179+
fn is_zero(&self, ctx: &mut Context<F>, a: impl Into<AssignedValue<F>>) -> AssignedValue<F> {
180+
self.is_soft_zero(ctx, a)
181+
}
182+
183+
fn is_equal_unenforced(
184+
&self,
185+
ctx: &mut Context<F>,
186+
a: AssignedValue<F>,
187+
b: AssignedValue<F>,
188+
) -> AssignedValue<F> {
189+
self.gate().is_equal(ctx, a, b)
190+
}
191+
192+
fn assert_equal(
193+
&self,
194+
ctx: &mut Context<F>,
195+
a: impl Into<AssignedValue<F>>,
196+
b: impl Into<AssignedValue<F>>,
197+
) {
198+
ctx.constrain_equal(&a.into(), &b.into());
199+
}
200+
}
201+
202+
impl<'range, F: BigPrimeField> Selectable<F, AssignedValue<F>> for NativeFieldChip<'range, F> {
203+
fn select(
204+
&self,
205+
ctx: &mut Context<F>,
206+
a: AssignedValue<F>,
207+
b: AssignedValue<F>,
208+
sel: AssignedValue<F>,
209+
) -> AssignedValue<F> {
210+
let gate = self.gate();
211+
GateInstructions::select(gate, ctx, a, b, sel)
212+
}
213+
214+
fn select_by_indicator(
215+
&self,
216+
ctx: &mut Context<F>,
217+
a: &impl AsRef<[AssignedValue<F>]>,
218+
coeffs: &[AssignedValue<F>],
219+
) -> AssignedValue<F> {
220+
let a = a.as_ref().to_vec();
221+
let gate = self.gate();
222+
GateInstructions::select_by_indicator(gate, ctx, a, coeffs.to_vec())
223+
}
224+
}

halo2-ecc/src/grumpkin/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use crate::ecc;
2+
use crate::fields::fp;
3+
use crate::halo2_proofs::halo2curves::grumpkin::{Fq, Fr};
4+
5+
pub type GrumpkinFrChip<'chip, F> = ecc::EccChip<'chip, F, fp::FpChip<'chip, Fq, Fr>>;
6+
7+
#[cfg(test)]
8+
mod tests;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#![allow(non_snake_case)]
2+
use std::fs::File;
3+
4+
use crate::ff::Field;
5+
use crate::group::Curve;
6+
use halo2_base::{
7+
gates::RangeChip,
8+
halo2_proofs::halo2curves::grumpkin::{Fq, Fr, G1Affine},
9+
utils::{biguint_to_fe, fe_to_biguint, testing::base_test},
10+
Context,
11+
};
12+
use num_bigint::BigUint;
13+
use rand::rngs::StdRng;
14+
use rand_core::SeedableRng;
15+
use serde::{Deserialize, Serialize};
16+
17+
use crate::{
18+
ecc::EccChip,
19+
fields::{fp::FpChip, native_fp::NativeFieldChip, FieldChip, FpStrategy},
20+
};
21+
22+
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
23+
struct CircuitParams {
24+
strategy: FpStrategy,
25+
degree: u32,
26+
num_advice: usize,
27+
num_lookup_advice: usize,
28+
num_fixed: usize,
29+
lookup_bits: usize,
30+
limb_bits: usize,
31+
num_limbs: usize,
32+
}
33+
34+
fn sm_test(
35+
ctx: &mut Context<Fq>,
36+
range: &RangeChip<Fq>,
37+
params: CircuitParams,
38+
base: G1Affine,
39+
scalar: Fr,
40+
window_bits: usize,
41+
) {
42+
let fp_chip = NativeFieldChip::<Fq>::new(range);
43+
let fq_chip = FpChip::<Fq, Fr>::new(range, params.limb_bits, params.num_limbs);
44+
let ecc_chip = EccChip::<Fq, NativeFieldChip<Fq>>::new(&fp_chip);
45+
46+
let s = fq_chip.load_private(ctx, scalar);
47+
let P = ecc_chip.assign_point(ctx, base);
48+
49+
let sm = ecc_chip.scalar_mult::<G1Affine>(
50+
ctx,
51+
P,
52+
s.limbs().to_vec(),
53+
fq_chip.limb_bits,
54+
window_bits,
55+
);
56+
57+
let sm_answer = (base * scalar).to_affine();
58+
59+
let sm_x = sm.x.value();
60+
let sm_y = sm.y.value();
61+
assert_eq!(*sm_x, sm_answer.x);
62+
assert_eq!(*sm_y, sm_answer.y);
63+
}
64+
65+
fn run_test(base: G1Affine, scalar: Fr) {
66+
let path = "configs/secp256k1/ecdsa_circuit.config";
67+
let params: CircuitParams = serde_json::from_reader(
68+
File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")),
69+
)
70+
.unwrap();
71+
72+
base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| {
73+
sm_test(ctx, range, params, base, scalar, 4);
74+
});
75+
}
76+
77+
#[test]
78+
fn test_grumpkin_sm_random() {
79+
let mut rng = StdRng::seed_from_u64(0);
80+
run_test(G1Affine::random(&mut rng), Fr::random(&mut rng));
81+
}
82+
83+
#[test]
84+
fn test_grumpkin_sm_minus_1() {
85+
let rng = StdRng::seed_from_u64(0);
86+
let base = G1Affine::random(rng);
87+
let mut s = -Fr::one();
88+
let mut n = fe_to_biguint(&s);
89+
loop {
90+
run_test(base, s);
91+
if &n % BigUint::from(2usize) == BigUint::from(0usize) {
92+
break;
93+
}
94+
n /= 2usize;
95+
s = biguint_to_fe(&n);
96+
}
97+
}
98+
99+
#[test]
100+
fn test_grumpkin_sm_0_1() {
101+
let rng = StdRng::seed_from_u64(0);
102+
let base = G1Affine::random(rng);
103+
run_test(base, Fr::ZERO);
104+
run_test(base, Fr::ONE);
105+
}

halo2-ecc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod ecc;
88
pub mod fields;
99

1010
pub mod bn254;
11+
pub mod grumpkin;
1112
pub mod secp256k1;
1213

1314
pub use halo2_base;

0 commit comments

Comments
 (0)