diff --git a/Cargo.toml b/Cargo.toml index 2b1e260a..18292835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/halo2-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index 2aaa5166..116459e6 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -30,16 +30,71 @@ pub trait BigPrimeField: ScalarField { fn from_u64_digits(val: &[u64]) -> Self; } #[cfg(feature = "halo2-axiom")] -impl 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) + } } } @@ -95,7 +150,7 @@ pub trait ScalarField: PrimeField + FromUniformBytes<64> + From + Hash + O /// [ScalarField] that is ~256 bits long #[cfg(feature = "halo2-pse")] -pub trait BigPrimeField = PrimeField + 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]. /// diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index fba53531..fc7d87ca 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -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"] } @@ -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 } @@ -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"] diff --git a/halo2-ecc/configs/bls12_381/bench_bls_signature.config b/halo2-ecc/configs/bls12_381/bench_bls_signature.config new file mode 100644 index 00000000..6e68e0f6 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_bls_signature.config @@ -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} diff --git a/halo2-ecc/configs/bls12_381/bench_ec_add.config b/halo2-ecc/configs/bls12_381/bench_ec_add.config new file mode 100644 index 00000000..eccbbd46 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_ec_add.config @@ -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} diff --git a/halo2-ecc/configs/bls12_381/bench_pairing.config b/halo2-ecc/configs/bls12_381/bench_pairing.config new file mode 100644 index 00000000..510a0c28 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_pairing.config @@ -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} diff --git a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config new file mode 100644 index 00000000..fc6686bb --- /dev/null +++ b/halo2-ecc/configs/bls12_381/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":104,"num_limbs":5,"num_aggregation":30} diff --git a/halo2-ecc/configs/bls12_381/ec_add_circuit.config b/halo2-ecc/configs/bls12_381/ec_add_circuit.config new file mode 100644 index 00000000..f5d38d53 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/ec_add_circuit.config @@ -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} diff --git a/halo2-ecc/configs/bls12_381/pairing_circuit.config b/halo2-ecc/configs/bls12_381/pairing_circuit.config new file mode 100644 index 00000000..1baf52d2 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -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} diff --git a/halo2-ecc/src/bigint/carry_mod.rs b/halo2-ecc/src/bigint/carry_mod.rs index a9667d79..098659c2 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -56,11 +56,11 @@ pub fn crt( // 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 @@ -138,13 +138,24 @@ pub fn crt( // 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] }; diff --git a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs index 13523ba5..26af73af 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -34,7 +34,10 @@ pub fn crt( // 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 @@ -90,7 +93,12 @@ pub fn crt( // 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] }; diff --git a/halo2-ecc/src/bls12_381/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs new file mode 100644 index 00000000..bd34f750 --- /dev/null +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -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, pairing_chip: &'chip PairingChip) -> 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, + signature: G2Affine, + pubkey: G1Affine, + msghash: G2Affine, + ) -> (EcPoint>>, EcPoint>>, EcPoint>) { + 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, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + ) -> AssignedValue { + 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::::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, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + ) { + 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::::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, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + g1_neg: EcPoint>, + ) -> FieldVector> { + self.pairing_chip.batched_pairing(ctx, &[(&g1_neg, &signature), (&pubkey, &msghash)]) + } +} diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs new file mode 100644 index 00000000..630dcc33 --- /dev/null +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -0,0 +1,332 @@ +use super::XI_0; +use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +use super::{Fq, Fq12, Fq2, FROBENIUS_COEFF_FQ12_C1}; +use crate::fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}; +use halo2_base::utils::BigPrimeField; +use halo2_base::{gates::GateInstructions, utils::modulus, Context, QuantumCell::Constant}; +use num_bigint::BigUint; + +impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { + // computes a ** (p ** power) + // only works for p = 3 (mod 4) and p = 1 (mod 6) + pub fn frobenius_map( + &self, + ctx: &mut Context, + a: &>::FieldPoint, + power: usize, + ) -> >::FieldPoint { + assert_eq!(modulus::() % 4u64, BigUint::from(3u64)); + assert_eq!(modulus::() % 6u64, BigUint::from(1u64)); + assert_eq!(a.0.len(), 12); + let pow = power % 12; + let mut out_fp2 = Vec::with_capacity(6); + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + for i in 0..6 { + let frob_coeff = FROBENIUS_COEFF_FQ12_C1[pow].pow_vartime(&[i as u64]); + // possible optimization (not implemented): load `frob_coeff` as we multiply instead of loading first + // frobenius map is used infrequently so this is a small optimization + + let mut a_fp2 = FieldVector(vec![a[i].clone(), a[i + 6].clone()]); + if pow % 2 != 0 { + a_fp2 = fp2_chip.conjugate(ctx, a_fp2); + } + // if `frob_coeff` is in `Fp` and not just `Fp2`, then we can be more efficient in multiplication + if frob_coeff == Fq2::one() { + out_fp2.push(a_fp2); + } else if frob_coeff.c1 == Fq::zero() { + let frob_fixed = fp_chip.load_constant(ctx, frob_coeff.c0); + { + let out_nocarry = fp2_chip.0.fp_mul_no_carry(ctx, a_fp2, frob_fixed); + out_fp2.push(fp2_chip.carry_mod(ctx, out_nocarry)); + } + } else { + let frob_fixed = fp2_chip.load_constant(ctx, frob_coeff); + out_fp2.push(fp2_chip.mul(ctx, a_fp2, frob_fixed)); + } + } + + let out_coeffs = out_fp2 + .iter() + .map(|x| x[0].clone()) + .chain(out_fp2.iter().map(|x| x[1].clone())) + .collect(); + + FieldVector(out_coeffs) + } + + // assume input is an element of Fp12 in the cyclotomic subgroup GΦ₁₂ + // A cyclotomic group is a subgroup of Fp^n defined by + // GΦₙ(p) = {α ∈ Fpⁿ : α^{Φₙ(p)} = 1} + + // below we implement compression and decompression for an element GΦ₁₂ following Theorem 3.1 of https://eprint.iacr.org/2010/542.pdf + // Fp4 = Fp2(w^3) where (w^3)^2 = XI_0 +u + // Fp12 = Fp4(w) where w^3 = w^3 + + /// in = g0 + g2 w + g4 w^2 + g1 w^3 + g3 w^4 + g5 w^5 where g_i = g_i0 + g_i1 * u are elements of Fp2 + /// out = Compress(in) = [ g2, g3, g4, g5 ] + pub fn cyclotomic_compress(&self, a: &FqPoint) -> Vec> { + let a = &a.0; + let g2 = FieldVector(vec![a[1].clone(), a[1 + 6].clone()]); + let g3 = FieldVector(vec![a[4].clone(), a[4 + 6].clone()]); + let g4 = FieldVector(vec![a[2].clone(), a[2 + 6].clone()]); + let g5 = FieldVector(vec![a[5].clone(), a[5 + 6].clone()]); + vec![g2, g3, g4, g5] + } + + /// Input: + /// * `compression = [g2, g3, g4, g5]` where g_i are proper elements of Fp2 + /// Output: + /// * `Decompress(compression) = g0 + g2 w + g4 w^2 + g1 w^3 + g3 w^4 + g5 w^5` where + /// * All elements of output are proper elements of Fp2 and: + /// c = XI0 + u + /// if g2 != 0: + /// g1 = (g5^2 * c + 3 g4^2 - 2 g3)/(4g2) + /// g0 = (2 g1^2 + g2 * g5 - 3 g3*g4) * c + 1 + /// if g2 = 0: + /// g1 = (2 g4 * g5)/g3 + /// g0 = (2 g1^2 - 3 g3 * g4) * c + 1 + pub fn cyclotomic_decompress( + &self, + ctx: &mut Context, + compression: Vec>, + ) -> FqPoint { + let [g2, g3, g4, g5]: [_; 4] = compression.try_into().unwrap(); + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + let g5_sq = fp2_chip.mul_no_carry(ctx, &g5, &g5); + let g5_sq_c = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, g5_sq); + + let g4_sq = fp2_chip.mul_no_carry(ctx, &g4, &g4); + let g4_sq_3 = fp2_chip.scalar_mul_no_carry(ctx, &g4_sq, 3); + let g3_2 = fp2_chip.scalar_mul_no_carry(ctx, &g3, 2); + + let mut g1_num = fp2_chip.add_no_carry(ctx, &g5_sq_c, &g4_sq_3); + g1_num = fp2_chip.sub_no_carry(ctx, &g1_num, &g3_2); + // can divide without carrying g1_num or g1_denom (I think) + let g2_4 = fp2_chip.scalar_mul_no_carry(ctx, &g2, 4); + let g1_1 = fp2_chip.divide_unsafe(ctx, &g1_num, &g2_4); + + let g4_g5 = fp2_chip.mul_no_carry(ctx, &g4, &g5); + let g1_num = fp2_chip.scalar_mul_no_carry(ctx, &g4_g5, 2); + let g1_0 = fp2_chip.divide_unsafe(ctx, &g1_num, &g3); + + let g2_is_zero = fp2_chip.is_zero(ctx, &g2); + // resulting `g1` is already in "carried" format (witness is in `[0, p)`) + let g1 = fp2_chip.0.select(ctx, g1_0, g1_1, g2_is_zero); + + // share the computation of 2 g1^2 between the two cases + let g1_sq = fp2_chip.mul_no_carry(ctx, &g1, &g1); + let g1_sq_2 = fp2_chip.scalar_mul_no_carry(ctx, &g1_sq, 2); + + let g2_g5 = fp2_chip.mul_no_carry(ctx, &g2, &g5); + let g3_g4 = fp2_chip.mul_no_carry(ctx, &g3, &g4); + let g3_g4_3 = fp2_chip.scalar_mul_no_carry(ctx, &g3_g4, 3); + let temp = fp2_chip.add_no_carry(ctx, &g1_sq_2, &g2_g5); + let temp = fp2_chip.0.select(ctx, g1_sq_2, temp, g2_is_zero); + let temp = fp2_chip.sub_no_carry(ctx, &temp, &g3_g4_3); + let mut g0 = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, temp); + + // compute `g0 + 1` + g0[0].truncation.limbs[0] = + fp2_chip.gate().add(ctx, g0[0].truncation.limbs[0], Constant(F::ONE)); + g0[0].native = fp2_chip.gate().add(ctx, g0[0].native, Constant(F::ONE)); + g0[0].truncation.max_limb_bits += 1; + g0[0].value += 1usize; + + // finally, carry g0 + let g0 = fp2_chip.carry_mod(ctx, g0); + + let mut g0 = g0.into_iter(); + let mut g1 = g1.into_iter(); + let mut g2 = g2.into_iter(); + let mut g3 = g3.into_iter(); + let mut g4 = g4.into_iter(); + let mut g5 = g5.into_iter(); + + let mut out_coeffs = Vec::with_capacity(12); + for _ in 0..2 { + out_coeffs.append(&mut vec![ + g0.next().unwrap(), + g2.next().unwrap(), + g4.next().unwrap(), + g1.next().unwrap(), + g3.next().unwrap(), + g5.next().unwrap(), + ]); + } + FieldVector(out_coeffs) + } + + // input is [g2, g3, g4, g5] = C(g) in compressed format of `cyclotomic_compress` + // assume all inputs are proper Fp2 elements + // output is C(g^2) = [h2, h3, h4, h5] computed using Theorem 3.2 of https://eprint.iacr.org/2010/542.pdf + // all output elements are proper Fp2 elements (with carry) + // c = XI_0 + u + // h2 = 2(g2 + 3*c*B_45) + // h3 = 3(A_45 - (c+1)B_45) - 2g3 + // h4 = 3(A_23 - (c+1)B_23) - 2g4 + // h5 = 2(g5 + 3B_23) + // A_ij = (g_i + g_j)(g_i + c g_j) + // B_ij = g_i g_j + pub fn cyclotomic_square( + &self, + ctx: &mut Context, + compression: &[FqPoint], + ) -> Vec> { + assert_eq!(compression.len(), 4); + let g2 = &compression[0]; + let g3 = &compression[1]; + let g4 = &compression[2]; + let g5 = &compression[3]; + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + + let g2_plus_g3 = fp2_chip.add_no_carry(ctx, g2, g3); + let cg3 = mul_no_carry_w6::, XI_0>(fp_chip, ctx, g3.into()); + let g2_plus_cg3 = fp2_chip.add_no_carry(ctx, g2, &cg3); + let a23 = fp2_chip.mul_no_carry(ctx, &g2_plus_g3, &g2_plus_cg3); + + let g4_plus_g5 = fp2_chip.add_no_carry(ctx, g4, g5); + let cg5 = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, g5.into()); + let g4_plus_cg5 = fp2_chip.add_no_carry(ctx, g4, &cg5); + let a45 = fp2_chip.mul_no_carry(ctx, &g4_plus_g5, &g4_plus_cg5); + + let b23 = fp2_chip.mul_no_carry(ctx, g2, g3); + let b45 = fp2_chip.mul_no_carry(ctx, g4, g5); + let b45_c = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, b45.clone()); + + let mut temp = fp2_chip.scalar_mul_and_add_no_carry(ctx, &b45_c, g2, 3); + let h2 = fp2_chip.scalar_mul_no_carry(ctx, &temp, 2); + + temp = fp2_chip.add_no_carry(ctx, b45_c, b45); + temp = fp2_chip.sub_no_carry(ctx, &a45, temp); + temp = fp2_chip.scalar_mul_no_carry(ctx, temp, 3); + let h3 = fp2_chip.scalar_mul_and_add_no_carry(ctx, g3, temp, -2); + + const XI0_PLUS_1: i64 = XI_0 + 1; + // (c + 1) = (XI_0 + 1) + u + temp = mul_no_carry_w6::, XI0_PLUS_1>(fp_chip, ctx, b23.clone()); + temp = fp2_chip.sub_no_carry(ctx, &a23, temp); + temp = fp2_chip.scalar_mul_no_carry(ctx, temp, 3); + let h4 = fp2_chip.scalar_mul_and_add_no_carry(ctx, g4, temp, -2); + + temp = fp2_chip.scalar_mul_and_add_no_carry(ctx, b23, g5, 3); + let h5 = fp2_chip.scalar_mul_no_carry(ctx, temp, 2); + + [h2, h3, h4, h5].into_iter().map(|h| fp2_chip.carry_mod(ctx, h)).collect() + } + + fn cyclotomic_square_for(&self, ctx: &mut Context, a: &FqPoint, n: usize) -> FqPoint { + let mut tv = self.cyclotomic_compress(a); + for _ in 0..n { + tv = self.cyclotomic_square(ctx, &tv); + } + self.cyclotomic_decompress(ctx, tv) + } + + /// # Assumptions + /// * `a` is a nonzero element in the cyclotomic subgroup + pub fn cyclotomic_pow(&self, ctx: &mut Context, a: FqPoint, exp: u64) -> FqPoint { + let mut res = self.load_private(ctx, Fq12::one()); + let mut found_one = false; + + for bit in (0..64).rev().map(|i| ((exp >> i) & 1) == 1) { + if found_one { + let compressed = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&res)); + res = self.cyclotomic_decompress(ctx, compressed); + } else { + found_one = bit; + } + + if bit { + res = self.mul(ctx, &res, &a); + } + } + + self.conjugate(ctx, res) + } + + // Optimized implementation of cyclotomic_pow on BLS_X for BLS12-381 + // Reference: https://github.com/celer-network/brevis-circuits/blob/fe7936f7f/gadgets/pairing_bls12381/tower.go#L801 + fn cyclotomic_pow_bls_x(&self, ctx: &mut Context, a: &FqPoint) -> FqPoint { + let mut tv = self.cyclotomic_compress(a); + for _ in 0..15 { + tv = self.cyclotomic_square(ctx, &tv); + } + let t0 = self.cyclotomic_decompress(ctx, tv.clone()); + + for _ in 0..32 { + tv = self.cyclotomic_square(ctx, &tv); + } + let t1 = self.cyclotomic_decompress(ctx, tv); + + let mut res = self.mul(ctx, &t0, &t1); + let mut t1 = self.cyclotomic_square_for(ctx, &t1, 9); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 3); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 2); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 1); + + res = self.mul(ctx, &res, &t1); + res = self.conjugate(ctx, res); + + self.cyclotomic_square_for(ctx, &res, 1) + } + + // out = in^{(q^12 - 1)/r} + pub fn final_exp( + &self, + ctx: &mut Context, + a: >::FieldPoint, + ) -> >::FieldPoint { + // a^{q^6} = conjugate of a + let f1 = self.conjugate(ctx, a.clone()); + let f2 = self.divide_unsafe(ctx, &f1, a); + let f3 = self.frobenius_map(ctx, &f2, 2); + + let t2 = self.mul(ctx, &f3, &f2); + let t1 = { + let tv = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&t2)); + let tv = self.cyclotomic_decompress(ctx, tv); + self.conjugate(ctx, tv) + }; + let t3 = self.cyclotomic_pow_bls_x(ctx, &t2); + let t4 = { + let tv = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&t3)); + self.cyclotomic_decompress(ctx, tv) + }; + + let t5 = self.mul(ctx, &t1, &t3); + let t1 = self.cyclotomic_pow_bls_x(ctx, &t5); + + let t0 = self.cyclotomic_pow_bls_x(ctx, &t1.clone()); + + let t6 = self.cyclotomic_pow_bls_x(ctx, &t0.clone()); + let t6 = self.mul(ctx, &t6, &t4); + let t4 = self.cyclotomic_pow_bls_x(ctx, &t6.clone()); + let t5 = self.conjugate(ctx, t5); + let t4 = self.mul(ctx, &t4, &t5); + let t4 = self.mul(ctx, &t4, &t2); + let t5 = self.conjugate(ctx, t2.clone()); + let t1 = self.mul(ctx, &t1, &t2); + + let t1 = self.frobenius_map(ctx, &t1, 3); + let t6 = self.mul(ctx, &t6, &t5); + let t6 = self.frobenius_map(ctx, &t6, 1); + let t3 = self.mul(ctx, &t3, &t0); + let t3 = self.frobenius_map(ctx, &t3, 2); + let t3 = self.mul(ctx, &t3, &t1); + let t3 = self.mul(ctx, &t3, &t6); + + self.mul(ctx, &t3, &t4) + } +} diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs new file mode 100644 index 00000000..d8988f85 --- /dev/null +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -0,0 +1,27 @@ +use crate::bigint::ProperCrtUint; +use crate::fields::vector::FieldVector; +use crate::fields::{fp, fp12, fp2}; + +pub mod bls_signature; +pub mod final_exp; +pub mod pairing; + +#[cfg(feature = "halo2-axiom")] +pub(crate) use crate::halo2_proofs::halo2curves::bls12_381::{ + Fq, Fq12, Fq2, G1Affine, G2Affine, BLS_X, BLS_X_IS_NEGATIVE, FROBENIUS_COEFF_FQ12_C1, +}; +#[cfg(feature = "halo2-pse")] +pub(crate) use halo2curves::bls12_381::{ + Fq, Fq12, Fq2, G1Affine, G2Affine, BLS_X, BLS_X_IS_NEGATIVE, FROBENIUS_COEFF_FQ12_C1, +}; + +pub(crate) const XI_0: i64 = 1; + +pub type FpChip<'range, F> = fp::FpChip<'range, F, Fq>; +pub type FpPoint = ProperCrtUint; +pub type FqPoint = FieldVector>; +pub type Fp2Chip<'chip, F> = fp2::Fp2Chip<'chip, F, FpChip<'chip, F>, Fq2>; +pub type Fp12Chip<'chip, F> = fp12::Fp12Chip<'chip, F, FpChip<'chip, F>, Fq12, XI_0>; + +#[cfg(test)] +pub(crate) mod tests; diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs new file mode 100644 index 00000000..39e08ed6 --- /dev/null +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -0,0 +1,458 @@ +#![allow(non_snake_case)] +use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint, XI_0}; +use super::{Fq12, G1Affine, G2Affine, BLS_X, BLS_X_IS_NEGATIVE}; +use crate::fields::vector::FieldVector; +use crate::{ + ecc::{EcPoint, EccChip}, + fields::fp12::mul_no_carry_w6, + fields::FieldChip, +}; + +use halo2_base::utils::BigPrimeField; +use halo2_base::Context; + +// Inputs: +// Q0 = (x_1, y_1) and Q1 = (x_2, y_2) are points in E(Fp2) +// P is point (X, Y) in E(Fp) +// Assuming Q0 != Q1 +// Output: +// line_{Psi(Q0), Psi(Q1)}(P) where Psi(x,y) = (w^2 x, w^3 y) +// - equals w^3 (y_1 - y_2) X + w^2 (x_2 - x_1) Y + w^5 (x_1 y_2 - x_2 y_1) =: out3 * w^3 + out2 * w^2 + out5 * w^5 where out2, out3, out5 are Fp2 points +// Output is [None, None, out2, out3, None, out5] as vector of `Option`s +pub fn sparse_line_function_unequal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + Q: (&EcPoint>, &EcPoint>), + P: &EcPoint>, +) -> Vec>> { + let (x_1, y_1) = (&Q.0.x, &Q.0.y); + let (x_2, y_2) = (&Q.1.x, &Q.1.y); + let (X, Y) = (&P.x, &P.y); + assert_eq!(x_1.0.len(), 2); + assert_eq!(y_1.0.len(), 2); + assert_eq!(x_2.0.len(), 2); + assert_eq!(y_2.0.len(), 2); + + let y1_minus_y2 = fp2_chip.sub_no_carry(ctx, y_1, y_2); + let x2_minus_x1 = fp2_chip.sub_no_carry(ctx, x_2, x_1); + let x1y2 = fp2_chip.mul_no_carry(ctx, x_1, y_2); + let x2y1 = fp2_chip.mul_no_carry(ctx, x_2, y_1); + + let out3 = fp2_chip.0.fp_mul_no_carry(ctx, y1_minus_y2, X); + let out4 = fp2_chip.0.fp_mul_no_carry(ctx, x2_minus_x1, Y); + let out2 = fp2_chip.sub_no_carry(ctx, &x1y2, &x2y1); + + // so far we have not "carried mod p" for any of the outputs + // we do this below + [None, Some(out2), None, Some(out3), Some(out4), None] + .into_iter() + .map(|option_nc| option_nc.map(|nocarry| fp2_chip.carry_mod(ctx, nocarry))) + .collect() +} + +// Assuming curve is of form Y^2 = X^3 + b (a = 0) to save operations +// Inputs: +// Q = (x, y) is a point in E(Fp) +// P = (P.x, P.y) in E(Fp2) +// Output: +// line_{Psi(Q), Psi(Q)}(P) where Psi(x,y) = (w^2 x, w^3 y) +// - equals (3x^3 - 2y^2)(XI_0 + u) + w^4 (-3 x^2 * Q.x) + w^3 (2 y * Q.y) =: out0 + out4 * w^4 + out3 * w^3 where out0, out3, out4 are Fp2 points +// Output is [out0, None, out2, out3, None, None] as vector of `Option`s +pub fn sparse_line_function_equal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, +) -> Vec>> { + let (x, y) = (&Q.x, &Q.y); + assert_eq!(x.0.len(), 2); + assert_eq!(y.0.len(), 2); + + let x_sq = fp2_chip.mul(ctx, x, x); + + let x_cube = fp2_chip.mul_no_carry(ctx, &x_sq, x); + let three_x_cu = fp2_chip.scalar_mul_no_carry(ctx, &x_cube, 3); + let y_sq = fp2_chip.mul_no_carry(ctx, y, y); + let two_y_sq = fp2_chip.scalar_mul_no_carry(ctx, &y_sq, 2); + let out0 = fp2_chip.sub_no_carry(ctx, &three_x_cu, &two_y_sq); + + let x_sq_Px = fp2_chip.0.fp_mul_no_carry(ctx, x_sq, &P.x); + let out2 = fp2_chip.scalar_mul_no_carry(ctx, x_sq_Px, -3); + + let y_Py = fp2_chip.0.fp_mul_no_carry(ctx, y.clone(), &P.y); + let out3 = fp2_chip.scalar_mul_no_carry(ctx, &y_Py, 2); + + // so far we have not "carried mod p" for any of the outputs + // we do this below + [Some(out0), None, Some(out2), Some(out3), None, None] + .into_iter() + .map(|option_nc| option_nc.map(|nocarry| fp2_chip.carry_mod(ctx, nocarry))) + .collect() +} + +// multiply Fp12 point `a` with Fp12 point `b` where `b` is len 6 vector of Fp2 points, where some are `None` to represent zero. +// Assumes `b` is not vector of all `None`s +pub fn sparse_fp12_multiply( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + a: &FqPoint, + b_fp2_coeffs: &[Option>], +) -> FqPoint { + assert_eq!(a.0.len(), 12); + assert_eq!(b_fp2_coeffs.len(), 6); + let mut a_fp2_coeffs = Vec::with_capacity(6); + for i in 0..6 { + a_fp2_coeffs.push(FieldVector(vec![a[i].clone(), a[i + 6].clone()])); + } + // a * b as element of Fp2[w] without evaluating w^6 = (XI_0 + u) + let mut prod_2d = vec![None; 11]; + for i in 0..6 { + for j in 0..6 { + prod_2d[i + j] = + match (prod_2d[i + j].clone(), &a_fp2_coeffs[i], b_fp2_coeffs[j].as_ref()) { + (a, _, None) => a, + (None, a, Some(b)) => { + let ab = fp2_chip.mul_no_carry(ctx, a, b); + Some(ab) + } + (Some(a), b, Some(c)) => { + let bc = fp2_chip.mul_no_carry(ctx, b, c); + let out = fp2_chip.add_no_carry(ctx, &a, &bc); + Some(out) + } + }; + } + } + + let mut out_fp2 = Vec::with_capacity(6); + for i in 0..6 { + // prod_2d[i] + prod_2d[i+6] * w^6 + let prod_nocarry = if i != 5 { + let eval_w6 = prod_2d[i + 6] + .as_ref() + .map(|a| mul_no_carry_w6::<_, _, XI_0>(fp2_chip.fp_chip(), ctx, a.clone())); + match (prod_2d[i].as_ref(), eval_w6) { + (None, b) => b.unwrap(), // Our current use cases of 235 and 034 sparse multiplication always result in non-None value + (Some(a), None) => a.clone(), + (Some(a), Some(b)) => fp2_chip.add_no_carry(ctx, a, &b), + } + } else { + prod_2d[i].clone().unwrap() + }; + let prod = fp2_chip.carry_mod(ctx, prod_nocarry); + out_fp2.push(prod); + } + + let mut out_coeffs = Vec::with_capacity(12); + for fp2_coeff in &out_fp2 { + out_coeffs.push(fp2_coeff[0].clone()); + } + for fp2_coeff in &out_fp2 { + out_coeffs.push(fp2_coeff[1].clone()); + } + FieldVector(out_coeffs) +} + +// Input: +// - g is Fp12 point +// - P = (P0, P1) with Q0, Q1 points in E(Fp2) +// - Q is point in E(Fp) +// Output: +// - out = g * l_{Psi(Q0), Psi(Q1)}(P) as Fp12 point +pub fn fp12_multiply_with_line_unequal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + g: &FqPoint, + P: &EcPoint>, + Q: (&EcPoint>, &EcPoint>), +) -> FqPoint { + let line = sparse_line_function_unequal::(fp2_chip, ctx, Q, P); + sparse_fp12_multiply::(fp2_chip, ctx, g, &line) +} + +// Input: +// - g is Fp12 point +// - P is point in E(Fp2) +// - Q is point in E(Fp) +// Output: +// - out = g * l_{Psi(Q), Psi(Q)}(P) as Fp12 point +pub fn fp12_multiply_with_line_equal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + g: &FqPoint, + P: &EcPoint>, + Q: &EcPoint>, +) -> FqPoint { + let line = sparse_line_function_equal::(fp2_chip, ctx, P, Q); + sparse_fp12_multiply::(fp2_chip, ctx, g, &line) +} + +// Assuming curve is of form `y^2 = x^3 + b` for now (a = 0) for less operations +// Value of `b` is never used +// Inputs: +// - Q = (x, y) is a point in E(Fp2) +// - P is a point in E(Fp) +// Output: +// - f_{loop_count}(Q,P) * l_{[loop_count] Q', Frob_p(Q')}(P) * l_{[loop_count] Q' + Frob_p(Q'), -Frob_p^2(Q')}(P) +// - where we start with `f_1(Q,P) = 1` and use Miller's algorithm f_{i+j} = f_i * f_j * l_{i,j}(Q,P) +// - Q' = Psi(Q) in E(Fp12) +// - Frob_p(x,y) = (x^p, y^p) +pub fn miller_loop( + ecc_chip: &EccChip>, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, +) -> FqPoint { + let sparse_f = sparse_line_function_equal::(ecc_chip.field_chip(), ctx, P, Q); + assert_eq!(sparse_f.len(), 6); + + let fp_chip = ecc_chip.field_chip.fp_chip(); + let zero_fp = fp_chip.load_constant(ctx, Fq::zero()); + let mut f_coeffs = Vec::with_capacity(12); + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[0].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[1].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + + let mut f = FieldVector(f_coeffs); + let fp12_chip = Fp12Chip::::new(fp_chip); + + let mut r = Q.clone(); + + let mut found_one = true; + let mut prev_bit = true; + + // double Q as in the first part of Miller loop + r = ecc_chip.double(ctx, r.clone()); + + // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) + // resequence the loop to perfrom additiona step for the previous iteration first and then doubling step + for bit in (0..62).rev().map(|i| ((BLS_X >> i) & 1) == 1) { + if prev_bit { + f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, P, (&r, Q)); + r = ecc_chip.add_unequal(ctx, r.clone(), Q.clone(), false); + } + + prev_bit = bit; + + if !found_one { + found_one = bit; + continue; + } + + f = fp12_chip.mul(ctx, &f, &f); + + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, P, &r); + r = ecc_chip.double(ctx, r.clone()); + } + + if BLS_X_IS_NEGATIVE { + f = fp12_chip.conjugate(ctx, f) + } + + f +} + +// let pairs = [(a_i, b_i)], a_i in G_1, b_i in G_2 +// output is Prod_i e'(a_i, b_i), where e'(a_i, b_i) is the output of `miller_loop_BN(b_i, a_i)` +pub fn multi_miller_loop( + ecc_chip: &EccChip>, + ctx: &mut Context, + pairs: Vec<(&EcPoint>, &EcPoint>)>, +) -> FqPoint { + let fp_chip = ecc_chip.field_chip.fp_chip(); + + // initialize the first line function into Fq12 point with first (Q,P) pair + // this is to skip first iteration by leveraging the fact that f = 1 + let mut f = { + let sparse_f = + sparse_line_function_equal::(ecc_chip.field_chip(), ctx, pairs[0].0, pairs[0].1); + assert_eq!(sparse_f.len(), 6); + + let zero_fp = fp_chip.load_constant(ctx, Fq::zero()); + let mut f_coeffs = Vec::with_capacity(12); + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[0].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[1].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + FieldVector(f_coeffs) + }; + + // process second (Q,P) pair + for &(p, q) in pairs.iter().skip(1) { + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, p, q); + } + + let fp12_chip = Fp12Chip::::new(fp_chip); + + let mut r = pairs.iter().map(|pair| pair.1.clone()).collect::>(); + let mut found_one = true; + let mut prev_bit = true; + + // double Q as in the first part of Miller loop + for r in r.iter_mut() { + *r = ecc_chip.double(ctx, r.clone()); + } + + // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) + // resequence the loop to perfrom additiona step for the previous iteration first and then doubling step + for bit in (0..62).rev().map(|i| ((BLS_X >> i) & 1) == 1) { + if prev_bit { + for (r, &(p, q)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, p, (r, q)); + *r = ecc_chip.add_unequal(ctx, r.clone(), q.clone(), false); + } + } + + prev_bit = bit; + + if !found_one { + found_one = bit; + continue; + } + + f = fp12_chip.mul(ctx, &f, &f); + + for (r, &(p, _q)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, p, r); + *r = ecc_chip.double(ctx, r.clone()); + } + } + + // Apperently Gt conjugation can be skipped for multi miller loop. However, cannot find evidence for this. + // if BLS_X_IS_NEGATIVE { + // f = fp12_chip.conjugate(ctx, f) + // } + + f +} + +/// Pairing chip for BLS12-381. +/// To avoid issues with mutably borrowing twice (not allowed in Rust), we only store `fp_chip` and construct `g2_chip` in scope when needed for temporary mutable borrows +pub struct PairingChip<'chip, F: BigPrimeField> { + pub fp_chip: &'chip FpChip<'chip, F>, +} + +impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { + pub fn new(fp_chip: &'chip FpChip) -> Self { + Self { fp_chip } + } + + /// Assigns a constant G1 point without checking if it's on the curve. + pub fn load_private_g1_unchecked( + &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)) + } + + /// Assigns a constant G2 point without checking if it's on the curve. + pub fn load_private_g2_unchecked( + &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)) + } + + /// Miller loop for a single pair of (G1, G2). + pub fn miller_loop( + &self, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, + ) -> FqPoint { + let fp2_chip = Fp2Chip::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + miller_loop::(&g2_chip, ctx, P, Q) + } + + /// Multi-pairing Miller loop. + pub fn multi_miller_loop( + &self, + ctx: &mut Context, + pairs: Vec<(&EcPoint>, &EcPoint>)>, + ) -> FqPoint { + let fp2_chip = Fp2Chip::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + multi_miller_loop::(&g2_chip, ctx, pairs) + } + + /// Final exponentiation to complete the pairing. + pub fn final_exp(&self, ctx: &mut Context, f: FqPoint) -> FqPoint { + let fp12_chip = Fp12Chip::::new(self.fp_chip); + fp12_chip.final_exp(ctx, f) + } + + // optimal Ate pairing + pub fn pairing( + &self, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + ) -> FqPoint { + let f0 = self.miller_loop(ctx, P, Q); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + // final_exp implemented in final_exp module + fp12_chip.final_exp(ctx, f0) + } + + /// Multi-pairing Miller loop + final exponentiation. + pub fn batched_pairing( + &self, + ctx: &mut Context, + pairs: &[(&EcPoint>, &EcPoint>)], + ) -> FqPoint { + let mml = self.multi_miller_loop(ctx, pairs.to_vec()); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fe = fp12_chip.final_exp(ctx, mml.clone()); + fe + } + + /* + * Conducts an efficient pairing check e(P, Q) = e(S, T) using only one + * final exponentiation. In particular, this constraints + * (e'(-P, Q)e'(S, T))^x = 1, where e' is the optimal ate pairing without + * the final exponentiation. Reduces number of necessary advice cells by + * ~30%. + */ + pub fn pairing_check( + &self, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + T: &EcPoint>, + S: &EcPoint>, + ) { + let ecc_chip_fp = EccChip::new(self.fp_chip); + let negated_P = ecc_chip_fp.negate(ctx, P); + let gt = self.batched_pairing(ctx, &[(&negated_P, Q), (S, T)]); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + fp12_chip.assert_equal(ctx, gt, fp12_one); + } +} diff --git a/halo2-ecc/src/bls12_381/tests/bls_signature.rs b/halo2-ecc/src/bls12_381/tests/bls_signature.rs new file mode 100644 index 00000000..8448f792 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/bls_signature.rs @@ -0,0 +1,149 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + ops::Neg, +}; + +use super::*; +use crate::{bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy}; +use halo2_base::{gates::RangeChip, utils::BigPrimeField, Context}; +use crate::halo2curves::pairing::group::ff::Field; +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, + range: &RangeChip, + params: BlsSignatureCircuitParams, + signature: G2Affine, + pubkey: G1Affine, + msghash: G2Affine, +) { + 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 assigned_signature = pairing_chip.load_private_g2_unchecked(ctx, signature); + let assigned_pubkey = pairing_chip.load_private_g1_unchecked(ctx, pubkey); + let assigned_msghash = pairing_chip.load_private_g2_unchecked(ctx, msghash); + + let result = bls_signature_chip.is_valid_signature( + ctx, + assigned_signature, + assigned_msghash, + assigned_pubkey, + ); + + // Verify off-circuit + let g1_neg = G1Affine::generator().neg(); + let actual_result = + multi_miller_loop(&[(&g1_neg, &signature.into()), (&pubkey, &msghash.into())]) + .final_exponentiation(); + + // Compare the 2 results + assert_eq!(*result.value(), F::from(actual_result == Gt::identity())) +} + +#[test] +fn test_bls_signature() { + let run_path = "configs/bls12_381/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 msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::from(msghash * sk); + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + bls_signature_test(ctx, range, params, signature, pubkey, msghash); + }) +} + +#[test] +fn test_bls_signature_fail() { + let run_path = "configs/bls12_381/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 msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::random(OsRng); + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + bls_signature_test(ctx, range, params, signature, pubkey, msghash); + }) +} + +#[test] +fn bench_bls_signature() -> Result<(), Box> { + let config_path = "configs/bls12_381/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/bls12_381").unwrap(); + fs::create_dir_all("data").unwrap(); + + let results_path = "results/bls12_381/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 msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::from(msghash * sk); + + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + (signature, pubkey, msghash), + (signature, pubkey, msghash), + |pool, range, (signature, pubkey, msghash)| { + bls_signature_test(pool.main(), range, bench_params, signature, pubkey, msghash); + }, + ); + + 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, + stats.proof_time.time.elapsed(), + stats.proof_size, + stats.verify_time.time.elapsed() + )?; + } + Ok(()) +} diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs new file mode 100644 index 00000000..8a2a566d --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -0,0 +1,109 @@ +use std::fs; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +use super::*; +use crate::fields::{FieldChip, FpStrategy}; +use halo2_base::gates::RangeChip; +use halo2_base::utils::testing::base_test; +use halo2_base::utils::BigPrimeField; +use halo2_base::Context; +use itertools::Itertools; +use rand_core::OsRng; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +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, + batch_size: usize, +} + +fn g2_add_test( + ctx: &mut Context, + range: &RangeChip, + params: CircuitParams, + _points: Vec, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let fp2_chip = Fp2Chip::::new(&fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + + let points = + _points.iter().map(|pt| g2_chip.assign_point_unchecked(ctx, *pt)).collect::>(); + + let acc = g2_chip.sum::(ctx, points); + + let answer = _points.iter().fold(G2Affine::identity(), |a, &b| (a + b).to_affine()); + let x = fp2_chip.get_assigned_value(&acc.x.into()); + let y = fp2_chip.get_assigned_value(&acc.y.into()); + assert_eq!(answer.x, x); + assert_eq!(answer.y, y); +} + +#[test] +fn test_ec_add() { + let path = "configs/bls12_381/ec_add_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let k = params.degree; + let points = (0..params.batch_size).map(|_| G2Affine::random(OsRng)).collect_vec(); + + base_test() + .k(k) + .lookup_bits(params.lookup_bits) + .run(|ctx, range| g2_add_test(ctx, range, params, points)); +} + +#[test] +fn bench_ec_add() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_ec_add.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/bls12_381").unwrap(); + + let results_path = "results/bls12_381/ec_add_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,batch_size,proof_time,proof_size,verify_time")?; + fs::create_dir_all("data").unwrap(); + + 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 mut rng = OsRng; + + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + vec![G2Affine::generator(); bench_params.batch_size], + (0..bench_params.batch_size).map(|_| G2Affine::random(&mut rng)).collect_vec(), + |pool, range, points| { + g2_add_test(pool.main(), range, bench_params, points); + }, + ); + 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.batch_size, + stats.proof_time.time.elapsed(), + stats.proof_size, + stats.verify_time.time.elapsed() + )?; + } + Ok(()) +} diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs new file mode 100644 index 00000000..5cf43b14 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -0,0 +1,22 @@ +#![allow(non_snake_case)] +use super::pairing::PairingChip; +use super::*; +use crate::ecc::EccChip; +use crate::fields::FpStrategy; +use crate::group::Curve; +use halo2_base::utils::testing::base_test; +use rand::rngs::StdRng; +use rand_core::SeedableRng; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +pub mod bls_signature; +#[cfg(feature = "halo2-axiom")] +pub(crate) use crate::halo2_proofs::halo2curves::bls12_381::{ + multi_miller_loop, pairing, Fr as Scalar, Gt, +}; +#[cfg(feature = "halo2-pse")] +pub(crate) use halo2curves::bls12_381::{multi_miller_loop, pairing, Fr as Scalar, Gt}; + +pub mod ec_add; +pub mod pairing; diff --git a/halo2-ecc/src/bls12_381/tests/pairing.rs b/halo2-ecc/src/bls12_381/tests/pairing.rs new file mode 100644 index 00000000..cc9ffe78 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -0,0 +1,173 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, +}; + +use crate::fields::FieldChip; + +use super::*; +use halo2_base::{ + gates::RangeChip, halo2_proofs::arithmetic::Field, utils::BigPrimeField, Context, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct PairingCircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, +} + +fn pairing_test( + ctx: &mut Context, + range: &RangeChip, + params: PairingCircuitParams, + P: G1Affine, + Q: G2Affine, +) { + 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); + // test optimal ate pairing + let f = chip.pairing(ctx, &Q_assigned, &P_assigned); + let actual_f = pairing(&P, &Q); + let fp12_chip = Fp12Chip::new(&fp_chip); + // cannot directly compare f and actual_f because `Gt` has private field `Fq12` + assert_eq!( + format!("Gt({:?})", fp12_chip.get_assigned_value(&f.into())), + format!("{actual_f:?}") + ); +} + +#[test] +fn test_pairing() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let P = G1Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + pairing_test(ctx, range, params, P, Q); + }); +} + +fn pairing_check_test( + ctx: &mut Context, + range: &RangeChip, + params: PairingCircuitParams, + P: G1Affine, + Q: G2Affine, + S: G1Affine, +) { + 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()); + chip.pairing_check(ctx, &Q_assigned, &P_assigned, &T_assigned, &S_assigned); +} + +/* + * Samples a random α,β in Fr and does the pairing check + * e(H_1^α, H_2^β) = e(H_1^(α*β), H_2), where H_1 is the generator for G1 and + * H_2 for G2. + */ +#[test] +fn test_pairing_check() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let alpha = Scalar::random(&mut rng); + let beta = Scalar::random(&mut rng); + let P = G1Affine::from(G1Affine::generator() * alpha); + let Q = G2Affine::from(G2Affine::generator() * beta); + let S = G1Affine::from(G1Affine::generator() * alpha * beta); + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + pairing_check_test(ctx, range, params, P, Q, S); + }) +} + +/* + * Samples a random α,β in Fr and does an incorrect pairing check + * e(H_1^α, H_2^β) = e(H_1^α, H_2), where H_1 is the generator for G1 and + * H_2 for G2. + */ +#[test] +fn test_pairing_check_fail() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let alpha = Scalar::random(&mut rng); + let beta = Scalar::random(&mut rng); + let P = G1Affine::from(G1Affine::generator() * alpha); + let Q = G2Affine::from(G2Affine::generator() * beta); + base_test().k(params.degree).lookup_bits(params.lookup_bits).expect_satisfied(false).run( + |ctx, range| { + pairing_check_test(ctx, range, params, P, Q, P); + }, + ) +} + +#[test] +fn bench_pairing() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_pairing.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/bls12_381").unwrap(); + fs::create_dir_all("data").unwrap(); + + let results_path = "results/bls12_381/pairing_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: PairingCircuitParams = + serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + + let P = G1Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + (P, Q), + (P, Q), + |pool, range, (P, Q)| { + pairing_test(pool.main(), range, bench_params, P, Q); + }, + ); + + 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(()) +} diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index adacce89..bca2635f 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -4,7 +4,7 @@ use std::{ }; use super::*; -use crate::halo2curves::pairing::{group::ff::Field, MillerLoopResult}; +use crate::group::ff::Field; use crate::{ bn254::bls_signature::BlsSignatureChip, fields::FpStrategy, halo2_proofs::halo2curves::bn256::G2Affine, @@ -17,6 +17,11 @@ use halo2_base::{ }; use rand_core::OsRng; +#[cfg(feature = "halo2-axiom")] +use crate::halo2curves::pairing::MillerLoopResult; +#[cfg(feature = "halo2-pse")] +use halo2_base::halo2_proofs::halo2curves::pairing::MillerLoopResult; + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct BlsSignatureCircuitParams { strategy: FpStrategy, diff --git a/halo2-ecc/src/fields/fp12.rs b/halo2-ecc/src/fields/fp12.rs index bdb9f790..8ce8670c 100644 --- a/halo2-ecc/src/fields/fp12.rs +++ b/halo2-ecc/src/fields/fp12.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +#[cfg(feature = "halo2-axiom")] use crate::ff::PrimeField as _; use crate::impl_field_ext_chip_common; @@ -249,3 +250,37 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + #[cfg(feature = "halo2-axiom")] + use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, Fq6}; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::{Fq, Fq12, Fq2, Fq6}; + // This means we store an Fp12 point as `\sum_{i = 0}^6 (a_{i0} + a_{i1} * u) * w^i` + // This is encoded in an FqPoint of degree 12 as `(a_{00}, ..., a_{50}, a_{01}, ..., a_{51})` + impl FieldExtConstructor for Fq12 { + fn new(c: [Fq; 12]) -> Self { + Fq12 { + c0: Fq6 { + c0: Fq2 { c0: c[0], c1: c[6] }, + c1: Fq2 { c0: c[2], c1: c[8] }, + c2: Fq2 { c0: c[4], c1: c[10] }, + }, + c1: Fq6 { + c0: Fq2 { c0: c[1], c1: c[7] }, + c1: Fq2 { c0: c[3], c1: c[9] }, + c2: Fq2 { c0: c[5], c1: c[11] }, + }, + } + } + + fn coeffs(&self) -> Vec { + let x = self; + vec![ + x.c0.c0.c0, x.c1.c0.c0, x.c0.c1.c0, x.c1.c1.c0, x.c0.c2.c0, x.c1.c2.c0, x.c0.c0.c1, + x.c1.c0.c1, x.c0.c1.c1, x.c1.c1.c1, x.c0.c2.c1, x.c1.c2.c1, + ] + } + } +} diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 71c5d446..37dc1140 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use std::marker::PhantomData; +#[cfg(feature = "halo2-axiom")] use crate::ff::PrimeField as _; use crate::impl_field_ext_chip_common; @@ -130,3 +131,21 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + #[cfg(feature = "halo2-axiom")] + use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq2}; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::{Fq, Fq2}; + + impl FieldExtConstructor for Fq2 { + fn new(c: [Fq; 2]) -> Self { + Fq2 { c0: c[0], c1: c[1] } + } + + fn coeffs(&self) -> Vec { + vec![self.c0, self.c1] + } + } +} diff --git a/halo2-ecc/src/lib.rs b/halo2-ecc/src/lib.rs index 5b3f191a..3f86ba86 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -7,6 +7,7 @@ pub mod bigint; pub mod ecc; pub mod fields; +pub mod bls12_381; pub mod bn254; pub mod grumpkin; pub mod secp256k1; diff --git a/rust-toolchain b/rust-toolchain index ee2d639b..1d77724c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2023-08-12 \ No newline at end of file +nightly-2023-11-16