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

feat: add support for secp256r1 #225

Open
wants to merge 2 commits into
base: community-edition
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions halo2-ecc/configs/secp256r1/bench_ecdsa.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"strategy":"Simple","degree":19,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":17,"num_advice":4,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":16,"num_advice":8,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":15,"limb_bits":90,"num_limbs":3}
{"strategy":"Simple","degree":15,"num_advice":17,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":14,"limb_bits":90,"num_limbs":3}
{"strategy":"Simple","degree":14,"num_advice":34,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":13,"limb_bits":91,"num_limbs":3}
{"strategy":"Simple","degree":13,"num_advice":68,"num_lookup_advice":12,"num_fixed":1,"lookup_bits":12,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":12,"num_advice":139,"num_lookup_advice":24,"num_fixed":2,"lookup_bits":11,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":11,"num_advice":291,"num_lookup_advice":53,"num_fixed":4,"lookup_bits":10,"limb_bits":88,"num_limbs":3}
1 change: 1 addition & 0 deletions halo2-ecc/configs/secp256r1/bench_ecdsa.t.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":15,"num_advice":17,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":14,"limb_bits":90,"num_limbs":3}
9 changes: 9 additions & 0 deletions halo2-ecc/configs/secp256r1/bench_schnorr.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"strategy":"Simple","degree":19,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":17,"num_advice":4,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":16,"num_advice":8,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":15,"limb_bits":90,"num_limbs":3}
{"strategy":"Simple","degree":15,"num_advice":17,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":14,"limb_bits":90,"num_limbs":3}
{"strategy":"Simple","degree":14,"num_advice":34,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":13,"limb_bits":91,"num_limbs":3}
{"strategy":"Simple","degree":13,"num_advice":68,"num_lookup_advice":12,"num_fixed":1,"lookup_bits":12,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":12,"num_advice":139,"num_lookup_advice":24,"num_fixed":2,"lookup_bits":11,"limb_bits":88,"num_limbs":3}
{"strategy":"Simple","degree":11,"num_advice":291,"num_lookup_advice":53,"num_fixed":4,"lookup_bits":10,"limb_bits":88,"num_limbs":3}
1 change: 1 addition & 0 deletions halo2-ecc/configs/secp256r1/ecdsa_circuit.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3}
9 changes: 9 additions & 0 deletions halo2-ecc/configs/secp256r1/scalar_multiplication.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"strategy":"Simple",
"degree":18,
"num_advice":2,
"num_lookup_advice":1,
"num_fixed":1,
"lookup_bits":17,
"limb_bits":88,
"num_limbs":3,
"window_bits":4}
1 change: 1 addition & 0 deletions halo2-ecc/configs/secp256r1/schnorr_circuit.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3}
4 changes: 2 additions & 2 deletions halo2-ecc/src/bn254/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ pub fn miller_loop_BN<F: BigPrimeField>(
let f_sq = fp12_chip.mul(ctx, &f, &f);
f = fp12_multiply_with_line_equal::<F>(ecc_chip.field_chip(), ctx, &f_sq, &R, P);
}
R = ecc_chip.double(ctx, &R);
R = ecc_chip.double::<G2Affine>(ctx, &R);

assert!(pseudo_binary_encoding[i] <= 1 && pseudo_binary_encoding[i] >= -1);
if pseudo_binary_encoding[i] != 0 {
Expand Down Expand Up @@ -350,7 +350,7 @@ pub fn multi_miller_loop_BN<F: BigPrimeField>(
}
}
for r in r.iter_mut() {
*r = ecc_chip.double(ctx, r.clone());
*r = ecc_chip.double::<G2Affine>(ctx, r.clone());
}

assert!(pseudo_binary_encoding[i] <= 1 && pseudo_binary_encoding[i] >= -1);
Expand Down
33 changes: 24 additions & 9 deletions halo2-ecc/src/ecc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ where
// formula from https://crypto.stanford.edu/pbc/notes/elliptic/explicit.html
// assume y != 0 (otherwise 2P = O)

// lamb = 3x^2 / (2 y) % p
// lamb = (3x^2 + a) / (2 y) % p
// x_3 = out[0] = lambda^2 - 2 x % p
// y_3 = out[1] = lambda (x - x_3) - y % p

Expand All @@ -300,17 +300,25 @@ where
/// # Assumptions
/// * `P.y != 0`
/// * `P` is not the point at infinity (undefined behavior otherwise)
pub fn ec_double<F: BigPrimeField, FC: FieldChip<F>>(
pub fn ec_double<F: BigPrimeField, FC: FieldChip<F>, C>(
chip: &FC,
ctx: &mut Context<F>,
P: impl Into<EcPoint<F, FC::FieldPoint>>,
) -> EcPoint<F, FC::FieldPoint> {
) -> EcPoint<F, FC::FieldPoint>
where
C: CurveAffine<Base = FC::FieldType>,
{
let P = P.into();
// removed optimization that computes `2 * lambda` while assigning witness to `lambda` simultaneously, in favor of readability. The difference is just copying `lambda` once
let two_y = chip.scalar_mul_no_carry(ctx, &P.y, 2);
let three_x = chip.scalar_mul_no_carry(ctx, &P.x, 3);
let three_x_sq = chip.mul_no_carry(ctx, three_x, &P.x);
let lambda = chip.divide_unsafe(ctx, three_x_sq, two_y);
let lambda_numerator = if C::a() != C::Base::ZERO {
chip.add_constant_no_carry(ctx, three_x_sq, C::a())
} else {
three_x_sq
};
let lambda = chip.divide_unsafe(ctx, lambda_numerator, two_y);

// x_3 = lambda^2 - 2 x % p
let lambda_sq = chip.mul_no_carry(ctx, &lambda, &lambda);
Expand Down Expand Up @@ -595,6 +603,10 @@ where
{
let lhs = chip.mul_no_carry(ctx, &P.y, &P.y);
let mut rhs = chip.mul(ctx, &P.x, &P.x).into();

if C::a() != C::Base::ZERO {
rhs = chip.add_constant_no_carry(ctx, rhs, C::a());
}
rhs = chip.mul_no_carry(ctx, rhs, &P.x);

rhs = chip.add_constant_no_carry(ctx, rhs, C::b());
Expand Down Expand Up @@ -692,7 +704,7 @@ where
let mut rand_start_vec = Vec::with_capacity(k + window_bits);
rand_start_vec.push(base);
for idx in 1..(k + window_bits) {
let base_mult = ec_double(chip, ctx, &rand_start_vec[idx - 1]);
let base_mult = ec_double::<F, FC, C>(chip, ctx, &rand_start_vec[idx - 1]);
rand_start_vec.push(base_mult);
}
assert!(rand_start_vec.len() >= k + window_bits);
Expand Down Expand Up @@ -743,7 +755,7 @@ where
// compute \sum_i x_i P_i + (2^{k + 1} - 1) * A
for idx in 0..num_windows {
for _ in 0..window_bits {
curr_point = ec_double(chip, ctx, curr_point);
curr_point = ec_double::<F, FC, C>(chip, ctx, curr_point);
}
for (cached_points, rounded_bits) in
cached_points.chunks(cache_size).zip(rounded_bits.chunks(rounded_bitlen))
Expand Down Expand Up @@ -961,12 +973,15 @@ impl<'chip, F: BigPrimeField, FC: FieldChip<F>> EccChip<'chip, F, FC> {
ec_sub_unequal(self.field_chip, ctx, P, Q, is_strict)
}

pub fn double(
pub fn double<C>(
&self,
ctx: &mut Context<F>,
P: impl Into<EcPoint<F, FC::FieldPoint>>,
) -> EcPoint<F, FC::FieldPoint> {
ec_double(self.field_chip, ctx, P)
) -> EcPoint<F, FC::FieldPoint>
where
C: CurveAffine<Base = FC::FieldType>,
{
ec_double::<F, FC, C>(self.field_chip, ctx, P)
}

pub fn is_equal(
Expand Down
10 changes: 5 additions & 5 deletions halo2-ecc/src/ecc/pippenger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ where
let mut any_points = Vec::with_capacity(num_rounds);
any_points.push(any_base);
for _ in 1..num_rounds {
any_points.push(ec_double(chip, ctx, any_points.last().unwrap()));
any_points.push(ec_double::<F, FC, C>(chip, ctx, any_points.last().unwrap()));
}

// now begins multi-threading
Expand Down Expand Up @@ -318,21 +318,21 @@ where
// we have agg[j] = G'[j] + (2^num_rounds - 1) * any_base
// let any_point = (2^num_rounds - 1) * any_base
// TODO: can we remove all these random point operations somehow?
let mut any_point = ec_double(chip, ctx, any_points.last().unwrap());
let mut any_point = ec_double::<F, FC, C>(chip, ctx, any_points.last().unwrap());
any_point = ec_sub_unequal(chip, ctx, any_point, &any_points[0], true);

// compute sum_{k=0..scalar_bits} agg[k] * 2^k - (sum_{k=0..scalar_bits} 2^k) * rand_point
// (sum_{k=0..scalar_bits} 2^k) = (2^scalar_bits - 1)
let mut sum = agg.pop().unwrap().into();
let mut any_sum = any_point.clone();
for g in agg.iter().rev() {
any_sum = ec_double(chip, ctx, any_sum);
any_sum = ec_double::<F, FC, C>(chip, ctx, any_sum);
// cannot use ec_double_and_add_unequal because you cannot guarantee that `sum != g`
sum = ec_double(chip, ctx, sum);
sum = ec_double::<F, FC, C>(chip, ctx, sum);
sum = ec_add_unequal(chip, ctx, sum, g, true);
}

any_sum = ec_double(chip, ctx, any_sum);
any_sum = ec_double::<F, FC, C>(chip, ctx, any_sum);
any_sum = ec_sub_unequal(chip, ctx, any_sum, any_point, true);

ec_sub_strict(chip, ctx, sum, any_sum)
Expand Down
3 changes: 2 additions & 1 deletion halo2-ecc/src/ecc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::halo2_proofs::{
plonk::*,
};
use halo2_base::gates::RangeChip;
use halo2_base::halo2_proofs::halo2curves::secp256k1::Secp256k1Affine;
use halo2_base::utils::bigint_to_fe;
use halo2_base::utils::testing::base_test;
use halo2_base::utils::value_to_option;
Expand Down Expand Up @@ -47,7 +48,7 @@ fn basic_g1_tests<F: BigPrimeField>(
println!("add unequal witness OK");

// test double
let doub = chip.double(ctx, &P_assigned);
let doub = chip.double::<G1Affine>(ctx, &P_assigned);
assert_eq!(doub.x.0.truncation.to_bigint(limb_bits), doub.x.0.value);
assert_eq!(doub.y.0.truncation.to_bigint(limb_bits), doub.y.0.value);
{
Expand Down
1 change: 1 addition & 0 deletions halo2-ecc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod fields;
pub mod bn254;
pub mod grumpkin;
pub mod secp256k1;
pub mod secp256r1;

pub use halo2_base;
pub(crate) use halo2_base::halo2_proofs;
Expand Down
12 changes: 12 additions & 0 deletions halo2-ecc/src/secp256r1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::halo2_proofs::halo2curves::secp256r1::{Fp, Fq};

use crate::ecc;
use crate::fields::fp;

pub type FpChip<'range, F> = fp::FpChip<'range, F, Fp>;
pub type FqChip<'range, F> = fp::FpChip<'range, F, Fq>;
pub type Secp256r1Chip<'chip, F> = ecc::EccChip<'chip, F, FpChip<'chip, F>>;
pub const SECP_B: u64 = 7;

#[cfg(test)]
mod tests;
146 changes: 146 additions & 0 deletions halo2-ecc/src/secp256r1/tests/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#![allow(non_snake_case)]
use std::fs::File;
use std::io::BufReader;
use std::io::Write;
use std::{fs, io::BufRead};

use super::*;
use crate::fields::FpStrategy;
use crate::halo2_proofs::{
arithmetic::CurveAffine,
halo2curves::bn256::Fr,
halo2curves::secp256r1::{Fp, Fq, Secp256r1Affine},
};
use crate::secp256r1::{FpChip, FqChip};
use crate::{
ecc::{ecdsa::ecdsa_verify_no_pubkey_check, EccChip},
fields::FieldChip,
};
use halo2_base::gates::RangeChip;
use halo2_base::utils::{biguint_to_fe, fe_to_biguint, modulus, BigPrimeField};
use halo2_base::Context;
use serde::{Deserialize, Serialize};
use test_log::test;

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub 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,
}

#[derive(Clone, Copy, Debug)]
pub struct ECDSAInput {
pub r: Fq,
pub s: Fq,
pub msghash: Fq,
pub pk: Secp256r1Affine,
}

pub fn ecdsa_test<F: BigPrimeField>(
ctx: &mut Context<F>,
range: &RangeChip<F>,
params: CircuitParams,
input: ECDSAInput,
) -> F {
let fp_chip = FpChip::<F>::new(range, params.limb_bits, params.num_limbs);
let fq_chip = FqChip::<F>::new(range, params.limb_bits, params.num_limbs);

let [m, r, s] = [input.msghash, input.r, input.s].map(|x| fq_chip.load_private(ctx, x));

let ecc_chip = EccChip::<F, FpChip<F>>::new(&fp_chip);
let pk = ecc_chip.load_private_unchecked(ctx, (input.pk.x, input.pk.y));
// test ECDSA
let res = ecdsa_verify_no_pubkey_check::<F, Fp, Fq, Secp256r1Affine>(
&ecc_chip, ctx, pk, r, s, m, 4, 4,
);
*res.value()
}

pub fn random_ecdsa_input(rng: &mut StdRng) -> ECDSAInput {
let sk = <Secp256r1Affine as CurveAffine>::ScalarExt::random(rng.clone());
let pk = Secp256r1Affine::from(Secp256r1Affine::generator() * sk);
let msghash = <Secp256r1Affine as CurveAffine>::ScalarExt::random(rng.clone());

let k = <Secp256r1Affine as CurveAffine>::ScalarExt::random(rng);
let k_inv = k.invert().unwrap();

let r_point = Secp256r1Affine::from(Secp256r1Affine::generator() * k).coordinates().unwrap();
let x = r_point.x();
let x_bigint = fe_to_biguint(x);
let r = biguint_to_fe::<Fq>(&(x_bigint % modulus::<Fq>()));
let s = k_inv * (msghash + (r * sk));

ECDSAInput { r, s, msghash, pk }
}

pub fn run_test(input: ECDSAInput) {
let path = "configs/secp256r1/ecdsa_circuit.config";
let params: CircuitParams = serde_json::from_reader(
File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")),
)
.unwrap();

let res = base_test()
.k(params.degree)
.lookup_bits(params.lookup_bits)
.run(|ctx, range| ecdsa_test(ctx, range, params, input));
assert_eq!(res, Fr::ONE);
}

#[test]
fn test_secp256r1_ecdsa() {
let mut rng = StdRng::seed_from_u64(0);
let input = random_ecdsa_input(&mut rng);
run_test(input);
}

#[test]
fn bench_secp256r1_ecdsa() -> Result<(), Box<dyn std::error::Error>> {
let config_path = "configs/secp256r1/bench_ecdsa.config";
let bench_params_file =
File::open(config_path).unwrap_or_else(|e| panic!("{config_path} does not exist: {e:?}"));
fs::create_dir_all("results/secp256r1").unwrap();
fs::create_dir_all("data").unwrap();
let results_path = "results/secp256r1/ecdsa_bench.csv";
let mut fs_results = File::create(results_path).unwrap();
writeln!(fs_results, "degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time")?;

let mut rng = StdRng::seed_from_u64(0);
let bench_params_reader = BufReader::new(bench_params_file);
for line in bench_params_reader.lines() {
let bench_params: CircuitParams = serde_json::from_str(line.unwrap().as_str()).unwrap();
let k = bench_params.degree;
println!("---------------------- degree = {k} ------------------------------",);

let stats =
base_test().k(k).lookup_bits(bench_params.lookup_bits).unusable_rows(20).bench_builder(
random_ecdsa_input(&mut rng),
random_ecdsa_input(&mut rng),
|pool, range, input| {
ecdsa_test(pool.main(), range, bench_params, input);
},
);

writeln!(
fs_results,
"{},{},{},{},{},{},{},{:?},{},{:?}",
bench_params.degree,
bench_params.num_advice,
bench_params.num_lookup_advice,
bench_params.num_fixed,
bench_params.lookup_bits,
bench_params.limb_bits,
bench_params.num_limbs,
stats.proof_time.time.elapsed(),
stats.proof_size,
stats.verify_time.time.elapsed()
)?;
}
Ok(())
}
Loading