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: BLS signatures over BLS12-381 curve #176

Open
wants to merge 29 commits into
base: community-edition
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
12a68f3
add bls12 module
nulltea Sep 30, 2023
c23d264
make compile
nulltea Sep 30, 2023
14077b4
add tests; they fail :(
nulltea Sep 30, 2023
29f3e82
tests WIP
nulltea Oct 1, 2023
701f8f4
fix miller look args names
nulltea Oct 1, 2023
8355312
impl (single) miller_loop
nulltea Oct 1, 2023
4cd431a
cargo fix+fmt
nulltea Oct 1, 2023
54d0d3e
add bls signature chip
nulltea Oct 1, 2023
02fb814
add tests
nulltea Oct 2, 2023
4c107de
add comments
nulltea Oct 2, 2023
0ee4b0a
Merge branch 'feat/bls12-381-pairing' into feat/bls12-381-signatures
nulltea Oct 2, 2023
9c35ca7
add comments
nulltea Oct 2, 2023
f689ab8
use separte halo2curves with `halo2-pse` feature
nulltea Oct 12, 2023
1a01328
use separte halo2curves with `halo2-pse` feature
nulltea Oct 12, 2023
d7bbe5b
Merge remote-tracking branch 'upstream/community-edition' into feat/b…
nulltea Jan 5, 2024
d1d1b92
optimize final exp
nulltea Jan 5, 2024
9452a6c
Merge branch 'feat/bls12-381-pairing' into feat/bls12-381-signatures
nulltea Jan 5, 2024
90b7594
update test config
nulltea Dec 10, 2023
b371954
update test config
nulltea Jan 5, 2024
ae455d8
fix bigint for bls12-381
nulltea Feb 19, 2024
9ce6725
fix bigint for bls12-381
nulltea Feb 19, 2024
01654cf
Merge remote-tracking branch 'origin/community-edition' into feat/bls…
nulltea Feb 20, 2024
8b79482
fix build
nulltea Feb 20, 2024
5775309
Merge branch 'feat/bls12-381-pairing' into feat/bls12-381-signatures
nulltea Feb 20, 2024
dd8b0d2
fix for halo2-pse
nulltea Feb 20, 2024
39994bf
Merge branch 'feat/bls12-381-pairing' into feat/bls12-381-signatures
nulltea Feb 20, 2024
7ef05a1
fix ec_add test config
nulltea Apr 15, 2024
a01e898
Fix bls_signature_verify insecure usage (#2)
nulltea May 15, 2024
938d6b7
Add `must_use` flag for `is_valid_signature` returned selector (#11)
nulltea May 15, 2024
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ halo2-ecc = { path = "../halo2-lib/halo2-ecc" }
[patch.crates-io]
halo2-base = { path = "../halo2-lib/halo2-base" }
halo2-ecc = { path = "../halo2-lib/halo2-ecc" }
halo2curves-axiom = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" }
77 changes: 66 additions & 11 deletions halo2-base/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,71 @@ pub trait BigPrimeField: ScalarField {
fn from_u64_digits(val: &[u64]) -> Self;
}
#[cfg(feature = "halo2-axiom")]
impl<F> BigPrimeField for F
where
F: ScalarField + From<[u64; 4]>, // Assume [u64; 4] is little-endian. We only implement ScalarField when this is true.
{
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
debug_assert!(val.len() <= 4);
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
mod bn256 {
use crate::halo2_proofs::halo2curves::bn256::{Fq, Fr};

impl super::BigPrimeField for Fr {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}

impl super::BigPrimeField for Fq {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}
}

#[cfg(feature = "halo2-axiom")]
mod secp256k1 {
use crate::halo2_proofs::halo2curves::secp256k1::{Fp, Fq};

impl super::BigPrimeField for Fp {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}

impl super::BigPrimeField for Fq {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}
}

#[cfg(feature = "halo2-axiom")]
mod bls12_381 {
use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fr};

impl super::BigPrimeField for Fr {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 4];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}

impl super::BigPrimeField for Fq {
#[inline(always)]
fn from_u64_digits(val: &[u64]) -> Self {
let mut raw = [0u64; 6];
raw[..val.len()].copy_from_slice(val);
Self::from(raw)
}
}
}

Expand Down Expand Up @@ -95,7 +150,7 @@ pub trait ScalarField: PrimeField + FromUniformBytes<64> + From<bool> + Hash + O

/// [ScalarField] that is ~256 bits long
#[cfg(feature = "halo2-pse")]
pub trait BigPrimeField = PrimeField<Repr = [u8; 32]> + ScalarField;
pub trait BigPrimeField = PrimeField + ScalarField;

/// Converts an [Iterator] of u64 digits into `number_of_limbs` limbs of `bit_len` bits returned as a [Vec].
///
Expand Down
9 changes: 7 additions & 2 deletions halo2-ecc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ itertools = "0.11"
num-bigint = { version = "0.4", features = ["rand"] }
num-integer = "0.1"
num-traits = "0.2"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
rand_core = { version = "0.6", default-features = false, features = [
"getrandom",
] }
rand = "0.8"
rand_chacha = "0.3.1"
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -23,6 +25,9 @@ rayon = "1.8"
test-case = "3.1.0"

halo2-base = { version = "=0.4.1", path = "../halo2-base", default-features = false }
# Use additional axiom-crypto halo2curves for BLS12-381 chips when [feature = "halo2-pse"] is on,
# because the PSE halo2curves does not support BLS12-381 chips and Halo2 depnds on lower major version so patching it is not possible
halo2curves = { package = "halo2curves-axiom", version = "0.5", optional=true }

# plotting circuit layout
plotters = { version = "0.3.0", optional = true }
Expand All @@ -41,7 +46,7 @@ default = ["jemallocator", "halo2-axiom", "display"]
dev-graph = ["halo2-base/dev-graph", "plotters"]
display = ["halo2-base/display"]
asm = ["halo2-base/asm"]
halo2-pse = ["halo2-base/halo2-pse"]
halo2-pse = ["halo2-base/halo2-pse", "halo2curves"]
halo2-axiom = ["halo2-base/halo2-axiom"]
jemallocator = ["halo2-base/jemallocator"]
mimalloc = ["halo2-base/mimalloc"]
Expand Down
8 changes: 8 additions & 0 deletions halo2-ecc/configs/bls12_381/bench_bls_signature.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{"strategy":"Simple","degree":15,"num_advice":105,"num_lookup_advice":14,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":16,"num_advice":50,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":18,"num_advice":13,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":20,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":19,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":21,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":20,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
{"strategy":"Simple","degree":22,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":21,"limb_bits":120,"num_limbs":4,"num_aggregation":2}
5 changes: 5 additions & 0 deletions halo2-ecc/configs/bls12_381/bench_ec_add.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"strategy":"Simple","degree":15,"num_advice":10,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4,"batch_size":100}
{"strategy":"Simple","degree":16,"num_advice":5,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4,"batch_size":100}
{"strategy":"Simple","degree":17,"num_advice":4,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"batch_size":100}
{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4,"batch_size":100}
{"strategy":"Simple","degree":19,"num_advice":1,"num_lookup_advice":0,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4,"batch_size":100}
9 changes: 9 additions & 0 deletions halo2-ecc/configs/bls12_381/bench_pairing.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"strategy":"Simple","degree":14,"num_advice":211,"num_lookup_advice":27,"num_fixed":1,"lookup_bits":13,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":15,"num_advice":105,"num_lookup_advice":14,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":16,"num_advice":50,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":18,"num_advice":13,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":20,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":19,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":21,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":20,"limb_bits":120,"num_limbs":4}
{"strategy":"Simple","degree":22,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":21,"limb_bits":120,"num_limbs":4}
1 change: 1 addition & 0 deletions halo2-ecc/configs/bls12_381/bls_signature_circuit.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5,"num_aggregation":30}
1 change: 1 addition & 0 deletions halo2-ecc/configs/bls12_381/ec_add_circuit.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5,"batch_size":100}
1 change: 1 addition & 0 deletions halo2-ecc/configs/bls12_381/pairing_circuit.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5}
23 changes: 17 additions & 6 deletions halo2-ecc/src/bigint/carry_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ pub fn crt<F: BigPrimeField>(
// Let n' <= quot_max_bits - n(k-1) - 1
// If quot[i] <= 2^n for i < k - 1 and quot[k-1] <= 2^{n'} then
// quot < 2^{n(k-1)+1} + 2^{n' + n(k-1)} = (2+2^{n'}) 2^{n(k-1)} < 2^{n'+1} * 2^{n(k-1)} <= 2^{quot_max_bits - n(k-1)} * 2^{n(k-1)}
let quot_last_limb_bits = quot_max_bits - n * (k - 1);

let bits_wo_last_limb: usize = n * (k - 1);
// `has_redunant_limb` will be true when native element can be represented in k-1 limbs, but some cases require an extra limb to carry.
// This is only the case for BLS12-381, which requires k=5 and n > 102 because of the check above.
let has_redunant_limb = quot_max_bits < bits_wo_last_limb;
let out_max_bits = modulus.bits() as usize;
// we assume `modulus` requires *exactly* `k` limbs to represent (if `< k` limbs ok, you should just be using that)
let out_last_limb_bits = out_max_bits - n * (k - 1);

// these are witness vectors:
// we need to find `out_vec` as a proper BigInt with k limbs
Expand Down Expand Up @@ -138,13 +138,24 @@ pub fn crt<F: BigPrimeField>(

// range check limbs of `out` are in [0, 2^n) except last limb should be in [0, 2^out_last_limb_bits)
for (out_index, out_cell) in out_assigned.iter().enumerate() {
let limb_bits = if out_index == k - 1 { out_last_limb_bits } else { n };
if has_redunant_limb && out_index == k - 1 {
let zero = ctx.load_zero();
ctx.constrain_equal(out_cell, &zero);
continue;
}
// we assume `modulus` requires *exactly* `k` limbs to represent (if `< k` limbs ok, you should just be using that)
let limb_bits = if out_index == k - 1 { out_max_bits - bits_wo_last_limb } else { n };
range.range_check(ctx, *out_cell, limb_bits);
}

// range check that quot_cell in quot_assigned is in [-2^n, 2^n) except for last cell check it's in [-2^quot_last_limb_bits, 2^quot_last_limb_bits)
for (q_index, quot_cell) in quot_assigned.iter().enumerate() {
let limb_bits = if q_index == k - 1 { quot_last_limb_bits } else { n };
if has_redunant_limb && q_index == k - 1 {
let zero = ctx.load_zero();
ctx.constrain_equal(quot_cell, &zero);
continue;
}
let limb_bits = if q_index == k - 1 { quot_max_bits - bits_wo_last_limb } else { n };
let limb_base =
if q_index == k - 1 { range.gate().pow_of_two()[limb_bits] } else { limb_bases[1] };

Expand Down
12 changes: 10 additions & 2 deletions halo2-ecc/src/bigint/check_carry_mod_to_zero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ pub fn crt<F: BigPrimeField>(
// see carry_mod.rs for explanation
let quot_max_bits = trunc_len - 1 + (F::NUM_BITS as usize) - 1 - (modulus.bits() as usize);
assert!(quot_max_bits < trunc_len);
let quot_last_limb_bits = quot_max_bits - n * (k - 1);
let bits_wo_last_limb: usize = n * (k - 1);
// `has_redunant_limb` will be true when native element can be represented in k-1 limbs, but some cases require an extra limb to carry.
// This is only the case for BLS12-381, which requires k=5 and n > 102 because of the check above.
let has_redunant_limb = quot_max_bits < bits_wo_last_limb;

// these are witness vectors:
// we need to find `quot_vec` as a proper BigInt with k limbs
Expand Down Expand Up @@ -90,7 +93,12 @@ pub fn crt<F: BigPrimeField>(

// range check that quot_cell in quot_assigned is in [-2^n, 2^n) except for last cell check it's in [-2^quot_last_limb_bits, 2^quot_last_limb_bits)
for (q_index, quot_cell) in quot_assigned.iter().enumerate() {
let limb_bits = if q_index == k - 1 { quot_last_limb_bits } else { n };
if has_redunant_limb && q_index == k - 1 {
let zero = ctx.load_zero();
ctx.constrain_equal(quot_cell, &zero);
continue;
}
let limb_bits = if q_index == k - 1 { quot_max_bits - n * (k - 1) } else { n };
let limb_base =
if q_index == k - 1 { range.gate().pow_of_two()[limb_bits] } else { limb_bases[1] };

Expand Down
101 changes: 101 additions & 0 deletions halo2-ecc/src/bls12_381/bls_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use std::ops::Neg;

use super::pairing::PairingChip;
use super::{Fp12Chip, FpChip};
use super::{Fq12, G1Affine, G2Affine};
use crate::bigint::ProperCrtUint;
use crate::ecc::{EcPoint, EccChip};
use crate::fields::vector::FieldVector;
use crate::fields::FieldChip;
use halo2_base::utils::BigPrimeField;
use halo2_base::{AssignedValue, Context};

pub struct BlsSignatureChip<'chip, F: BigPrimeField> {
pub fp_chip: &'chip FpChip<'chip, F>,
pub pairing_chip: &'chip PairingChip<'chip, F>,
}

impl<'chip, F: BigPrimeField> BlsSignatureChip<'chip, F> {
pub fn new(fp_chip: &'chip FpChip<F>, pairing_chip: &'chip PairingChip<F>) -> Self {
Self { fp_chip, pairing_chip }
}

/// Warning: this function loads (signature, pubkey, msghash) as private witnesses but does not validate that any of the them.
/// This function is mainly intended for testing. If you're using this function make sure to add constraints for the returned assigned points.
///
/// Verifies that e(g1, signature) = e(pubkey, H(m)) by checking e(g1, signature)*e(pubkey, -H(m)) === 1
/// where e(,) is optimal Ate pairing
/// G1: {g1, pubkey}, G2: {signature, message}
pub fn bls_signature_verify(
&self,
ctx: &mut Context<F>,
signature: G2Affine,
pubkey: G1Affine,
msghash: G2Affine,
) -> (EcPoint<F, FieldVector<ProperCrtUint<F>>>, EcPoint<F, FieldVector<ProperCrtUint<F>>>, EcPoint<F, ProperCrtUint<F>>) {
let signature_assigned = self.pairing_chip.load_private_g2_unchecked(ctx, signature);
let pubkey_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, pubkey);
let hash_m_assigned = self.pairing_chip.load_private_g2_unchecked(ctx, msghash);

self.assert_valid_signature(ctx, signature_assigned.clone(), hash_m_assigned.clone(), pubkey_assigned.clone());

(signature_assigned, hash_m_assigned, pubkey_assigned)
}

/// Verifies BLS signature and returns assigned selector.
///
/// Checks that e(g1, signature) = e(pubkey, H(m)) by checking e(g1, signature)*e(pubkey, -H(m)) === 1
/// where e(,) is optimal Ate pairing
/// G1: {g1, pubkey}, G2: {signature, message}
#[must_use]
pub fn is_valid_signature(
&self,
ctx: &mut Context<F>,
signature: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
msghash: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
pubkey: EcPoint<F, ProperCrtUint<F>>,
) -> AssignedValue<F> {
let g1_chip = EccChip::new(self.fp_chip);

let g1_neg = g1_chip.assign_constant_point(ctx, G1Affine::generator().neg());

let gt = self.compute_pairing(ctx, signature, msghash, pubkey, g1_neg);

let fp12_chip = Fp12Chip::<F>::new(self.fp_chip);
let fp12_one = fp12_chip.load_constant(ctx, Fq12::one());

fp12_chip.is_equal(ctx, gt, fp12_one)
}

/// Verifies BLS signature with equality check.
///
/// Checks that e(g1, signature) = e(pubkey, H(m)) by checking e(g1, signature)*e(pubkey, -H(m)) === 1
/// where e(,) is optimal Ate pairing
/// G1: {g1, pubkey}, G2: {signature, message}
pub fn assert_valid_signature(
&self,
ctx: &mut Context<F>,
signature: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
msghash: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
pubkey: EcPoint<F, ProperCrtUint<F>>,
) {
let g1_chip = EccChip::new(self.fp_chip);
let g1_neg = g1_chip.assign_constant_point(ctx, G1Affine::generator().neg());

let gt = self.compute_pairing(ctx, signature, msghash, pubkey, g1_neg);
let fp12_chip = Fp12Chip::<F>::new(self.fp_chip);
let fp12_one = fp12_chip.load_constant(ctx, Fq12::one());
fp12_chip.assert_equal(ctx, gt, fp12_one);
}

fn compute_pairing(
&self,
ctx: &mut Context<F>,
signature: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
msghash: EcPoint<F, FieldVector<ProperCrtUint<F>>>,
pubkey: EcPoint<F, ProperCrtUint<F>>,
g1_neg: EcPoint<F, ProperCrtUint<F>>,
) -> FieldVector<ProperCrtUint<F>> {
self.pairing_chip.batched_pairing(ctx, &[(&g1_neg, &signature), (&pubkey, &msghash)])
}
}
Loading