From 17890d5ac14263840f431b981cb00d68a9618e74 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Sun, 2 Jul 2023 23:21:34 +0800 Subject: [PATCH 1/7] feat: Add BLS signature verification for BN254 --- halo2-base/Cargo.toml | 7 +- .../configs/bn254/bench_bls_signature.config | 15 ++ .../bn254/bls_signature_circuit.config | 1 + halo2-ecc/src/bn254/bls_signature.rs | 108 ++++++++ halo2-ecc/src/bn254/mod.rs | 1 + halo2-ecc/src/bn254/tests/bls_signature.rs | 244 ++++++++++++++++++ halo2-ecc/src/bn254/tests/mod.rs | 2 + 7 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 halo2-ecc/configs/bn254/bench_bls_signature.config create mode 100644 halo2-ecc/configs/bn254/bls_signature_circuit.config create mode 100644 halo2-ecc/src/bn254/bls_signature.rs create mode 100644 halo2-ecc/src/bn254/tests/bls_signature.rs diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 33799495..59496e3c 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -17,7 +17,12 @@ serde_json = "1.0" log = "0.4" # Use Axiom's custom halo2 monorepo for faster proving when feature = "halo2-axiom" is on -halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "axiom/dev", package = "halo2_proofs", optional = true } +# halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "axiom/dev", package = "halo2_proofs", optional = true } +# Use below before Axiom's PR is merged +halo2_proofs_axiom = { git = "https://github.com/flyingnobita/halo2.git", branch = "bls12-381/dev", package = "halo2_proofs", optional = true } +# Use below for local development +# halo2_proofs_axiom = { path = "../../flyingnobita-halo2/halo2_proofs", package = "halo2_proofs", optional = true } + # Use PSE halo2 and halo2curves for compatibility when feature = "halo2-pse" is on halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02", optional = true } diff --git a/halo2-ecc/configs/bn254/bench_bls_signature.config b/halo2-ecc/configs/bn254/bench_bls_signature.config new file mode 100644 index 00000000..694ee974 --- /dev/null +++ b/halo2-ecc/configs/bn254/bench_bls_signature.config @@ -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} \ No newline at end of file diff --git a/halo2-ecc/configs/bn254/bls_signature_circuit.config b/halo2-ecc/configs/bn254/bls_signature_circuit.config new file mode 100644 index 00000000..1cfb3d21 --- /dev/null +++ b/halo2-ecc/configs/bn254/bls_signature_circuit.config @@ -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} diff --git a/halo2-ecc/src/bn254/bls_signature.rs b/halo2-ecc/src/bn254/bls_signature.rs new file mode 100644 index 00000000..4a57d21e --- /dev/null +++ b/halo2-ecc/src/bn254/bls_signature.rs @@ -0,0 +1,108 @@ +#![allow(non_snake_case)] + +use super::pairing::PairingChip; +use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +use crate::ecc::EccChip; +use crate::fields::{FieldChip, PrimeField}; +use crate::halo2_proofs::halo2curves::bn256::{G1Affine, G2Affine}; +use halo2_base::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, pairing_chip: &'chip PairingChip) -> 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, + g1: G1Affine, + signatures: &[G2Affine], + pubkeys: &[G1Affine], + msghash: G2Affine, + ) -> FqPoint { + 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::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + + let g1_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, g1); + // Checking element from G1 is on curve also check that it's in subgroup G1 since G1 has cofactor of 1 + g1_chip.assert_is_on_curve::( + ctx, + &g1_assigned, + ); + + let hash_m_assigned = self.pairing_chip.load_private_g2_unchecked(ctx, msghash); + g2_chip.assert_is_on_curve::( + ctx, + &hash_m_assigned, + ); + + let mut signature_agg_assigned = + self.pairing_chip.load_private_g2_unchecked(ctx, signatures[0]); + g2_chip.field_chip.enforce_less_than(ctx, signature_agg_assigned.x().clone()); + let mut pubkey_agg_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, pubkeys[0]); + g1_chip.field_chip.enforce_less_than(ctx, pubkey_agg_assigned.x().clone()); + + // loop through signatures and aggregate them + for (index, signature) in signatures.iter().enumerate() { + if index > 0 { + let signature_assigned = + self.pairing_chip.load_private_g2_unchecked(ctx, *signature); + g2_chip.field_chip.enforce_less_than(ctx, signature_assigned.x().clone()); + g2_chip + .assert_is_on_curve::( + ctx, + &signature_assigned, + ); + signature_agg_assigned = + g2_chip.add_unequal(ctx, &signature_agg_assigned, &signature_assigned, false); + } + } + + // loop through pubkeys and aggregate them + for (index, pubkey) in pubkeys.iter().enumerate() { + if index > 0 { + let pubkey_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, *pubkey); + g1_chip.field_chip.enforce_less_than(ctx, pubkey_assigned.x().clone()); + g1_chip + .assert_is_on_curve::( + ctx, + &pubkey_assigned, + ); + pubkey_agg_assigned = + g1_chip.add_unequal(ctx, &pubkey_agg_assigned, &pubkey_assigned, false); + } + } + + let fp12_chip = Fp12Chip::::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); + result + } +} diff --git a/halo2-ecc/src/bn254/mod.rs b/halo2-ecc/src/bn254/mod.rs index deed3c4d..ab0a4ee3 100644 --- a/halo2-ecc/src/bn254/mod.rs +++ b/halo2-ecc/src/bn254/mod.rs @@ -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; diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs new file mode 100644 index 00000000..24f276cd --- /dev/null +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -0,0 +1,244 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, +}; + +use super::*; +use crate::{ + fields::{FieldChip, FpStrategy}, + halo2_proofs::halo2curves::bn256::G2Affine, +}; +use halo2_base::{ + gates::{ + builder::{ + CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, + RangeCircuitBuilder, + }, + RangeChip, + }, + halo2_proofs::{ + halo2curves::{ + bn256::{multi_miller_loop, G2Prepared}, + 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( + ctx: &mut Context, + 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::::default(params.lookup_bits); + let fp_chip_1 = FpChip::::new(&range, params.limb_bits, params.num_limbs); + let fp_chip_2 = FpChip::::new(&range, params.limb_bits, params.num_limbs); + let pairing_chip = PairingChip::new(&fp_chip_1); + let bls_signature_chip = BlsSignatureChip::new(&fp_chip_2, &pairing_chip); + let result = bls_signature_chip.bls_signature_verify(ctx, g1, signatures, pubkeys, msghash); + + // Calculate non-halo2 pairing by multipairing + let signature_g2_prepared = G2Prepared::from(signatures.iter().sum::()); + let pubkey_aggregated = pubkeys.iter().sum::(); + 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 + let fp12_chip = Fp12Chip::new(&fp_chip_1); + assert_eq!( + format!("Gt({:?})", fp12_chip.get_assigned_value(&result.into())), + format!("{actual_result:?}"), + "Signatures do not match!" + ); +} + +fn random_bls_signature_circuit( + params: BlsSignatureCircuitParams, + stage: CircuitBuilderStage, + break_points: Option, +) -> RangeCircuitBuilder { + 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 = Vec::new(); + let mut signatures: Vec = Vec::new(); + let mut pubkeys: Vec = 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::(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> { + 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(¶ms, &circuit)?; + end_timer!(vk_time); + + let pk_time = start_timer!(|| "Generating pkey"); + let pk = keygen_pk(¶ms, 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, + ProverGWC<'_, Bn256>, + Challenge255, + _, + Blake2bWrite, G1Affine, Challenge255>, + _, + >(¶ms, &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(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + verify_proof::< + KZGCommitmentScheme, + VerifierGWC<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + 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(()) +} diff --git a/halo2-ecc/src/bn254/tests/mod.rs b/halo2-ecc/src/bn254/tests/mod.rs index 172300a1..89aea571 100644 --- a/halo2-ecc/src/bn254/tests/mod.rs +++ b/halo2-ecc/src/bn254/tests/mod.rs @@ -1,4 +1,5 @@ #![allow(non_snake_case)] +use super::bls_signature::BlsSignatureChip; use super::pairing::PairingChip; use super::*; use crate::{ecc::EccChip, fields::PrimeField}; @@ -24,6 +25,7 @@ use halo2_base::utils::fe_to_biguint; use serde::{Deserialize, Serialize}; use std::io::Write; +pub mod bls_signature; pub mod ec_add; pub mod fixed_base_msm; pub mod msm; From 553b60d2bd9a6066f3c1a4769bae3e9d6f12d1b1 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:25:22 +0800 Subject: [PATCH 2/7] Use load_private() and sum() as suggested --- halo2-ecc/src/bn254/bls_signature.rs | 56 +++++----------------------- halo2-ecc/src/bn254/pairing.rs | 16 ++------ halo2-ecc/src/bn254/tests/pairing.rs | 12 +++--- 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/halo2-ecc/src/bn254/bls_signature.rs b/halo2-ecc/src/bn254/bls_signature.rs index 4a57d21e..a720849e 100644 --- a/halo2-ecc/src/bn254/bls_signature.rs +++ b/halo2-ecc/src/bn254/bls_signature.rs @@ -3,7 +3,7 @@ use super::pairing::PairingChip; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; use crate::ecc::EccChip; -use crate::fields::{FieldChip, PrimeField}; +use crate::fields::PrimeField; use crate::halo2_proofs::halo2curves::bn256::{G1Affine, G2Affine}; use halo2_base::Context; @@ -41,55 +41,17 @@ impl<'chip, F: PrimeField> BlsSignatureChip<'chip, F> { let fp2_chip = Fp2Chip::::new(self.fp_chip); let g2_chip = EccChip::new(&fp2_chip); - let g1_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, g1); - // Checking element from G1 is on curve also check that it's in subgroup G1 since G1 has cofactor of 1 - g1_chip.assert_is_on_curve::( - ctx, - &g1_assigned, - ); - - let hash_m_assigned = self.pairing_chip.load_private_g2_unchecked(ctx, msghash); - g2_chip.assert_is_on_curve::( - ctx, - &hash_m_assigned, - ); + let g1_assigned = self.pairing_chip.load_private_g1(ctx, g1); - let mut signature_agg_assigned = - self.pairing_chip.load_private_g2_unchecked(ctx, signatures[0]); - g2_chip.field_chip.enforce_less_than(ctx, signature_agg_assigned.x().clone()); - let mut pubkey_agg_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, pubkeys[0]); - g1_chip.field_chip.enforce_less_than(ctx, pubkey_agg_assigned.x().clone()); + let hash_m_assigned = self.pairing_chip.load_private_g2(ctx, msghash); - // loop through signatures and aggregate them - for (index, signature) in signatures.iter().enumerate() { - if index > 0 { - let signature_assigned = - self.pairing_chip.load_private_g2_unchecked(ctx, *signature); - g2_chip.field_chip.enforce_less_than(ctx, signature_assigned.x().clone()); - g2_chip - .assert_is_on_curve::( - ctx, - &signature_assigned, - ); - signature_agg_assigned = - g2_chip.add_unequal(ctx, &signature_agg_assigned, &signature_assigned, false); - } - } + let signature_points = + signatures.iter().map(|pt| g2_chip.assign_point(ctx, *pt)).collect::>(); + let signature_agg_assigned = g2_chip.sum::(ctx, signature_points); - // loop through pubkeys and aggregate them - for (index, pubkey) in pubkeys.iter().enumerate() { - if index > 0 { - let pubkey_assigned = self.pairing_chip.load_private_g1_unchecked(ctx, *pubkey); - g1_chip.field_chip.enforce_less_than(ctx, pubkey_assigned.x().clone()); - g1_chip - .assert_is_on_curve::( - ctx, - &pubkey_assigned, - ); - pubkey_agg_assigned = - g1_chip.add_unequal(ctx, &pubkey_agg_assigned, &pubkey_assigned, false); - } - } + let pubkey_points = + pubkeys.iter().map(|pt| g1_chip.assign_point(ctx, *pt)).collect::>(); + let pubkey_agg_assigned = g1_chip.sum::(ctx, pubkey_points); let fp12_chip = Fp12Chip::::new(self.fp_chip); let g12_chip = EccChip::new(&fp12_chip); diff --git a/halo2-ecc/src/bn254/pairing.rs b/halo2-ecc/src/bn254/pairing.rs index 5a1cc19f..886985d4 100644 --- a/halo2-ecc/src/bn254/pairing.rs +++ b/halo2-ecc/src/bn254/pairing.rs @@ -453,23 +453,15 @@ impl<'chip, F: PrimeField> PairingChip<'chip, F> { Self { fp_chip } } - pub fn load_private_g1_unchecked( - &self, - ctx: &mut Context, - point: G1Affine, - ) -> EcPoint> { + pub fn load_private_g1(&self, ctx: &mut Context, point: G1Affine) -> EcPoint> { let g1_chip = EccChip::new(self.fp_chip); - g1_chip.load_private_unchecked(ctx, (point.x, point.y)) + g1_chip.load_private::(ctx, (point.x, point.y)) } - pub fn load_private_g2_unchecked( - &self, - ctx: &mut Context, - point: G2Affine, - ) -> EcPoint> { + pub fn load_private_g2(&self, ctx: &mut Context, point: G2Affine) -> EcPoint> { 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::(ctx, (point.x, point.y)) } pub fn miller_loop( diff --git a/halo2-ecc/src/bn254/tests/pairing.rs b/halo2-ecc/src/bn254/tests/pairing.rs index 2b93c89f..d00330ee 100644 --- a/halo2-ecc/src/bn254/tests/pairing.rs +++ b/halo2-ecc/src/bn254/tests/pairing.rs @@ -43,10 +43,10 @@ fn pairing_check_test( let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let chip = PairingChip::new(&fp_chip); - let P_assigned = chip.load_private_g1_unchecked(ctx, P); - let Q_assigned = chip.load_private_g2_unchecked(ctx, Q); - let S_assigned = chip.load_private_g1_unchecked(ctx, S); - let T_assigned = chip.load_private_g2_unchecked(ctx, G2Affine::generator()); + let P_assigned = chip.load_private_g1(ctx, P); + let Q_assigned = chip.load_private_g2(ctx, Q); + let S_assigned = chip.load_private_g1(ctx, S); + let T_assigned = chip.load_private_g2(ctx, G2Affine::generator()); chip.pairing_check(ctx, &Q_assigned, &P_assigned, &T_assigned, &S_assigned); } @@ -60,8 +60,8 @@ fn pairing_test( let range = RangeChip::::default(params.lookup_bits); let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); let chip = PairingChip::new(&fp_chip); - let P_assigned = chip.load_private_g1_unchecked(ctx, P); - let Q_assigned = chip.load_private_g2_unchecked(ctx, Q); + let P_assigned = chip.load_private_g1(ctx, P); + let Q_assigned = chip.load_private_g2(ctx, Q); // test optimal ate pairing let f = chip.pairing(ctx, &Q_assigned, &P_assigned); let actual_f = pairing(&P, &Q); From 5b1839a03f71bd4e0832d812f2f2be3d39922ea7 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Thu, 27 Jul 2023 00:24:20 +0800 Subject: [PATCH 3/7] Corrected as per request --- halo2-ecc/src/bn254/bls_signature.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/halo2-ecc/src/bn254/bls_signature.rs b/halo2-ecc/src/bn254/bls_signature.rs index a720849e..56d87d91 100644 --- a/halo2-ecc/src/bn254/bls_signature.rs +++ b/halo2-ecc/src/bn254/bls_signature.rs @@ -3,7 +3,9 @@ use super::pairing::PairingChip; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; 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::Context; @@ -45,12 +47,16 @@ impl<'chip, F: PrimeField> BlsSignatureChip<'chip, F> { let hash_m_assigned = self.pairing_chip.load_private_g2(ctx, msghash); - let signature_points = - signatures.iter().map(|pt| g2_chip.assign_point(ctx, *pt)).collect::>(); + let signature_points = signatures + .iter() + .map(|pt| g2_chip.load_private::(ctx, (pt.x, pt.y))) + .collect::>(); let signature_agg_assigned = g2_chip.sum::(ctx, signature_points); - let pubkey_points = - pubkeys.iter().map(|pt| g1_chip.assign_point(ctx, *pt)).collect::>(); + let pubkey_points = pubkeys + .iter() + .map(|pt| g1_chip.load_private::(ctx, (pt.x, pt.y))) + .collect::>(); let pubkey_agg_assigned = g1_chip.sum::(ctx, pubkey_points); let fp12_chip = Fp12Chip::::new(self.fp_chip); @@ -65,6 +71,14 @@ impl<'chip, F: PrimeField> BlsSignatureChip<'chip, F> { ], ); let result = fp12_chip.final_exp(ctx, multi_paired); + + // Check signatures are verified + assert_eq!( + format!("{:?}", fp12_chip.get_assigned_value(&result.clone().into())), + format!("{:?}", Fq12::one()), + "Signatures do not match!" + ); + result } } From abb39122a7943b6b0cdc7927adf145bab99e9642 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Fri, 28 Jul 2023 02:08:26 +0800 Subject: [PATCH 4/7] Update bls_signature_verify() to return AssignedValue --- halo2-ecc/src/bn254/bls_signature.rs | 15 +++++---------- halo2-ecc/src/bn254/tests/bls_signature.rs | 18 +++++++----------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/halo2-ecc/src/bn254/bls_signature.rs b/halo2-ecc/src/bn254/bls_signature.rs index 56d87d91..0e10a090 100644 --- a/halo2-ecc/src/bn254/bls_signature.rs +++ b/halo2-ecc/src/bn254/bls_signature.rs @@ -1,13 +1,13 @@ #![allow(non_snake_case)] use super::pairing::PairingChip; -use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +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::Context; +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> { @@ -31,7 +31,7 @@ impl<'chip, F: PrimeField> BlsSignatureChip<'chip, F> { signatures: &[G2Affine], pubkeys: &[G1Affine], msghash: G2Affine, - ) -> FqPoint { + ) -> AssignedValue { assert!( signatures.len() == pubkeys.len(), "signatures and pubkeys must be the same length" @@ -73,12 +73,7 @@ impl<'chip, F: PrimeField> BlsSignatureChip<'chip, F> { let result = fp12_chip.final_exp(ctx, multi_paired); // Check signatures are verified - assert_eq!( - format!("{:?}", fp12_chip.get_assigned_value(&result.clone().into())), - format!("{:?}", Fq12::one()), - "Signatures do not match!" - ); - - result + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + fp12_chip.is_equal(ctx, result, fp12_one) } } diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 24f276cd..00a6aff5 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -4,10 +4,7 @@ use std::{ }; use super::*; -use crate::{ - fields::{FieldChip, FpStrategy}, - halo2_proofs::halo2curves::bn256::G2Affine, -}; +use crate::{fields::FpStrategy, halo2_proofs::halo2curves::bn256::G2Affine}; use halo2_base::{ gates::{ builder::{ @@ -18,7 +15,7 @@ use halo2_base::{ }, halo2_proofs::{ halo2curves::{ - bn256::{multi_miller_loop, G2Prepared}, + bn256::{multi_miller_loop, G2Prepared, Gt}, pairing::MillerLoopResult, }, poly::kzg::multiopen::{ProverGWC, VerifierGWC}, @@ -68,12 +65,11 @@ fn bls_signature_test( .final_exponentiation(); // Compare the 2 results - let fp12_chip = Fp12Chip::new(&fp_chip_1); - assert_eq!( - format!("Gt({:?})", fp12_chip.get_assigned_value(&result.into())), - format!("{actual_result:?}"), - "Signatures do not match!" - ); + if *(result.value()) == 1.into() { + assert_eq!(actual_result, Gt::identity()) + } else { + assert_ne!(actual_result, Gt::identity()) + } } fn random_bls_signature_circuit( From c3126212ca2b8ff9d4787ca10ccae609bef56516 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Sat, 29 Jul 2023 16:08:11 +0800 Subject: [PATCH 5/7] Eliminate unnecessary fp_chip. Shortened result comparison --- halo2-ecc/src/bn254/tests/bls_signature.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 00a6aff5..8c7d0bd4 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -50,10 +50,9 @@ fn bls_signature_test( // Calculate halo2 pairing by multipairing std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); let range = RangeChip::::default(params.lookup_bits); - let fp_chip_1 = FpChip::::new(&range, params.limb_bits, params.num_limbs); - let fp_chip_2 = FpChip::::new(&range, params.limb_bits, params.num_limbs); - let pairing_chip = PairingChip::new(&fp_chip_1); - let bls_signature_chip = BlsSignatureChip::new(&fp_chip_2, &pairing_chip); + let fp_chip = FpChip::::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 @@ -65,11 +64,7 @@ fn bls_signature_test( .final_exponentiation(); // Compare the 2 results - if *(result.value()) == 1.into() { - assert_eq!(actual_result, Gt::identity()) - } else { - assert_ne!(actual_result, Gt::identity()) - } + assert_eq!(*result.value(), F::from(actual_result == Gt::identity())) } fn random_bls_signature_circuit( From 2e6d64a32cd57d042301f3c280e7ec401bbad19b Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:29:34 +0800 Subject: [PATCH 6/7] Use forloop instead of iterator for affine to avoid dependence on halo2_proofs --- halo2-base/Cargo.toml | 7 +------ halo2-ecc/src/bn254/tests/bls_signature.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 59496e3c..33799495 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -17,12 +17,7 @@ serde_json = "1.0" log = "0.4" # Use Axiom's custom halo2 monorepo for faster proving when feature = "halo2-axiom" is on -# halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "axiom/dev", package = "halo2_proofs", optional = true } -# Use below before Axiom's PR is merged -halo2_proofs_axiom = { git = "https://github.com/flyingnobita/halo2.git", branch = "bls12-381/dev", package = "halo2_proofs", optional = true } -# Use below for local development -# halo2_proofs_axiom = { path = "../../flyingnobita-halo2/halo2_proofs", package = "halo2_proofs", optional = true } - +halo2_proofs_axiom = { git = "https://github.com/axiom-crypto/halo2.git", branch = "axiom/dev", package = "halo2_proofs", optional = true } # Use PSE halo2 and halo2curves for compatibility when feature = "halo2-pse" is on halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02", optional = true } diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 8c7d0bd4..115ab8ed 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -56,8 +56,18 @@ fn bls_signature_test( let result = bls_signature_chip.bls_signature_verify(ctx, g1, signatures, pubkeys, msghash); // Calculate non-halo2 pairing by multipairing - let signature_g2_prepared = G2Prepared::from(signatures.iter().sum::()); - let pubkey_aggregated = pubkeys.iter().sum::(); + 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)]) From ba04cf5d6302d18c2ef729dfacbb7f05d0caec47 Mon Sep 17 00:00:00 2001 From: flyingnobita <46126470+flyingnobita@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:29:44 +0800 Subject: [PATCH 7/7] Add test to Github CI --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd87d1b5..0b21ae44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,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: |