Skip to content

Commit

Permalink
implement schnorr siganture (#116)
Browse files Browse the repository at this point in the history
* schnorr signature draft

* record hash update code

* refactor function signature

* clean up

* add test for schnorr signature

* update comment

* implement even check

* update test

* fix a bug

* update test

* nit

* resolve comments

* resolve comments

* nits

* nits
  • Loading branch information
odyssey2077 authored Aug 31, 2023
1 parent f2eacb1 commit 5de49cf
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 3 deletions.
9 changes: 9 additions & 0 deletions halo2-ecc/configs/secp256k1/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/secp256k1/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}
20 changes: 20 additions & 0 deletions halo2-ecc/src/bigint/big_is_even.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::OverflowInteger;
use halo2_base::gates::GateInstructions;
use halo2_base::gates::RangeChip;
use halo2_base::{safe_types::RangeInstructions, utils::ScalarField, AssignedValue, Context};

/// # Assumptions
/// * `a` has nonzero number of limbs
pub fn positive<F: ScalarField>(
range: &RangeChip<F>,
ctx: &mut Context<F>,
a: OverflowInteger<F>,
limb_bits: usize,
) -> AssignedValue<F> {
let k = a.limbs.len();
assert_ne!(k, 0);
let first_cell: AssignedValue<F> = a.limbs[0];

let last_bit = range.get_last_bit(ctx, first_cell, limb_bits);
range.gate.not(ctx, last_bit)
}
1 change: 1 addition & 0 deletions halo2-ecc/src/bigint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use num_traits::Zero;

pub mod add_no_carry;
pub mod big_is_equal;
pub mod big_is_even;
pub mod big_is_zero;
pub mod big_less_than;
pub mod carry_mod;
Expand Down
1 change: 1 addition & 0 deletions halo2-ecc/src/ecc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use std::marker::PhantomData;

pub mod ecdsa;
pub mod fixed_base;
pub mod schnorr_signature;
// pub mod fixed_base_pippenger;
pub mod pippenger;

Expand Down
80 changes: 80 additions & 0 deletions halo2-ecc/src/ecc/schnorr_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::bigint::{big_is_equal, ProperCrtUint};
use crate::fields::{fp::FpChip, FieldChip, PrimeField};
use halo2_base::{gates::GateInstructions, utils::CurveAffineExt, AssignedValue, Context};

use super::{fixed_base, scalar_multiply, EcPoint, EccChip};

// CF is the coordinate field of GA
// SF is the scalar field of GA
// p = base field modulus
// n = scalar field modulus
// checks r < p, 0 < s < n, 0 < msgHash < n
// this circuit applies over constraints that s > 0, msgHash > 0 cause scalar_multiply can't handle zero scalar
/// `pubkey` should not be the identity point
/// follow spec in https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
pub fn schnorr_verify_no_pubkey_check<F: PrimeField, CF: PrimeField, SF: PrimeField, GA>(
chip: &EccChip<F, FpChip<F, CF>>,
ctx: &mut Context<F>,
pubkey: EcPoint<F, <FpChip<F, CF> as FieldChip<F>>::FieldPoint>,
r: ProperCrtUint<F>, // int(sig[0:32]); fail if r ≥ p.
s: ProperCrtUint<F>, // int(sig[32:64]); fail if s ≥ n
msgHash: ProperCrtUint<F>, // int(hashBIP0340/challenge(bytes(r) || bytes(P) || m)) mod n
var_window_bits: usize,
fixed_window_bits: usize,
) -> AssignedValue<F>
where
GA: CurveAffineExt<Base = CF, ScalarExt = SF>,
{
let base_chip = chip.field_chip;
let scalar_chip =
FpChip::<F, SF>::new(base_chip.range, base_chip.limb_bits, base_chip.num_limbs);

// check r < p
let r_valid = base_chip.is_less_than_p(ctx, &r);
// check 0 < s < n
let s_valid = scalar_chip.is_soft_nonzero(ctx, &s);
// check 0 < e < n
let e_valid = scalar_chip.is_soft_nonzero(ctx, &msgHash);

// compute s * G and msgHash * pubkey
let s_G = fixed_base::scalar_multiply(
base_chip,
ctx,
&GA::generator(),
s.limbs().to_vec(),
base_chip.limb_bits,
fixed_window_bits,
);
let e_P = scalar_multiply::<_, _, GA>(
base_chip,
ctx,
pubkey,
msgHash.limbs().to_vec(),
base_chip.limb_bits,
var_window_bits,
);

// check s_G.x != e_P.x, which is a requirement for sub_unequal
let x_eq = base_chip.is_equal(ctx, &s_G.x, &e_P.x);
let x_neq = base_chip.gate().not(ctx, x_eq);

// R = s⋅G - e⋅P
// R is not infinity point implicitly constrainted by is_strict = true
let R = chip.sub_unequal(ctx, s_G, e_P, true);

// check R.y is even
let R_y = R.y;
let R_y_is_even: AssignedValue<F> = base_chip.is_even(ctx, &R_y);

// check R.x == r
let R_x = scalar_chip.enforce_less_than(ctx, R.x);
let equal_check = big_is_equal::assign(base_chip.gate(), ctx, R_x.0, r);

let res1 = base_chip.gate().and(ctx, r_valid, s_valid);
let res2: AssignedValue<F> = base_chip.gate().and(ctx, res1, e_valid);
let res3 = base_chip.gate().and(ctx, res2, x_neq);
let res4: AssignedValue<F> = base_chip.gate().and(ctx, res3, R_y_is_even);
let res5 = base_chip.gate().and(ctx, res4, equal_check);

res5
}
39 changes: 36 additions & 3 deletions halo2-ecc/src/fields/fp.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::{FieldChip, PrimeField, PrimeFieldChip, Selectable};
use crate::bigint::{
add_no_carry, big_is_equal, big_is_zero, carry_mod, check_carry_mod_to_zero, mul_no_carry,
scalar_mul_and_add_no_carry, scalar_mul_no_carry, select, select_by_indicator, sub,
sub_no_carry, CRTInteger, FixedCRTInteger, OverflowInteger, ProperCrtUint, ProperUint,
add_no_carry, big_is_equal, big_is_even, big_is_zero, carry_mod, check_carry_mod_to_zero,
mul_no_carry, scalar_mul_and_add_no_carry, scalar_mul_no_carry, select, select_by_indicator,
sub, sub_no_carry, CRTInteger, FixedCRTInteger, OverflowInteger, ProperCrtUint, ProperUint,
};
use crate::halo2_proofs::halo2curves::CurveAffine;
use halo2_base::gates::RangeChip;
Expand Down Expand Up @@ -124,13 +124,46 @@ impl<'range, F: PrimeField, Fp: PrimeField> FpChip<'range, F, Fp> {
self.gate().assert_is_const(ctx, &borrow.unwrap(), &F::one());
}

/// Given proper CRT integer `a`, returns 1 iff `a < modulus::<F>()`
/// # Assumptions
/// * `a` is proper representation of BigUint
pub fn is_less_than_p(
&self,
ctx: &mut Context<F>,
a: impl Into<ProperCrtUint<F>>,
) -> AssignedValue<F> {
let a = a.into();

// underflow != 0 iff carry < p
let p = self.load_constant_uint(ctx, self.p.to_biguint().unwrap());
let (_, underflow) =
sub::crt::<F>(self.range(), ctx, a, p, self.limb_bits, self.limb_bases[1]);
let is_underflow_zero = self.gate().is_zero(ctx, underflow);
let no_underflow = self.gate().not(ctx, is_underflow_zero);

no_underflow
}

pub fn load_constant_uint(&self, ctx: &mut Context<F>, a: BigUint) -> ProperCrtUint<F> {
FixedCRTInteger::from_native(a, self.num_limbs, self.limb_bits).assign(
ctx,
self.limb_bits,
self.native_modulus(),
)
}

// assuming `a` has been range checked to be a proper BigInt
// constrain the witness `a` to be `< p`
// then check if `a[0]` is even
pub fn is_even(
&self,
ctx: &mut Context<F>,
a: impl Into<ProperCrtUint<F>>,
) -> AssignedValue<F> {
let a = a.into();
self.enforce_less_than_p(ctx, a.clone());
big_is_even::positive(self.range(), ctx, a.0.truncation, self.limb_bits)
}
}

impl<'range, F: PrimeField, Fp: PrimeField> PrimeFieldChip<F> for FpChip<'range, F, Fp> {
Expand Down
2 changes: 2 additions & 0 deletions halo2-ecc/src/secp256k1/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use crate::{
pub mod ecdsa;
pub mod ecdsa_tests;

pub mod schnorr_signature_tests;

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct CircuitParams {
strategy: FpStrategy,
Expand Down
Loading

0 comments on commit 5de49cf

Please sign in to comment.