Skip to content

Commit

Permalink
feat: Add BLS signature verification for BN254 (#89)
Browse files Browse the repository at this point in the history
* feat: Add BLS signature verification for BN254

* Use load_private() and sum() as suggested

* Corrected as per request

* Update bls_signature_verify() to return AssignedValue<F>

* Eliminate unnecessary fp_chip. Shortened result comparison

* Use forloop instead of iterator for affine to avoid dependence on halo2_proofs

* Add test to Github CI
  • Loading branch information
flyingnobita authored Aug 10, 2023
1 parent 1047146 commit f2eacb1
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 18 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
cargo test -- test_sm
cargo test -- test_fb
cargo test -- test_pairing
cargo test -- test_bls_signature
- name: Run halo2-ecc tests real prover
working-directory: 'halo2-ecc'
run: |
Expand Down
15 changes: 15 additions & 0 deletions halo2-ecc/configs/bn254/bench_bls_signature.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{"strategy":"Simple","degree":14,"num_advice":211,"num_lookup_advice":27,"num_fixed":1,"lookup_bits":13,"limb_bits":91,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":15,"num_advice":105,"num_lookup_advice":14,"num_fixed":1,"lookup_bits":14,"limb_bits":90,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":16,"num_advice":50,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":15,"limb_bits":90,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":18,"num_advice":13,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":90,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":20,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":19,"limb_bits":88,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":21,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":20,"limb_bits":88,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":22,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":21,"limb_bits":88,"num_limbs":3,"num_aggregation":2}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":20}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":200}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":2000}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":20000}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":40000}
{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3,"num_aggregation":80000}
1 change: 1 addition & 0 deletions halo2-ecc/configs/bn254/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":90,"num_limbs":3,"num_aggregation":30}
79 changes: 79 additions & 0 deletions halo2-ecc/src/bn254/bls_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![allow(non_snake_case)]

use super::pairing::PairingChip;
use super::{Fp12Chip, Fp2Chip, FpChip};
use crate::ecc::EccChip;
use crate::fields::FieldChip;
use crate::fields::PrimeField;
use crate::halo2_proofs::halo2curves::bn256::Fq12;
use crate::halo2_proofs::halo2curves::bn256::{G1Affine, G2Affine};
use halo2_base::{AssignedValue, Context};

// To avoid issues with mutably borrowing twice (not allowed in Rust), we only store fp_chip and construct g2_chip and fp12_chip in scope when needed for temporary mutable borrows
pub struct BlsSignatureChip<'chip, F: PrimeField> {
pub fp_chip: &'chip FpChip<'chip, F>,
pub pairing_chip: &'chip PairingChip<'chip, F>,
}

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

// 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}
// TODO add support for aggregating signatures over different messages
pub fn bls_signature_verify(
&self,
ctx: &mut Context<F>,
g1: G1Affine,
signatures: &[G2Affine],
pubkeys: &[G1Affine],
msghash: G2Affine,
) -> AssignedValue<F> {
assert!(
signatures.len() == pubkeys.len(),
"signatures and pubkeys must be the same length"
);
assert!(!signatures.is_empty(), "signatures must not be empty");
assert!(!pubkeys.is_empty(), "pubkeys must not be empty");

let g1_chip = EccChip::new(self.fp_chip);
let fp2_chip = Fp2Chip::<F>::new(self.fp_chip);
let g2_chip = EccChip::new(&fp2_chip);

let g1_assigned = self.pairing_chip.load_private_g1(ctx, g1);

let hash_m_assigned = self.pairing_chip.load_private_g2(ctx, msghash);

let signature_points = signatures
.iter()
.map(|pt| g2_chip.load_private::<G2Affine>(ctx, (pt.x, pt.y)))
.collect::<Vec<_>>();
let signature_agg_assigned = g2_chip.sum::<G2Affine>(ctx, signature_points);

let pubkey_points = pubkeys
.iter()
.map(|pt| g1_chip.load_private::<G1Affine>(ctx, (pt.x, pt.y)))
.collect::<Vec<_>>();
let pubkey_agg_assigned = g1_chip.sum::<G1Affine>(ctx, pubkey_points);

let fp12_chip = Fp12Chip::<F>::new(self.fp_chip);
let g12_chip = EccChip::new(&fp12_chip);
let neg_signature_assigned_g12 = g12_chip.negate(ctx, &signature_agg_assigned);

let multi_paired = self.pairing_chip.multi_miller_loop(
ctx,
vec![
(&g1_assigned, &neg_signature_assigned_g12),
(&pubkey_agg_assigned, &hash_m_assigned),
],
);
let result = fp12_chip.final_exp(ctx, multi_paired);

// Check signatures are verified
let fp12_one = fp12_chip.load_constant(ctx, Fq12::one());
fp12_chip.is_equal(ctx, result, fp12_one)
}
}
1 change: 1 addition & 0 deletions halo2-ecc/src/bn254/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::fields::vector::FieldVector;
use crate::fields::{fp, fp12, fp2};
use crate::halo2_proofs::halo2curves::bn256::{Fq, Fq12, Fq2};

pub mod bls_signature;
pub mod final_exp;
pub mod pairing;

Expand Down
16 changes: 4 additions & 12 deletions halo2-ecc/src/bn254/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,23 +453,15 @@ impl<'chip, F: PrimeField> PairingChip<'chip, F> {
Self { fp_chip }
}

pub fn load_private_g1_unchecked(
&self,
ctx: &mut Context<F>,
point: G1Affine,
) -> EcPoint<F, FpPoint<F>> {
pub fn load_private_g1(&self, ctx: &mut Context<F>, point: G1Affine) -> EcPoint<F, FpPoint<F>> {
let g1_chip = EccChip::new(self.fp_chip);
g1_chip.load_private_unchecked(ctx, (point.x, point.y))
g1_chip.load_private::<G1Affine>(ctx, (point.x, point.y))
}

pub fn load_private_g2_unchecked(
&self,
ctx: &mut Context<F>,
point: G2Affine,
) -> EcPoint<F, FqPoint<F>> {
pub fn load_private_g2(&self, ctx: &mut Context<F>, point: G2Affine) -> EcPoint<F, FqPoint<F>> {
let fp2_chip = Fp2Chip::new(self.fp_chip);
let g2_chip = EccChip::new(&fp2_chip);
g2_chip.load_private_unchecked(ctx, (point.x, point.y))
g2_chip.load_private::<G2Affine>(ctx, (point.x, point.y))
}

pub fn miller_loop(
Expand Down
245 changes: 245 additions & 0 deletions halo2-ecc/src/bn254/tests/bls_signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
use std::{
fs::{self, File},
io::{BufRead, BufReader},
};

use super::*;
use crate::{fields::FpStrategy, halo2_proofs::halo2curves::bn256::G2Affine};
use halo2_base::{
gates::{
builder::{
CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints,
RangeCircuitBuilder,
},
RangeChip,
},
halo2_proofs::{
halo2curves::{
bn256::{multi_miller_loop, G2Prepared, Gt},
pairing::MillerLoopResult,
},
poly::kzg::multiopen::{ProverGWC, VerifierGWC},
},
utils::fs::gen_srs,
Context,
};
use rand_core::OsRng;

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
struct BlsSignatureCircuitParams {
strategy: FpStrategy,
degree: u32,
num_advice: usize,
num_lookup_advice: usize,
num_fixed: usize,
lookup_bits: usize,
limb_bits: usize,
num_limbs: usize,
num_aggregation: u32,
}

/// Verify e(g1, signature_agg) = e(pubkey_agg, H(m))
fn bls_signature_test<F: PrimeField>(
ctx: &mut Context<F>,
params: BlsSignatureCircuitParams,
g1: G1Affine,
signatures: &[G2Affine],
pubkeys: &[G1Affine],
msghash: G2Affine,
) {
// Calculate halo2 pairing by multipairing
std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string());
let range = RangeChip::<F>::default(params.lookup_bits);
let fp_chip = FpChip::<F>::new(&range, params.limb_bits, params.num_limbs);
let pairing_chip = PairingChip::new(&fp_chip);
let bls_signature_chip = BlsSignatureChip::new(&fp_chip, &pairing_chip);
let result = bls_signature_chip.bls_signature_verify(ctx, g1, signatures, pubkeys, msghash);

// Calculate non-halo2 pairing by multipairing
let mut signatures_g2: G2Affine = signatures[0];
for i in 1..signatures.len() {
signatures_g2 = (signatures_g2 + signatures[i]).into();
}
let signature_g2_prepared = G2Prepared::from(signatures_g2);

let mut pubkeys_g1: G1Affine = pubkeys[0];
for i in 1..signatures.len() {
pubkeys_g1 = (pubkeys_g1 + pubkeys[i]).into();
}
let pubkey_aggregated = pubkeys_g1;

let hash_m_prepared = G2Prepared::from(-msghash);
let actual_result =
multi_miller_loop(&[(&g1, &signature_g2_prepared), (&pubkey_aggregated, &hash_m_prepared)])
.final_exponentiation();

// Compare the 2 results
assert_eq!(*result.value(), F::from(actual_result == Gt::identity()))
}

fn random_bls_signature_circuit(
params: BlsSignatureCircuitParams,
stage: CircuitBuilderStage,
break_points: Option<MultiPhaseThreadBreakPoints>,
) -> RangeCircuitBuilder<Fr> {
let k = params.degree as usize;
let mut builder = match stage {
CircuitBuilderStage::Mock => GateThreadBuilder::mock(),
CircuitBuilderStage::Prover => GateThreadBuilder::prover(),
CircuitBuilderStage::Keygen => GateThreadBuilder::keygen(),
};

assert!(params.num_aggregation > 0, "Cannot aggregate 0 signatures!");

// TODO: Implement hash_to_curve(msg) for arbitrary message
let msg_hash = G2Affine::random(OsRng);
let g1 = G1Affine::generator();

let mut sks: Vec<Fr> = Vec::new();
let mut signatures: Vec<G2Affine> = Vec::new();
let mut pubkeys: Vec<G1Affine> = Vec::new();

for _ in 0..params.num_aggregation {
let sk = Fr::random(OsRng);
let signature = G2Affine::from(msg_hash * sk);
let pubkey = G1Affine::from(G1Affine::generator() * sk);

sks.push(sk);
signatures.push(signature);
pubkeys.push(pubkey);
}

let start0 = start_timer!(|| format!("Witness generation for circuit in {stage:?} stage"));
bls_signature_test::<Fr>(builder.main(0), params, g1, &signatures, &pubkeys, msg_hash);

let circuit = match stage {
CircuitBuilderStage::Mock => {
builder.config(k, Some(20));
RangeCircuitBuilder::mock(builder)
}
CircuitBuilderStage::Keygen => {
builder.config(k, Some(20));
RangeCircuitBuilder::keygen(builder)
}
CircuitBuilderStage::Prover => RangeCircuitBuilder::prover(builder, break_points.unwrap()),
};
end_timer!(start0);
circuit
}

#[test]
fn test_bls_signature() {
let run_path = "configs/bn254/bls_signature_circuit.config";
let path = run_path;
let params: BlsSignatureCircuitParams = serde_json::from_reader(
File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")),
)
.unwrap();
println!("num_advice: {num_advice}", num_advice = params.num_advice);
let circuit = random_bls_signature_circuit(params, CircuitBuilderStage::Mock, None);
MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied();
}

#[test]
fn bench_bls_signature() -> Result<(), Box<dyn std::error::Error>> {
let rng = OsRng;
let config_path = "configs/bn254/bench_bls_signature.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/bn254").unwrap();
fs::create_dir_all("data").unwrap();

let results_path = "results/bn254/bls_signature_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,num_aggregation,proof_time,proof_size,verify_time")?;

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

let params = gen_srs(k);
let circuit = random_bls_signature_circuit(bench_params, CircuitBuilderStage::Keygen, None);

let vk_time = start_timer!(|| "Generating vkey");
let vk = keygen_vk(&params, &circuit)?;
end_timer!(vk_time);

let pk_time = start_timer!(|| "Generating pkey");
let pk = keygen_pk(&params, vk, &circuit)?;
end_timer!(pk_time);

let break_points = circuit.0.break_points.take();
drop(circuit);
// create a proof
let proof_time = start_timer!(|| "Proving time");
let circuit = random_bls_signature_circuit(
bench_params,
CircuitBuilderStage::Prover,
Some(break_points),
);
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
create_proof::<
KZGCommitmentScheme<Bn256>,
ProverGWC<'_, Bn256>,
Challenge255<G1Affine>,
_,
Blake2bWrite<Vec<u8>, G1Affine, Challenge255<G1Affine>>,
_,
>(&params, &pk, &[circuit], &[&[]], rng, &mut transcript)?;
let proof = transcript.finalize();
end_timer!(proof_time);

let proof_size = {
let path = format!(
"data/bls_signature_bn254_circuit_proof_{}_{}_{}_{}_{}_{}_{}_{}.data",
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,
bench_params.num_aggregation
);
let mut fd = File::create(&path)?;
fd.write_all(&proof)?;
let size = fd.metadata().unwrap().len();
fs::remove_file(path)?;
size
};

let verify_time = start_timer!(|| "Verify time");
let verifier_params = params.verifier_params();
let strategy = SingleStrategy::new(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
verify_proof::<
KZGCommitmentScheme<Bn256>,
VerifierGWC<'_, Bn256>,
Challenge255<G1Affine>,
Blake2bRead<&[u8], G1Affine, Challenge255<G1Affine>>,
SingleStrategy<'_, Bn256>,
>(verifier_params, pk.get_vk(), strategy, &[&[]], &mut transcript)
.unwrap();
end_timer!(verify_time);

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,
bench_params.num_aggregation,
proof_time.time.elapsed(),
proof_size,
verify_time.time.elapsed()
)?;
}
Ok(())
}
Loading

0 comments on commit f2eacb1

Please sign in to comment.