From 12a68f3b2219c405c96f1c0d9c8e6153fe5b6460 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 30 Sep 2023 20:13:54 +0200 Subject: [PATCH 01/51] add bls12 module --- halo2-ecc/src/bls12_381/final_exp.rs | 331 +++++++++++++++++++ halo2-ecc/src/bls12_381/mod.rs | 16 + halo2-ecc/src/bls12_381/notes.md | 4 + halo2-ecc/src/bls12_381/pairing.rs | 457 +++++++++++++++++++++++++++ 4 files changed, 808 insertions(+) create mode 100644 halo2-ecc/src/bls12_381/final_exp.rs create mode 100644 halo2-ecc/src/bls12_381/mod.rs create mode 100644 halo2-ecc/src/bls12_381/notes.md create mode 100644 halo2-ecc/src/bls12_381/pairing.rs 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..575ecab7 --- /dev/null +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -0,0 +1,331 @@ +use super::pairing::fq2_mul_by_nonresidue; +use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +use crate::halo2_proofs::arithmetic::Field; +use crate::{ + ecc::get_naf, + fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip, PrimeField}, +}; +use halo2_base::{gates::GateInstructions, utils::modulus, Context, QuantumCell::Constant}; +use halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; +use itertools::Itertools; +use num_bigint::BigUint; + +const XI_0: i64 = 1; + +impl<'chip, F: PrimeField> 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) + } + + // exp is in little-endian + /// # Assumptions + /// * `a` is nonzero field point + pub fn pow( + &self, + ctx: &mut Context, + a: &>::FieldPoint, + exp: Vec, + ) -> >::FieldPoint { + let mut res = a.clone(); + let mut is_started = false; + let naf = get_naf(exp); + + for &z in naf.iter().rev() { + if is_started { + res = self.mul(ctx, &res, &res); + } + + if z != 0 { + assert!(z == 1 || z == -1); + if is_started { + res = if z == 1 { + self.mul(ctx, &res, a) + } else { + self.divide_unsafe(ctx, &res, a) + }; + } else { + assert_eq!(z, 1); + is_started = true; + } + } + } + res + } + + // 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() + } + + /// # 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) + } + + // 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: FieldVector> = { + 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(ctx, t2.clone(), BLS_X); + 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(ctx, t5.clone(), BLS_X); + let t0 = self.cyclotomic_pow(ctx, t1.clone(), BLS_X); + let t6 = self.cyclotomic_pow(ctx, t0.clone(), BLS_X); + let t6 = self.mul(ctx, &t6, &t4); + let t4 = self.cyclotomic_pow(ctx, t6.clone(), BLS_X); + 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..f2493b74 --- /dev/null +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -0,0 +1,16 @@ +use crate::bigint::ProperCrtUint; +use crate::fields::vector::FieldVector; +use crate::fields::{fp, fp12, fp2}; +use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; + +// pub mod final_exp; +pub mod pairing; + +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, 1>; + +// #[cfg(test)] +// pub(crate) mod tests; diff --git a/halo2-ecc/src/bls12_381/notes.md b/halo2-ecc/src/bls12_381/notes.md new file mode 100644 index 00000000..975ac1bc --- /dev/null +++ b/halo2-ecc/src/bls12_381/notes.md @@ -0,0 +1,4 @@ + + +## Issues: +- [From<[u64; 4]>](https://github.com/axiom-crypto/halo2-lib/blob/980b39bcca5b3327aaef6c8d73577d9381bfa899/halo2-base/src/utils/mod.rs#L35) is not implemented for bls12_381::Fq (halo2-axiom only) diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs new file mode 100644 index 00000000..65a84425 --- /dev/null +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -0,0 +1,457 @@ +#![allow(non_snake_case)] +use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; +use crate::ff::PrimeField; +use crate::fields::vector::FieldVector; +use crate::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; +use crate::{ + ecc::{EcPoint, EccChip}, + fields::fp12::mul_no_carry_w6, + fields::FieldChip, +}; +use halo2_base::utils::BigPrimeField; +use halo2_base::Context; + +const XI_0: i64 = 1; + +// 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, + Q: &EcPoint>, + P: &EcPoint>, +) -> Vec>> { + let (x, y) = (&P.x, &P.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, &Q.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(), &Q.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>, &EcPoint>), + Q: &EcPoint>, +) -> FqPoint { + let line = sparse_line_function_unequal::(fp2_chip, ctx, P, Q); + 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, Q, P); + 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) +// - `pseudo_binary_encoding` is fixed vector consisting of {-1, 0, 1} entries such that `loop_count = sum pseudo_binary_encoding[i] * 2^i` +// 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) +// - Above formula is specific to BN curves +// Assume: +// - Q != O and the order of Q in E(Fp2) is r +// - r is prime, so [i]Q != [j]Q for i != j in Z/r +// - `0 <= loop_count < r` and `loop_count < p` (to avoid [loop_count]Q' = Frob_p(Q')) +// - x^3 + b = 0 has no solution in Fp2, i.e., the y-coordinate of Q cannot be 0. +pub fn miller_loop( + ecc_chip: &EccChip>, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + pseudo_binary_encoding: &[i8], +) -> FqPoint { + todo!() +} + +// 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(); + let fp12_chip = Fp12Chip::::new(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 &(q, p) 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 P 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) + // restrucuture loop to perfrom additiona step for the previous iteration first and then doubling step + for (i, bit) in (0..62).rev().map(|i| (i as usize, ((BLS_X >> i) & 1) == 1)) { + if prev_bit { + for (r, &(q, p)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, (r, p), q); + *r = ecc_chip.add_unequal(ctx, r.clone(), p.clone(), false); + } + } + + prev_bit = bit; + + if !found_one { + found_one = bit; + continue; + } + + f = fp12_chip.mul(ctx, &f, &f); + + for (r, &(q, p)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, r, q); + *r = ecc_chip.double(ctx, r.clone()); + } + } + + f +} + +fn permute_vector(v1: &Vec, indexes: &[usize]) -> Vec { + indexes.iter().map(|i| v1[*i].clone()).collect() +} + +// Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j +// Frob_p( twist(Q) ) = ( (w^2 x)^p, (w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * y^p ) +// Input: +// - Q = (x, y) point in E(Fp2) +// - coeff[1][2], coeff[1][3] as assigned cells: this is an optimization to avoid loading new constants +// Output: +// - (coeff[1][2] * x^p, coeff[1][3] * y^p) point in E(Fp2) +pub fn twisted_frobenius( + ecc_chip: &EccChip>, + ctx: &mut Context, + Q: impl Into>>, + c2: impl Into>, + c3: impl Into>, +) -> EcPoint> { + let Q = Q.into(); + let c2 = c2.into(); + let c3 = c3.into(); + assert_eq!(c2.0.len(), 2); + assert_eq!(c3.0.len(), 2); + + let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); + let frob_y = ecc_chip.field_chip.conjugate(ctx, Q.y); + let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); + let out_y = ecc_chip.field_chip.mul(ctx, c3, frob_y); + EcPoint::new(out_x, out_y) +} + +// Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j +// -Frob_p( twist(Q) ) = ( (w^2 x)^p, -(w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * -y^p ) +// Input: +// - Q = (x, y) point in E(Fp2) +// Output: +// - (coeff[1][2] * x^p, coeff[1][3] * -y^p) point in E(Fp2) +pub fn neg_twisted_frobenius( + ecc_chip: &EccChip>, + ctx: &mut Context, + Q: impl Into>>, + c2: impl Into>, + c3: impl Into>, +) -> EcPoint> { + let Q = Q.into(); + let c2 = c2.into(); + let c3 = c3.into(); + assert_eq!(c2.0.len(), 2); + assert_eq!(c3.0.len(), 2); + + let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); + let neg_frob_y = ecc_chip.field_chip.neg_conjugate(ctx, Q.y); + let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); + let out_y = ecc_chip.field_chip.mul(ctx, c3, neg_frob_y); + EcPoint::new(out_x, out_y) +} + +// 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 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 } + } + + 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)) + } + + 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)) + } + + pub fn miller_loop( + &self, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + ) -> FqPoint { + let fp2_chip = Fp2Chip::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + // miller_loop_BN::( + // &g2_chip, + // ctx, + // Q, + // P, + // &SIX_U_PLUS_2_NAF, // pseudo binary encoding for BN254 + // ) + unimplemented!() + } + + 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); + let f = multi_miller_loop::(&g2_chip, ctx, pairs); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + + f + } + + 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, Q, P); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + // final_exp implemented in final_exp module + fp12_chip.final_exp(ctx, f0) + } + + /* + * 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 mml = self.multi_miller_loop(ctx, vec![(&negated_P, Q), (S, T)]); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fe = fp12_chip.final_exp(ctx, mml); + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + fp12_chip.assert_equal(ctx, fe, fp12_one); + } +} From c23d2645b2f0c97b27eb73ac926da2215c359b6a Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 30 Sep 2023 21:21:29 +0200 Subject: [PATCH 02/51] make compile --- Cargo.toml | 9 ++++ halo2-base/src/utils/mod.rs | 62 +++++++++++++++++++++++----- halo2-ecc/src/bls12_381/final_exp.rs | 16 ++++--- halo2-ecc/src/bls12_381/mod.rs | 6 +-- halo2-ecc/src/bls12_381/pairing.rs | 42 ++----------------- halo2-ecc/src/fields/fp12.rs | 31 ++++++++++++++ halo2-ecc/src/fields/fp2.rs | 14 +++++++ halo2-ecc/src/lib.rs | 1 + 8 files changed, 121 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2d3ab72..09c03f33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,12 @@ debug = true [patch."https://github.com/axiom-crypto/halo2-lib.git"] halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } + +[patch."https://github.com/privacy-scaling-explorations/halo2curves"] +# halo2curves = { git = "https://github.com/timoftime/halo2curves", rev = "b682183" } +halo2curves = { path = "../halo2curves" } + + +[patch."https://github.com/axiom-crypto/halo2curves"] +# halo2curves = { git = "https://github.com/timoftime/halo2curves", rev = "b682183" } +halo2curves = { path = "../halo2curves" } diff --git a/halo2-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index 2aaa5166..6af4c935 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -30,19 +30,61 @@ 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 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) + } + } +} + +// mod bls12_381 { +// use super::BigPrimeField; + +// impl BigPrimeField for crate::halo2_proofs::halo2curves::bls12_381::Fq { +// fn from_u64_digits(val: &[u64]) -> Self { +// todo!() +// } +// } +// } + /// Helper trait to represent a field element that can be converted into [u64] limbs. /// /// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the integer representation of the field element into multiple [u64] values e.g. `limbs`. diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index 575ecab7..b27d0439 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,18 +1,16 @@ -use super::pairing::fq2_mul_by_nonresidue; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use crate::halo2_proofs::arithmetic::Field; +use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ ecc::get_naf, - fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip, PrimeField}, + 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 halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; -use itertools::Itertools; use num_bigint::BigUint; const XI_0: i64 = 1; -impl<'chip, F: PrimeField> Fp12Chip<'chip, F> { +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( @@ -30,7 +28,7 @@ impl<'chip, F: PrimeField> Fp12Chip<'chip, F> { 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]); + 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 @@ -172,8 +170,8 @@ impl<'chip, F: PrimeField> Fp12Chip<'chip, F> { // 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())); + 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; diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index f2493b74..c4c22d5c 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -3,7 +3,7 @@ use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; -// pub mod final_exp; +pub mod final_exp; pub mod pairing; pub type FpChip<'range, F> = fp::FpChip<'range, F, Fq>; @@ -12,5 +12,5 @@ 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, 1>; -// #[cfg(test)] -// pub(crate) mod tests; +#[cfg(test)] +pub(crate) mod tests; diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index 65a84425..5ed1f603 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -1,6 +1,5 @@ #![allow(non_snake_case)] use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; -use crate::ff::PrimeField; use crate::fields::vector::FieldVector; use crate::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; use crate::{ @@ -189,29 +188,11 @@ pub fn fp12_multiply_with_line_equal( 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) -// - `pseudo_binary_encoding` is fixed vector consisting of {-1, 0, 1} entries such that `loop_count = sum pseudo_binary_encoding[i] * 2^i` -// 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) -// - Above formula is specific to BN curves -// Assume: -// - Q != O and the order of Q in E(Fp2) is r -// - r is prime, so [i]Q != [j]Q for i != j in Z/r -// - `0 <= loop_count < r` and `loop_count < p` (to avoid [loop_count]Q' = Frob_p(Q')) -// - x^3 + b = 0 has no solution in Fp2, i.e., the y-coordinate of Q cannot be 0. pub fn miller_loop( ecc_chip: &EccChip>, ctx: &mut Context, Q: &EcPoint>, P: &EcPoint>, - pseudo_binary_encoding: &[i8], ) -> FqPoint { todo!() } @@ -224,7 +205,6 @@ pub fn multi_miller_loop( pairs: Vec<(&EcPoint>, &EcPoint>)>, ) -> FqPoint { let fp_chip = ecc_chip.field_chip.fp_chip(); - let fp12_chip = Fp12Chip::::new(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 @@ -270,7 +250,7 @@ pub fn multi_miller_loop( // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) // restrucuture loop to perfrom additiona step for the previous iteration first and then doubling step - for (i, bit) in (0..62).rev().map(|i| (i as usize, ((BLS_X >> i) & 1) == 1)) { + for bit in (0..62).rev().map(|i| ((BLS_X >> i) & 1) == 1) { if prev_bit { for (r, &(q, p)) in r.iter_mut().zip(pairs.iter()) { f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, (r, p), q); @@ -287,7 +267,7 @@ pub fn multi_miller_loop( f = fp12_chip.mul(ctx, &f, &f); - for (r, &(q, p)) in r.iter_mut().zip(pairs.iter()) { + for (r, &(q, _p)) in r.iter_mut().zip(pairs.iter()) { f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, r, q); *r = ecc_chip.double(ctx, r.clone()); } @@ -296,10 +276,6 @@ pub fn multi_miller_loop( f } -fn permute_vector(v1: &Vec, indexes: &[usize]) -> Vec { - indexes.iter().map(|i| v1[*i].clone()).collect() -} - // Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j // Frob_p( twist(Q) ) = ( (w^2 x)^p, (w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * y^p ) // Input: @@ -390,14 +366,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { ) -> FqPoint { let fp2_chip = Fp2Chip::::new(self.fp_chip); let g2_chip = EccChip::new(&fp2_chip); - // miller_loop_BN::( - // &g2_chip, - // ctx, - // Q, - // P, - // &SIX_U_PLUS_2_NAF, // pseudo binary encoding for BN254 - // ) - unimplemented!() + miller_loop::(&g2_chip, ctx, Q, P) } pub fn multi_miller_loop( @@ -407,10 +376,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { ) -> FqPoint { let fp2_chip = Fp2Chip::::new(self.fp_chip); let g2_chip = EccChip::new(&fp2_chip); - let f = multi_miller_loop::(&g2_chip, ctx, pairs); - let fp12_chip = Fp12Chip::::new(self.fp_chip); - - f + multi_miller_loop::(&g2_chip, ctx, pairs) } pub fn final_exp(&self, ctx: &mut Context, f: FqPoint) -> FqPoint { diff --git a/halo2-ecc/src/fields/fp12.rs b/halo2-ecc/src/fields/fp12.rs index bdb9f790..64b6a200 100644 --- a/halo2-ecc/src/fields/fp12.rs +++ b/halo2-ecc/src/fields/fp12.rs @@ -249,3 +249,34 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + use crate::halo2_proofs::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..8068e2f5 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -130,3 +130,17 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + use crate::halo2_proofs::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..d87ce7fc 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -8,6 +8,7 @@ pub mod ecc; pub mod fields; pub mod bn254; +pub mod bls12_381; pub mod grumpkin; pub mod secp256k1; From 14077b45455b670dab072962acf46fef669550b7 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 30 Sep 2023 22:04:29 +0200 Subject: [PATCH 03/51] add tests; they fail :( --- halo2-base/src/utils/mod.rs | 32 +++- .../configs/bls12_381/pairing_circuit.config | 1 + halo2-ecc/src/bigint/carry_mod.rs | 12 +- .../src/bigint/check_carry_mod_to_zero.rs | 14 +- halo2-ecc/src/bls12_381/tests/mod.rs | 21 +++ halo2-ecc/src/bls12_381/tests/pairing.rs | 173 ++++++++++++++++++ 6 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 halo2-ecc/configs/bls12_381/pairing_circuit.config create mode 100644 halo2-ecc/src/bls12_381/tests/mod.rs create mode 100644 halo2-ecc/src/bls12_381/tests/pairing.rs diff --git a/halo2-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index 6af4c935..f0396980 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -52,6 +52,29 @@ mod bn256 { } } +#[cfg(feature = "halo2-axiom")] +mod secp256k1 { + use crate::halo2_proofs::halo2curves::secp256k1::{Fq, Fp}; + + 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}; @@ -75,15 +98,6 @@ mod bls12_381 { } } -// mod bls12_381 { -// use super::BigPrimeField; - -// impl BigPrimeField for crate::halo2_proofs::halo2curves::bls12_381::Fq { -// fn from_u64_digits(val: &[u64]) -> Self { -// todo!() -// } -// } -// } /// Helper trait to represent a field element that can be converted into [u64] limbs. /// 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..070e8642 --- /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":120,"num_limbs":4} diff --git a/halo2-ecc/src/bigint/carry_mod.rs b/halo2-ecc/src/bigint/carry_mod.rs index a9667d79..c12a0fde 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -44,7 +44,8 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + // FIXME: hotfix for BLS12 support + // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // in order for CRT method to work, we need `abs(out + modulus * quotient - a) < 2^{trunc_len - 1} * native_modulus::` // this is ensured if `0 <= out < 2^{n*k}` and @@ -56,7 +57,8 @@ 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); + // FIXME: hotfix for BLS12 support + let quot_last_limb_bits = 0; //quot_max_bits - n * (k - 1); 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) @@ -69,7 +71,8 @@ pub fn crt( let (quot_val, out_val) = a.value.div_mod_floor(modulus); debug_assert!(out_val < (BigInt::one() << (n * k))); - debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + // FIXME: hotfix for BLS12 support + // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); // decompose_bigint just throws away signed limbs in index >= k let out_vec = decompose_bigint::(&out_val, k, n); @@ -144,7 +147,8 @@ 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 }; + // FIXME: hotfix for BLS12 support + let limb_bits = if q_index == k - 1 { /* quot_last_limb_bits */ n } 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..7a2e634d 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -29,12 +29,14 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + // FIXME: hotfix for BLS12 support + // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // 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); + // FIXME: hotfix for BLS12 support + let quot_last_limb_bits = 0; // quot_max_bits - n * (k - 1); // these are witness vectors: // we need to find `quot_vec` as a proper BigInt with k limbs @@ -44,8 +46,9 @@ pub fn crt( let (quot_val, _out_val) = a.value.div_mod_floor(modulus); // only perform safety checks in debug mode - debug_assert_eq!(_out_val, BigInt::zero()); - debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + // FIXME: hotfix for BLS12 support + // debug_assert_eq!(_out_val, BigInt::zero()); + // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); let quot_vec = decompose_bigint::("_val, k, n); @@ -90,7 +93,8 @@ 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 }; + // FIXME: hotfix for BLS12 support + let limb_bits = if q_index == k - 1 { n /* quot_last_limb_bits */ } 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/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs new file mode 100644 index 00000000..71ec5cdd --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -0,0 +1,21 @@ +#![allow(non_snake_case)] +use super::pairing::PairingChip; +use super::*; +use crate::ecc::EccChip; +use crate::group::Curve; +use crate::{ + fields::FpStrategy, + halo2_proofs::halo2curves::bls12_381::{pairing, Fr, G1Affine}, +}; +use halo2_base::utils::fe_to_biguint; +use halo2_base::{ + gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, + halo2_proofs::halo2curves::bls12_381::G1, + utils::testing::base_test, +}; +use rand::rngs::StdRng; +use rand_core::SeedableRng; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +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..fc9f2082 --- /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, halo2curves::bls12_381::G2Affine}, 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 = Fr::random(&mut rng); + let beta = Fr::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 = Fr::random(&mut rng); + let beta = Fr::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(()) +} From 29f3e82407a3021d9e57ea2539b3159e105114c0 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 1 Oct 2023 17:23:20 +0200 Subject: [PATCH 04/51] tests WIP --- halo2-base/src/utils/mod.rs | 19 ++- .../configs/bls12_381/bench_ec_add.config | 5 + .../configs/bls12_381/bench_pairing.config | 9 ++ .../configs/bls12_381/ec_add_circuit.config | 1 + halo2-ecc/results/bls12_381/pairing_bench.csv | 1 + halo2-ecc/src/bls12_381/notes.md | 4 - halo2-ecc/src/bls12_381/tests/ec_add.rs | 110 ++++++++++++++++ halo2-ecc/src/bls12_381/tests/mod.rs | 11 +- halo2-ecc/src/bls12_381/tests/pairing.rs | 122 +++++++++--------- 9 files changed, 200 insertions(+), 82 deletions(-) create mode 100644 halo2-ecc/configs/bls12_381/bench_ec_add.config create mode 100644 halo2-ecc/configs/bls12_381/bench_pairing.config create mode 100644 halo2-ecc/configs/bls12_381/ec_add_circuit.config create mode 100644 halo2-ecc/results/bls12_381/pairing_bench.csv delete mode 100644 halo2-ecc/src/bls12_381/notes.md create mode 100644 halo2-ecc/src/bls12_381/tests/ec_add.rs diff --git a/halo2-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index f0396980..fb1d7235 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -54,7 +54,7 @@ mod bn256 { #[cfg(feature = "halo2-axiom")] mod secp256k1 { - use crate::halo2_proofs::halo2curves::secp256k1::{Fq, Fp}; + use crate::halo2_proofs::halo2curves::secp256k1::{Fp, Fq}; impl super::BigPrimeField for Fp { #[inline(always)] @@ -79,14 +79,14 @@ mod secp256k1 { 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 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)] @@ -98,7 +98,6 @@ mod bls12_381 { } } - /// Helper trait to represent a field element that can be converted into [u64] limbs. /// /// Note: Since the number of bits necessary to represent a field element is larger than the number of bits in a u64, we decompose the integer representation of the field element into multiple [u64] values e.g. `limbs`. 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/ec_add_circuit.config b/halo2-ecc/configs/bls12_381/ec_add_circuit.config new file mode 100644 index 00000000..c17d44b9 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/ec_add_circuit.config @@ -0,0 +1 @@ +{"strategy":"Simple","degree":17,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"batch_size":100} diff --git a/halo2-ecc/results/bls12_381/pairing_bench.csv b/halo2-ecc/results/bls12_381/pairing_bench.csv new file mode 100644 index 00000000..04656bac --- /dev/null +++ b/halo2-ecc/results/bls12_381/pairing_bench.csv @@ -0,0 +1 @@ +degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time diff --git a/halo2-ecc/src/bls12_381/notes.md b/halo2-ecc/src/bls12_381/notes.md deleted file mode 100644 index 975ac1bc..00000000 --- a/halo2-ecc/src/bls12_381/notes.md +++ /dev/null @@ -1,4 +0,0 @@ - - -## Issues: -- [From<[u64; 4]>](https://github.com/axiom-crypto/halo2-lib/blob/980b39bcca5b3327aaef6c8d73577d9381bfa899/halo2-base/src/utils/mod.rs#L35) is not implemented for bls12_381::Fq (halo2-axiom only) 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..816956c7 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -0,0 +1,110 @@ +use std::fs; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +use super::*; +use crate::fields::{FieldChip, FpStrategy}; +use crate::halo2_proofs::halo2curves::bls12_381::G2Affine; +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 index 71ec5cdd..14315f7d 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -5,17 +5,14 @@ use crate::ecc::EccChip; use crate::group::Curve; use crate::{ fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::{pairing, Fr, G1Affine}, -}; -use halo2_base::utils::fe_to_biguint; -use halo2_base::{ - gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, - halo2_proofs::halo2curves::bls12_381::G1, - utils::testing::base_test, + halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar, G1Affine}, + halo2_proofs::halo2curves::bn256::Fr, }; +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 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 index fc9f2082..a4eb45d5 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -45,20 +45,20 @@ fn pairing_test( ); } -#[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); - }); -} +// #[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, @@ -90,8 +90,8 @@ fn test_pairing_check() { ) .unwrap(); let mut rng = StdRng::seed_from_u64(0); - let alpha = Fr::random(&mut rng); - let beta = Fr::random(&mut rng); + 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); @@ -113,8 +113,8 @@ fn test_pairing_check_fail() { ) .unwrap(); let mut rng = StdRng::seed_from_u64(0); - let alpha = Fr::random(&mut rng); - let beta = Fr::random(&mut rng); + 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( @@ -124,50 +124,50 @@ fn test_pairing_check_fail() { ) } -#[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(); +// #[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 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 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); - }, - ); +// 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(()) -} +// 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(()) +// } From 701f8f45d9d23fd53a38de0a5e0019be219bb393 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 1 Oct 2023 18:14:26 +0200 Subject: [PATCH 05/51] fix miller look args names --- halo2-ecc/src/bls12_381/pairing.rs | 89 ++++++++++++++++-------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index 5ed1f603..bd2ae790 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -62,10 +62,10 @@ pub fn sparse_line_function_unequal( pub fn sparse_line_function_equal( fp2_chip: &Fp2Chip, ctx: &mut Context, - Q: &EcPoint>, - P: &EcPoint>, + P: &EcPoint>, + Q: &EcPoint>, ) -> Vec>> { - let (x, y) = (&P.x, &P.y); + let (x, y) = (&Q.x, &Q.y); assert_eq!(x.0.len(), 2); assert_eq!(y.0.len(), 2); @@ -77,10 +77,10 @@ pub fn sparse_line_function_equal( 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, &Q.x); + 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(), &Q.y); + 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 @@ -181,19 +181,24 @@ pub fn fp12_multiply_with_line_equal( fp2_chip: &Fp2Chip, ctx: &mut Context, g: &FqPoint, - P: &EcPoint>, - Q: &EcPoint>, + P: &EcPoint>, + Q: &EcPoint>, ) -> FqPoint { - let line = sparse_line_function_equal::(fp2_chip, ctx, Q, P); + let line = sparse_line_function_equal::(fp2_chip, ctx, P, Q); sparse_fp12_multiply::(fp2_chip, ctx, g, &line) } pub fn miller_loop( ecc_chip: &EccChip>, ctx: &mut Context, - Q: &EcPoint>, P: &EcPoint>, + Q: &EcPoint>, ) -> FqPoint { + let fp_chip: &crate::fields::fp::FpChip<'_, F, Fq> = ecc_chip.field_chip.fp_chip(); + + // let sparse_f = sparse_line_function_equal::(ecc_chip.field_chip(), ctx, Q, P); + + todo!() } @@ -204,7 +209,7 @@ pub fn multi_miller_loop( ctx: &mut Context, pairs: Vec<(&EcPoint>, &EcPoint>)>, ) -> FqPoint { - let fp_chip = ecc_chip.field_chip.fp_chip(); + let fp_chip: &crate::fields::fp::FpChip<'_, F, Fq> = 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 @@ -233,7 +238,7 @@ pub fn multi_miller_loop( }; // process second (Q,P) pair - for &(q, p) in pairs.iter().skip(1) { + for &(p, q) in pairs.iter().skip(1) { f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, p, q); } @@ -249,7 +254,7 @@ pub fn multi_miller_loop( } // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) - // restrucuture loop to perfrom additiona step for the previous iteration first and then doubling step + // 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, &(q, p)) in r.iter_mut().zip(pairs.iter()) { @@ -267,8 +272,8 @@ pub fn multi_miller_loop( f = fp12_chip.mul(ctx, &f, &f); - for (r, &(q, _p)) in r.iter_mut().zip(pairs.iter()) { - f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, r, q); + 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()); } } @@ -303,31 +308,31 @@ pub fn twisted_frobenius( EcPoint::new(out_x, out_y) } -// Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j -// -Frob_p( twist(Q) ) = ( (w^2 x)^p, -(w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * -y^p ) -// Input: -// - Q = (x, y) point in E(Fp2) -// Output: -// - (coeff[1][2] * x^p, coeff[1][3] * -y^p) point in E(Fp2) -pub fn neg_twisted_frobenius( - ecc_chip: &EccChip>, - ctx: &mut Context, - Q: impl Into>>, - c2: impl Into>, - c3: impl Into>, -) -> EcPoint> { - let Q = Q.into(); - let c2 = c2.into(); - let c3 = c3.into(); - assert_eq!(c2.0.len(), 2); - assert_eq!(c3.0.len(), 2); - - let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); - let neg_frob_y = ecc_chip.field_chip.neg_conjugate(ctx, Q.y); - let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); - let out_y = ecc_chip.field_chip.mul(ctx, c3, neg_frob_y); - EcPoint::new(out_x, out_y) -} +// // Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j +// // -Frob_p( twist(Q) ) = ( (w^2 x)^p, -(w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * -y^p ) +// // Input: +// // - Q = (x, y) point in E(Fp2) +// // Output: +// // - (coeff[1][2] * x^p, coeff[1][3] * -y^p) point in E(Fp2) +// pub fn neg_twisted_frobenius( +// ecc_chip: &EccChip>, +// ctx: &mut Context, +// Q: impl Into>>, +// c2: impl Into>, +// c3: impl Into>, +// ) -> EcPoint> { +// let Q = Q.into(); +// let c2 = c2.into(); +// let c3 = c3.into(); +// assert_eq!(c2.0.len(), 2); +// assert_eq!(c3.0.len(), 2); + +// let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); +// let neg_frob_y = ecc_chip.field_chip.neg_conjugate(ctx, Q.y); +// let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); +// let out_y = ecc_chip.field_chip.mul(ctx, c3, neg_frob_y); +// EcPoint::new(out_x, out_y) +// } // 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 PairingChip<'chip, F: BigPrimeField> { @@ -361,12 +366,12 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { pub fn miller_loop( &self, ctx: &mut Context, - Q: &EcPoint>, 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, Q, P) + miller_loop::(&g2_chip, ctx, P, Q) } pub fn multi_miller_loop( @@ -391,7 +396,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { Q: &EcPoint>, P: &EcPoint>, ) -> FqPoint { - let f0 = self.miller_loop(ctx, Q, P); + 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) From 83553125deb8349385ed002138e5f7d4014453d5 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 1 Oct 2023 21:15:46 +0200 Subject: [PATCH 06/51] impl (single) miller_loop --- halo2-ecc/src/bls12_381/pairing.rs | 128 +++++++++++------------ halo2-ecc/src/bls12_381/tests/ec_add.rs | 82 +++++++-------- halo2-ecc/src/bls12_381/tests/mod.rs | 1 - halo2-ecc/src/bls12_381/tests/pairing.rs | 114 ++++++++++---------- 4 files changed, 162 insertions(+), 163 deletions(-) diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index bd2ae790..cb46065c 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -7,6 +7,7 @@ use crate::{ fields::fp12::mul_no_carry_w6, fields::FieldChip, }; +use halo2_base::halo2_proofs::halo2curves::bls12_381::BLS_X_IS_NEGATIVE; use halo2_base::utils::BigPrimeField; use halo2_base::Context; @@ -164,10 +165,10 @@ pub fn fp12_multiply_with_line_unequal( fp2_chip: &Fp2Chip, ctx: &mut Context, g: &FqPoint, - P: (&EcPoint>, &EcPoint>), - Q: &EcPoint>, + P: &EcPoint>, + Q: (&EcPoint>, &EcPoint>), ) -> FqPoint { - let line = sparse_line_function_unequal::(fp2_chip, ctx, P, Q); + let line = sparse_line_function_unequal::(fp2_chip, ctx, Q, P); sparse_fp12_multiply::(fp2_chip, ctx, g, &line) } @@ -194,12 +195,64 @@ pub fn miller_loop( P: &EcPoint>, Q: &EcPoint>, ) -> FqPoint { - let fp_chip: &crate::fields::fp::FpChip<'_, F, Fq> = ecc_chip.field_chip.fp_chip(); + 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); - // let sparse_f = sparse_line_function_equal::(ecc_chip.field_chip(), ctx, Q, P); + 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) + } - todo!() + f } // let pairs = [(a_i, b_i)], a_i in G_1, b_i in G_2 @@ -209,7 +262,7 @@ pub fn multi_miller_loop( ctx: &mut Context, pairs: Vec<(&EcPoint>, &EcPoint>)>, ) -> FqPoint { - let fp_chip: &crate::fields::fp::FpChip<'_, F, Fq> = ecc_chip.field_chip.fp_chip(); + 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 @@ -248,7 +301,7 @@ pub fn multi_miller_loop( let mut found_one = true; let mut prev_bit = true; - // double P as in the first part of Miller loop + // double Q as in the first part of Miller loop for r in r.iter_mut() { *r = ecc_chip.double(ctx, r.clone()); } @@ -257,9 +310,9 @@ pub fn multi_miller_loop( // 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, &(q, p)) in r.iter_mut().zip(pairs.iter()) { - f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, (r, p), q); - *r = ecc_chip.add_unequal(ctx, r.clone(), p.clone(), false); + 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); } } @@ -281,59 +334,6 @@ pub fn multi_miller_loop( f } -// Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j -// Frob_p( twist(Q) ) = ( (w^2 x)^p, (w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * y^p ) -// Input: -// - Q = (x, y) point in E(Fp2) -// - coeff[1][2], coeff[1][3] as assigned cells: this is an optimization to avoid loading new constants -// Output: -// - (coeff[1][2] * x^p, coeff[1][3] * y^p) point in E(Fp2) -pub fn twisted_frobenius( - ecc_chip: &EccChip>, - ctx: &mut Context, - Q: impl Into>>, - c2: impl Into>, - c3: impl Into>, -) -> EcPoint> { - let Q = Q.into(); - let c2 = c2.into(); - let c3 = c3.into(); - assert_eq!(c2.0.len(), 2); - assert_eq!(c3.0.len(), 2); - - let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); - let frob_y = ecc_chip.field_chip.conjugate(ctx, Q.y); - let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); - let out_y = ecc_chip.field_chip.mul(ctx, c3, frob_y); - EcPoint::new(out_x, out_y) -} - -// // Frobenius coefficient coeff[1][j] = ((9+u)^{(p-1)/6})^j -// // -Frob_p( twist(Q) ) = ( (w^2 x)^p, -(w^3 y)^p ) = twist( coeff[1][2] * x^p, coeff[1][3] * -y^p ) -// // Input: -// // - Q = (x, y) point in E(Fp2) -// // Output: -// // - (coeff[1][2] * x^p, coeff[1][3] * -y^p) point in E(Fp2) -// pub fn neg_twisted_frobenius( -// ecc_chip: &EccChip>, -// ctx: &mut Context, -// Q: impl Into>>, -// c2: impl Into>, -// c3: impl Into>, -// ) -> EcPoint> { -// let Q = Q.into(); -// let c2 = c2.into(); -// let c3 = c3.into(); -// assert_eq!(c2.0.len(), 2); -// assert_eq!(c3.0.len(), 2); - -// let frob_x = ecc_chip.field_chip.conjugate(ctx, Q.x); -// let neg_frob_y = ecc_chip.field_chip.neg_conjugate(ctx, Q.y); -// let out_x = ecc_chip.field_chip.mul(ctx, c2, frob_x); -// let out_y = ecc_chip.field_chip.mul(ctx, c3, neg_frob_y); -// EcPoint::new(out_x, out_y) -// } - // 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 PairingChip<'chip, F: BigPrimeField> { pub fp_chip: &'chip FpChip<'chip, F>, diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs index 816956c7..9d0b3692 100644 --- a/halo2-ecc/src/bls12_381/tests/ec_add.rs +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -64,47 +64,47 @@ fn test_ec_add() { .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(); +#[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 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 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(()) -// } + 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 index 14315f7d..53c966ae 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -6,7 +6,6 @@ use crate::group::Curve; use crate::{ fields::FpStrategy, halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar, G1Affine}, - halo2_proofs::halo2curves::bn256::Fr, }; use halo2_base::utils::testing::base_test; use rand::rngs::StdRng; diff --git a/halo2-ecc/src/bls12_381/tests/pairing.rs b/halo2-ecc/src/bls12_381/tests/pairing.rs index a4eb45d5..ee164dba 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -45,20 +45,20 @@ fn pairing_test( ); } -// #[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); -// }); -// } +#[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, @@ -124,50 +124,50 @@ fn test_pairing_check_fail() { ) } -// #[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(); +#[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 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 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); -// }, -// ); + 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(()) -// } + 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(()) +} From 4cd431a0e69bdbaab059a7aa02e90cbe9cc52017 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 1 Oct 2023 21:19:53 +0200 Subject: [PATCH 07/51] cargo fix+fmt --- halo2-ecc/src/bigint/carry_mod.rs | 11 ++++++++--- halo2-ecc/src/bigint/check_carry_mod_to_zero.rs | 10 +++++++--- halo2-ecc/src/bls12_381/tests/pairing.rs | 5 ++++- halo2-ecc/src/lib.rs | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/halo2-ecc/src/bigint/carry_mod.rs b/halo2-ecc/src/bigint/carry_mod.rs index c12a0fde..0d8ddabd 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -8,7 +8,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::{One, Signed}; +use num_traits::One; use super::{check_carry_to_zero, CRTInteger, OverflowInteger, ProperCrtUint, ProperUint}; @@ -58,7 +58,7 @@ pub fn crt( // 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)} // FIXME: hotfix for BLS12 support - let quot_last_limb_bits = 0; //quot_max_bits - n * (k - 1); + let _quot_last_limb_bits = 0; //quot_max_bits - n * (k - 1); 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) @@ -148,7 +148,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() { // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { /* quot_last_limb_bits */ n } else { n }; + let limb_bits = if q_index == k - 1 { + /* quot_last_limb_bits */ + n + } 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 7a2e634d..9659e6d3 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -7,7 +7,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::{One, Signed, Zero}; +use num_traits::One; use std::{cmp::max, iter}; // same as carry_mod::crt but `out = 0` so no need to range check @@ -36,7 +36,7 @@ pub fn crt( let quot_max_bits = trunc_len - 1 + (F::NUM_BITS as usize) - 1 - (modulus.bits() as usize); assert!(quot_max_bits < trunc_len); // FIXME: hotfix for BLS12 support - let quot_last_limb_bits = 0; // quot_max_bits - n * (k - 1); + let _quot_last_limb_bits = 0; // quot_max_bits - n * (k - 1); // these are witness vectors: // we need to find `quot_vec` as a proper BigInt with k limbs @@ -94,7 +94,11 @@ 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() { // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { n /* quot_last_limb_bits */ } else { n }; + let limb_bits = if q_index == k - 1 { + n /* quot_last_limb_bits */ + } 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/tests/pairing.rs b/halo2-ecc/src/bls12_381/tests/pairing.rs index ee164dba..b46e6037 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -7,7 +7,10 @@ use crate::fields::FieldChip; use super::*; use halo2_base::{ - gates::RangeChip, halo2_proofs::{arithmetic::Field, halo2curves::bls12_381::G2Affine}, utils::BigPrimeField, Context, + gates::RangeChip, + halo2_proofs::{arithmetic::Field, halo2curves::bls12_381::G2Affine}, + utils::BigPrimeField, + Context, }; use serde::{Deserialize, Serialize}; diff --git a/halo2-ecc/src/lib.rs b/halo2-ecc/src/lib.rs index d87ce7fc..3f86ba86 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -7,8 +7,8 @@ pub mod bigint; pub mod ecc; pub mod fields; -pub mod bn254; pub mod bls12_381; +pub mod bn254; pub mod grumpkin; pub mod secp256k1; From 54d0d3e56ca808c2b2490160b24aad37677a7785 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 1 Oct 2023 22:06:47 +0200 Subject: [PATCH 08/51] add bls signature chip --- halo2-ecc/src/bls12_381/bls_signature.rs | 97 ++++++++++++++++++++++++ halo2-ecc/src/bls12_381/mod.rs | 1 + 2 files changed, 98 insertions(+) create mode 100644 halo2-ecc/src/bls12_381/bls_signature.rs 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..230e404f --- /dev/null +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -0,0 +1,97 @@ +use std::ops::Neg; + +use super::pairing::PairingChip; +use super::{Fp12Chip, FpChip}; +use crate::bigint::ProperCrtUint; +use crate::ecc::{EcPoint, EccChip}; +use crate::fields::vector::FieldVector; +use crate::fields::FieldChip; +use crate::halo2_proofs::halo2curves::bls12_381::{G1Affine, G2Affine}; +use halo2_base::halo2_proofs::halo2curves::bls12_381::Fq12; +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 } + } + + // 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, + ) { + 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, hash_m_assigned, pubkey_assigned); + } + + 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) + } + + 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> { + let mml = self + .pairing_chip + .multi_miller_loop(ctx, vec![(&g1_neg, &signature), (&pubkey, &msghash)]); + + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fe = fp12_chip.final_exp(ctx, mml); + + assert_eq!(fe.0.len(), 12); + + fe + } +} diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index c4c22d5c..aa6e0910 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -3,6 +3,7 @@ use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; +pub mod bls_signature; pub mod final_exp; pub mod pairing; From 02fb814c079847033df289508ddbe9ea3f3e0ef2 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 2 Oct 2023 12:41:48 +0200 Subject: [PATCH 09/51] add tests --- .../bls12_381/bench_bls_signature.config | 8 + .../bls12_381/bls_signature_circuit.config | 1 + .../src/bls12_381/tests/bls_signature.rs | 158 ++++++++++++++++++ halo2-ecc/src/bls12_381/tests/mod.rs | 1 + 4 files changed, 168 insertions(+) create mode 100644 halo2-ecc/configs/bls12_381/bench_bls_signature.config create mode 100644 halo2-ecc/configs/bls12_381/bls_signature_circuit.config create mode 100644 halo2-ecc/src/bls12_381/tests/bls_signature.rs 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/bls_signature_circuit.config b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config new file mode 100644 index 00000000..09d9a232 --- /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":120,"num_limbs":4,"num_aggregation":30} 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..baef5e9b --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/bls_signature.rs @@ -0,0 +1,158 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + ops::Neg, +}; + +use super::*; +use crate::{ + bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy, + halo2_proofs::halo2curves::bls12_381::G2Affine, +}; +use halo2_base::{ + gates::RangeChip, + halo2_proofs::halo2curves::bls12_381::{multi_miller_loop, Gt}, + utils::BigPrimeField, + Context, +}; +extern crate pairing; +use 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/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index 53c966ae..ce937258 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -13,5 +13,6 @@ use rand_core::SeedableRng; use serde::{Deserialize, Serialize}; use std::io::Write; +pub mod bls_signature; pub mod ec_add; pub mod pairing; From 4c107de023bb77c71eb8ac0fded595a10e9877ec Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 2 Oct 2023 13:07:29 +0200 Subject: [PATCH 10/51] add comments --- Cargo.toml | 7 ++----- halo2-ecc/src/bls12_381/final_exp.rs | 3 +-- halo2-ecc/src/bls12_381/mod.rs | 4 +++- halo2-ecc/src/bls12_381/pairing.rs | 27 +++++++++++++++++++++++---- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09c03f33..01b1acb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,10 +41,7 @@ halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } [patch."https://github.com/privacy-scaling-explorations/halo2curves"] -# halo2curves = { git = "https://github.com/timoftime/halo2curves", rev = "b682183" } -halo2curves = { path = "../halo2curves" } - +halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } [patch."https://github.com/axiom-crypto/halo2curves"] -# halo2curves = { git = "https://github.com/timoftime/halo2curves", rev = "b682183" } -halo2curves = { path = "../halo2curves" } +halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index b27d0439..acc712fd 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,3 +1,4 @@ +use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ @@ -8,8 +9,6 @@ use halo2_base::utils::BigPrimeField; use halo2_base::{gates::GateInstructions, utils::modulus, Context, QuantumCell::Constant}; use num_bigint::BigUint; -const XI_0: i64 = 1; - impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { // computes a ** (p ** power) // only works for p = 3 (mod 4) and p = 1 (mod 6) diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index c4c22d5c..e39d079f 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -6,11 +6,13 @@ use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; pub mod final_exp; pub mod pairing; +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, 1>; +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 index cb46065c..be44bad5 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -1,5 +1,5 @@ #![allow(non_snake_case)] -use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; +use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint, XI_0}; use crate::fields::vector::FieldVector; use crate::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; use crate::{ @@ -11,8 +11,6 @@ use halo2_base::halo2_proofs::halo2curves::bls12_381::BLS_X_IS_NEGATIVE; use halo2_base::utils::BigPrimeField; use halo2_base::Context; -const XI_0: i64 = 1; - // 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) @@ -189,6 +187,16 @@ pub fn fp12_multiply_with_line_equal( 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, @@ -331,10 +339,16 @@ pub fn multi_miller_loop( } } + // 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 } -// 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 +/// 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>, } @@ -344,6 +358,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { 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, @@ -353,6 +368,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { 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, @@ -363,6 +379,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { 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, @@ -374,6 +391,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { miller_loop::(&g2_chip, ctx, P, Q) } + /// Multi-pairing Miller loop. pub fn multi_miller_loop( &self, ctx: &mut Context, @@ -384,6 +402,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { 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) From 9c35ca740d5f39b84684369effafa374000577f1 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 2 Oct 2023 14:37:41 +0200 Subject: [PATCH 11/51] add comments --- halo2-ecc/src/bls12_381/bls_signature.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/halo2-ecc/src/bls12_381/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs index 230e404f..cfd11a93 100644 --- a/halo2-ecc/src/bls12_381/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -38,6 +38,7 @@ impl<'chip, F: BigPrimeField> BlsSignatureChip<'chip, F> { self.assert_valid_signature(ctx, signature_assigned, hash_m_assigned, pubkey_assigned); } + /// Verifies BLS signature and returns assigned selector. pub fn is_valid_signature( &self, ctx: &mut Context, @@ -57,6 +58,7 @@ impl<'chip, F: BigPrimeField> BlsSignatureChip<'chip, F> { fp12_chip.is_equal(ctx, gt, fp12_one) } + /// Verifies BLS signature with equality check. pub fn assert_valid_signature( &self, ctx: &mut Context, From 405609bce9af3afffffd2fb879dbaf747f08afd8 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 6 Oct 2023 20:24:25 +0200 Subject: [PATCH 12/51] add hash2curve chip & trait --- halo2-ecc/src/bls12_381/final_exp.rs | 1 + halo2-ecc/src/bls12_381/hash_to_curve.rs | 954 +++++++++++++++++++++++ halo2-ecc/src/bls12_381/mod.rs | 11 +- halo2-ecc/src/bls12_381/utils.rs | 106 +++ halo2-ecc/src/bn254/final_exp.rs | 4 +- halo2-ecc/src/bn254/pairing.rs | 1 + halo2-ecc/src/ecc/mod.rs | 102 +++ halo2-ecc/src/fields/fp.rs | 23 +- halo2-ecc/src/fields/fp2.rs | 81 +- halo2-ecc/src/fields/mod.rs | 36 + halo2-ecc/src/fields/vector.rs | 26 + 11 files changed, 1327 insertions(+), 18 deletions(-) create mode 100644 halo2-ecc/src/bls12_381/hash_to_curve.rs create mode 100644 halo2-ecc/src/bls12_381/utils.rs diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index acc712fd..0721ea3a 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,5 +1,6 @@ use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +use crate::fields::FieldChipExt; use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ ecc::get_naf, diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs new file mode 100644 index 00000000..403c22f9 --- /dev/null +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -0,0 +1,954 @@ +//! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` +//! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 + +use std::{iter, marker::PhantomData}; + +use super::utils::*; +use crate::ecc::EcPoint; +use crate::ff::Field; +use crate::fields::FieldChipExt; +use crate::halo2_base::{AssignedValue, Context, QuantumCell}; +use crate::halo2_proofs::plonk::Error; +use crate::{ + ecc::EccChip, + fields::{vector::FieldVector, FieldChip, Selectable}, +}; +use halo2_base::gates::{GateInstructions, RangeChip, RangeInstructions}; +use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; +use halo2_base::halo2_proofs::halo2curves::CurveExt; +use halo2_base::utils::BigPrimeField; +use itertools::Itertools; +use num_bigint::BigUint; + +use super::{Fp2Chip, Fp2Point, G2Point}; + +pub trait HashInstructions { + const BLOCK_SIZE: usize; + const DIGEST_SIZE: usize; + + type ThreadBuidler; + + /// Digests input using hash function and returns finilized output. + /// `MAX_INPUT_SIZE` is the maximum size of input that can be processed by the hash function. + /// `strict` flag indicates whether to perform range check on input bytes. + fn digest( + &self, + ctx: &mut Self::ThreadBuidler, + input: impl Iterator>, + strict: bool, + ) -> Result, Error>; + + fn range(&self) -> &RangeChip; +} + +pub trait HashEccChip, C: HashCurveExt> +where + FC::FieldType: crate::ff::PrimeField, + FC: Selectable, +{ + fn field_chip(&self) -> &FC; + + fn scalar_mult_bits( + &self, + ctx: &mut Context, + p: EcPoint, + bits: Vec>, + window_bits: usize, + ) -> EcPoint; + + fn hash_to_field>>( + &self, + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + msg: impl Iterator>, + dst: impl AsRef<[u8]>, + ) -> Result<[FC::FieldPoint; 2], Error>; + + fn isogeny_map( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint; + + fn clear_cofactor( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint; + + fn mul_by_bls_x( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + let bls_x_bits = (0..64) + .map(|i| ((C::BLS_X >> i) & 1) as u8) + .map(|b| ctx.load_constant(F::from(b as u64))) + .collect_vec(); + + self.scalar_mult_bits(ctx, p, bls_x_bits, 4) + } + + fn psi( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + // 1 / ((u+1) ^ ((q-1)/3)) + let psi_x = self.field_chip().load_constant(ctx, C::PSI_X); + + // 1 / ((u+1) ^ (p-1)/2) + let psi_y = self.field_chip().load_constant(ctx, C::PSI_Y); + + let x_frob = self.field_chip().conjugate(ctx, p.x); + let y_frob = self.field_chip().conjugate(ctx, p.y); + + let x = self.field_chip().mul(ctx, x_frob, psi_x.clone()); + let y = self.field_chip().mul(ctx, y_frob, psi_y.clone()); + + EcPoint::new(x, y) + } + + fn psi2( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + // 1 / 2 ^ ((q-1)/3) + let psi2_x = self.field_chip().load_constant(ctx, C::PSI2_X); + + let x = self.field_chip().mul(ctx, p.x, psi2_x.clone()); + let y = self.field_chip().negate(ctx, p.y); + + EcPoint::new(x, y) + } +} + +#[derive(Debug, Clone)] +pub struct AssignedHashResult { + // pub input_len: AssignedValue, + pub input_bytes: Vec>, + pub output_bytes: [AssignedValue; 32], +} + +#[derive(Debug)] +pub struct HashToCurveChip< + 'chip, + F: BigPrimeField, + FC: FieldChip, + HC: HashInstructions, + C: HashCurveExt, +> { + hash_chip: &'chip HC, + ecc_chip: EccChip<'chip, F, FC>, + _curve: PhantomData, +} + +impl< + 'chip, + F: BigPrimeField, + C: HashCurveExt, + FC: FieldChipExt, + HC: HashInstructions> + 'chip, + > HashToCurveChip<'chip, F, FC, HC, C> +where + FC::FieldType: crate::ff::PrimeField, + FC: Selectable, + EccChip<'chip, F, FC>: HashEccChip, +{ + pub fn new(hash_chip: &'chip HC, field_chip: &'chip FC) -> Self { + Self { hash_chip, ecc_chip: EccChip::new(field_chip), _curve: PhantomData } + } + + pub fn hash_to_curve( + &self, + ctx: &mut HC::ThreadBuidler, + msg: impl Iterator>, + dst: impl AsRef<[u8]>, + ) -> Result, Error> { + let u = self.ecc_chip.hash_to_field(ctx, self.hash_chip, msg, dst)?; + let p = self.map_to_curve(ctx, u)?; + Ok(p) + } + + fn map_to_curve( + &self, + ctx: &mut Context, + u: [FC::FieldPoint; 2], + ) -> Result, Error> { + let [u0, u1] = u; + + let p1 = self.map_to_curve_simple_swu(ctx, u0); + let p2 = self.map_to_curve_simple_swu(ctx, u1); + + let p_sum = self.ecc_chip.add_unequal(ctx, p1, p2, false); + + let iso_p = self.ecc_chip.isogeny_map(ctx, p_sum); + + Ok(self.ecc_chip.clear_cofactor(ctx, iso_p)) + } + + /// Implements [section 6.2 of draft-irtf-cfrg-hash-to-curve-16][map_to_curve_simple_swu] + /// + /// [map_to_curve_simple_swu]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2 + /// + /// References: + /// - https://github.com/mikelodder7/bls12_381_plus/blob/ml/0.5.6/src/hash_to_curve/map_g2.rs#L388 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/weierstrass.ts#L1175 + fn map_to_curve_simple_swu( + &self, + ctx: &mut Context, + u: FC::FieldPoint, + ) -> EcPoint { + let field_chip = self.ecc_chip.field_chip(); + let gate = field_chip.range().gate(); + + // constants + let swu_a = field_chip.load_constant(ctx, C::SWU_A); + let swu_b = field_chip.load_constant(ctx, C::SWU_B); + let swu_z = field_chip.load_constant(ctx, C::SWU_Z); + let fq2_one = field_chip.load_constant(ctx, ::ONE); + + let usq = field_chip.mul(ctx, u.clone(), u.clone()); // 1. tv1 = u^2 + let z_usq = field_chip.mul(ctx, usq, swu_z.clone()); // 2. tv1 = Z * tv1 + let zsq_u4 = field_chip.mul(ctx, z_usq.clone(), z_usq.clone()); // 3. tv2 = tv1^2 + let tv2 = field_chip.add(ctx, zsq_u4, z_usq.clone()); // 4. tv2 = tv2 + tv1 + let tv3 = field_chip.add_no_carry(ctx, tv2.clone(), fq2_one); // 5. tv3 = tv2 + 1 + let x0_num = field_chip.mul(ctx, tv3, swu_b.clone()); // 6. tv3 = B * tv3 + + let x_den = { + let tv2_is_zero = field_chip.is_zero(ctx, tv2.clone()); + let tv2_neg = field_chip.negate(ctx, tv2); + + field_chip.select(ctx, swu_z, tv2_neg, tv2_is_zero) // tv2_is_zero ? swu_z : tv2_neg + }; // 7. tv4 = tv2 != 0 ? -tv2 : Z + + let x_den = field_chip.mul(ctx, x_den, swu_a.clone()); // 8. tv4 = A * tv4 + + let x0_num_sqr = field_chip.mul(ctx, x0_num.clone(), x0_num.clone()); // 9. tv2 = tv3^2 + let x_densq = field_chip.mul(ctx, x_den.clone(), x_den.clone()); // 10. tv6 = tv4^2 + let ax_densq = field_chip.mul(ctx, x_densq.clone(), swu_a); // 11. tv5 = A * tv6 + let tv2 = field_chip.add_no_carry(ctx, x0_num_sqr, ax_densq); // 12. tv2 = tv2 + tv5 + let tv2 = field_chip.mul(ctx, tv2, x0_num.clone()); // 13. tv2 = tv2 * tv3 + let gx_den = field_chip.mul(ctx, x_densq, x_den.clone()); // 14. tv6 = tv6 * tv4 + let tv5 = field_chip.mul(ctx, gx_den.clone(), swu_b); // 15. tv5 = B * tv6 + let gx0_num = field_chip.add(ctx, tv2, tv5); // 16. tv2 = tv2 + tv5 + + let x = field_chip.mul(ctx, &z_usq, &x0_num); // 17. x = tv1 * tv3 + + let (is_gx1_square, y1) = self.sqrt_ratio(ctx, gx0_num, gx_den); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + + let y = field_chip.mul(ctx, &z_usq, &u); // 19. y = tv1 * u + let y = field_chip.mul(ctx, y, y1.clone()); // 20. y = y * y1 + let x = field_chip.select(ctx, x0_num, x, is_gx1_square); // 21. x = is_gx1_square ? tv3 : x + let y = field_chip.select(ctx, y1, y, is_gx1_square); // 22. y = is_gx1_square ? y1 : y + + let to_neg = { + let u_sgn = field_chip.sgn0(ctx, u); + let y_sgn = field_chip.sgn0(ctx, y.clone()); + gate.xor(ctx, u_sgn, y_sgn) + }; // 23. e1 = sgn0(u) == sgn0(y) // we implement an opposite condition: !e1 = sgn0(u) ^ sgn0(y) + + let y_neg = field_chip.negate(ctx, y.clone()); + let y = field_chip.select(ctx, y_neg, y, to_neg); // 24. y = !e1 ? -y : y + let x = field_chip.divide(ctx, x, x_den); // 25. x = x / tv4 + + EcPoint::new(x, y) + } + + // Implements [Appendix F.2.1 of draft-irtf-cfrg-hash-to-curve-16][sqrt_ration] + // + // [sqrt_ration]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2.1 + fn sqrt_ratio( + &self, + ctx: &mut Context, + num: FC::FieldPoint, + div: FC::FieldPoint, + ) -> (AssignedValue, FC::FieldPoint) { + let field_chip = self.ecc_chip.field_chip(); + let num_v = field_chip.get_assigned_value(&num.clone().into()); + let div_v = field_chip.get_assigned_value(&div.clone().into()); + + let (is_square, y) = FC::FieldType::sqrt_ratio(&num_v, &div_v); + + let is_square = ctx.load_witness(F::from(is_square.unwrap_u8() as u64)); + field_chip.gate().assert_bit(ctx, is_square); // assert is_square is boolean + + let y_assigned = field_chip.load_private(ctx, y); + let y_sqr = field_chip.mul(ctx, y_assigned.clone(), y_assigned.clone()); // y_sqr = y1^2 + + let ratio = field_chip.divide(ctx, num, div); // r = u / v + + let swu_z = field_chip.load_constant(ctx, C::SWU_Z); + let ratio_z = field_chip.mul(ctx, ratio.clone(), swu_z.clone()); // r_z = r * z + + let y_check = field_chip.select(ctx, ratio, ratio_z, is_square); // y_check = is_square ? ratio : r_z + + field_chip.assert_equal(ctx, y_check, y_sqr); // assert y_check == y_sqr + + (is_square, y_assigned) + } +} + +const G2_EXT_DEGREE: usize = 2; + +// L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) +const L: usize = 64; + +impl<'chip, F: BigPrimeField> HashEccChip, G2> + for EccChip<'chip, F, Fp2Chip<'chip, F>> +{ + fn field_chip(&self) -> &Fp2Chip<'chip, F> { + self.field_chip + } + + fn scalar_mult_bits( + &self, + ctx: &mut Context, + p: G2Point, + bits: Vec>, + window_bits: usize, + ) -> G2Point { + self.scalar_mult_bits(ctx, p, bits, window_bits) + } + + /// Implements [section 5.2 of `draft-irtf-cfrg-hash-to-curve-16`][hash_to_field]. + /// + /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.2 + /// + /// References: + /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L49 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L128 + /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L11 + fn hash_to_field>>( + &self, + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + msg: impl Iterator>, + dst: impl AsRef<[u8]>, + ) -> Result<[Fp2Point; 2], Error> { + let fp_chip = self.field_chip().fp_chip(); + let range = hash_chip.range(); + let gate = range.gate(); + + // constants + let zero = ctx.load_zero(); + + let assigned_msg = msg + .map(|cell| match cell { + QuantumCell::Existing(v) => v, + QuantumCell::Witness(v) => ctx.load_witness(v), + QuantumCell::Constant(v) => ctx.load_constant(v), + _ => unreachable!(), + }) + .collect_vec(); + + let len_in_bytes = 2 * G2_EXT_DEGREE * L; + let extended_msg = expand_message_xmd(ctx, hash_chip, assigned_msg, len_in_bytes, dst)?; + + // 2^256 + let two_pow_256 = fp_chip.load_constant_uint(ctx, BigUint::from(2u8).pow(256)); + let fq_bytes = 48; //((Fq::NUM_BITS as f64) / 8f64).ceil() as usize; + + let u = extended_msg + .chunks(L) + .chunks(G2_EXT_DEGREE) + .into_iter() + .map(|elm_chunk| { + FieldVector( + elm_chunk + .map(|tv| { + let mut buf = vec![zero; fq_bytes]; + let rem = fq_bytes - 32; + buf[rem..].copy_from_slice(&tv[..32]); + let lo = decode_into_field_be::( + ctx, + gate, + buf.to_vec(), + &fp_chip.limb_bases, + fp_chip.limb_bits(), + ); + + buf[rem..].copy_from_slice(&tv[32..]); + let hi = decode_into_field_be::( + ctx, + gate, + buf.to_vec(), + &fp_chip.limb_bases, + fp_chip.limb_bits(), + ); + + let lo_2_256 = fp_chip.mul_no_carry(ctx, lo, two_pow_256.clone()); + let lo_2_356_hi = fp_chip.add_no_carry(ctx, lo_2_256, hi); + fp_chip.carry_mod(ctx, lo_2_356_hi) + }) + .collect_vec(), + ) + }) + .collect_vec() + .try_into() + .unwrap(); + + Ok(u) + } + + /// Implements [Appendix E.3 of draft-irtf-cfrg-hash-to-curve-16][isogeny_map_g2] + /// + /// [isogeny_map_g2]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-E.3 + /// + /// References: + /// - https://github.com/mikelodder7/bls12_381_plus/blob/ml/0.5.6/src/g2.rs#L1153 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L167 + fn isogeny_map(&self, ctx: &mut Context, p: G2Point) -> G2Point { + let fp2_chip = self.field_chip(); + // constants + let iso_coeffs = [ + G2::ISO_XNUM.to_vec(), + G2::ISO_XDEN.to_vec(), + G2::ISO_YNUM.to_vec(), + G2::ISO_YDEN.to_vec(), + ] + .map(|coeffs| coeffs.into_iter().map(|iso| fp2_chip.load_constant(ctx, iso)).collect_vec()); + + let fq2_zero = fp2_chip.load_constant(ctx, Fq2::ZERO); + + let [x_num, x_den, y_num, y_den] = iso_coeffs.map(|coeffs| { + coeffs.into_iter().fold(fq2_zero.clone(), |acc, v| { + let acc = fp2_chip.mul(ctx, acc, &p.x); + let no_carry = fp2_chip.add_no_carry(ctx, acc, v); + fp2_chip.carry_mod(ctx, no_carry) + }) + }); + + let x = { fp2_chip.divide_unsafe(ctx, x_num, x_den) }; + + let y = { + let tv = fp2_chip.divide_unsafe(ctx, y_num, y_den); + fp2_chip.mul(ctx, &p.y, tv) + }; + + G2Point::new(x, y) + } + + /// Implements [Appendix G.3 of draft-irtf-cfrg-hash-to-curve-16][clear_cofactor] + /// + /// [clear_cofactor]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-G.3 + /// + /// References: + /// - https://github.com/mikelodder7/bls12_381_plus/blob/ml/0.5.6/src/g2.rs#L956 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/bls12-381.ts#L1111 + fn clear_cofactor(&self, ctx: &mut Context, p: G2Point) -> G2Point { + let t1 = { + // scalar multiplication is very expensive in terms of rows used + // TODO: is there other ways to clear cofactor that avoid scalar multiplication? + let tv = self.mul_by_bls_x(ctx, p.clone()); + self.negate(ctx, tv) + }; // [-x]P + + let t2 = self.psi(ctx, p.clone()); // Ψ(P) + + let t3 = self.double(ctx, p.clone()); // 2P + let t3 = self.psi2(ctx, t3); // Ψ²(2P) + let t3 = self.sub_unequal(ctx, t3, t2.clone(), false); // Ψ²(2P) - Ψ(P) + + let t2 = self.add_unequal(ctx, t1.clone(), t2, false); // [-x]P + Ψ(P) + let t2 = { + let tv = self.mul_by_bls_x(ctx, t2); + self.negate(ctx, tv) + }; // [x²]P - [x]Ψ(P) + + // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + let t3 = self.add_unequal(ctx, t3, t2, false); + // Ψ²(2P) - Ψ(Plet ) + [x²]P - [x]Ψ(P) + [x]P + let t3 = self.sub_unequal(ctx, t3, t1, false); + + // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P => [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) + self.sub_unequal(ctx, t3, p, false) + } +} + +/// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. +/// +/// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3 +/// +/// References: +/// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 +/// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 +/// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 +fn expand_message_xmd>>( + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + msg: Vec>, + len_in_bytes: usize, + dst: impl AsRef<[u8]>, +) -> Result>, Error> { + let range = hash_chip.range(); + let gate = range.gate(); + + let zero = ctx.load_zero(); + let one = ctx.load_constant(F::ONE); + + // assign DST bytes & cache them + let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); + let dst_prime = dst + .as_ref() + .iter() + .map(|&b| ctx.load_constant(F::from(b as u64))) + .chain(iter::once(dst_len)) + .collect_vec(); + + // padding and length strings + let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); + let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); + + // compute blocks + let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; + let mut b_vals = Vec::with_capacity(ell); + let msg_prime = z_pad + .into_iter() + .chain(msg) + .chain(l_i_b_str) + .chain(iter::once(zero)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); + + let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; + + b_vals.insert( + 0, + hash_chip + .digest::<77>( + ctx, + b_0.into_iter() + .chain(iter::once(one)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing), + false, + )? + .output_bytes, + ); + + for i in 1..ell { + let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) + .into_iter() + .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); + + b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); + } + + let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); + + Ok(uniform_bytes) +} + +pub trait HashCurveExt: CurveExt +where + Self::Base: crate::ff::PrimeField, +{ + type Fp: crate::ff::PrimeField + crate::ff::WithSmallOrderMulGroup<3>; + + const BLS_X: u64; + + const SWU_A: Self::Base; + const SWU_B: Self::Base; + const SWU_Z: Self::Base; + + const ISO_XNUM: [Self::Base; 4]; + const ISO_XDEN: [Self::Base; 3]; + const ISO_YNUM: [Self::Base; 4]; + const ISO_YDEN: [Self::Base; 4]; + + const PSI_X: Self::Base; + const PSI_Y: Self::Base; + const PSI2_X: Self::Base; +} + +mod bls12_381 { + use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq, G2}; + + use super::HashCurveExt; + + impl HashCurveExt for G2 { + type Fp = Fq; + + const BLS_X: u64 = 0xd201000000010000; + + const SWU_A: Self::Base = Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0xe53a_0000_0313_5242, + 0x0108_0c0f_def8_0285, + 0xe788_9edb_e340_f6bd, + 0x0b51_3751_2631_0601, + 0x02d6_9857_17c7_44ab, + 0x1220_b4e9_79ea_5467, + ]), + }; + + const SWU_B: Self::Base = Self::Base { + c0: Fq::from_raw_unchecked([ + 0x22ea_0000_0cf8_9db2, + 0x6ec8_32df_7138_0aa4, + 0x6e1b_9440_3db5_a66e, + 0x75bf_3c53_a794_73ba, + 0x3dd3_a569_412c_0a34, + 0x125c_db5e_74dc_4fd1, + ]), + c1: Fq::from_raw_unchecked([ + 0x22ea_0000_0cf8_9db2, + 0x6ec8_32df_7138_0aa4, + 0x6e1b_9440_3db5_a66e, + 0x75bf_3c53_a794_73ba, + 0x3dd3_a569_412c_0a34, + 0x125c_db5e_74dc_4fd1, + ]), + }; + + const SWU_Z: Self::Base = Self::Base { + c0: Fq::from_raw_unchecked([ + 0x87eb_ffff_fff9_555c, + 0x656f_ffe5_da8f_fffa, + 0x0fd0_7493_45d3_3ad2, + 0xd951_e663_0665_76f4, + 0xde29_1a3d_41e9_80d3, + 0x0815_664c_7dfe_040d, + ]), + c1: Fq::from_raw_unchecked([ + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206, + ]), + }; + + /// Coefficients of the 3-isogeny x map's numerator + const ISO_XNUM: [Self::Base; 4] = [ + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x40aa_c71c_71c7_25ed, + 0x1909_5555_7a84_e38e, + 0xd817_050a_8f41_abc3, + 0xd864_85d4_c87f_6fb1, + 0x696e_b479_f885_d059, + 0x198e_1a74_3280_02d2, + ]), + c1: Fq::zero(), + }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x0a0c_5555_5559_71c3, + 0xdb0c_0010_1f9e_aaae, + 0xb1fb_2f94_1d79_7997, + 0xd396_0742_ef41_6e1c, + 0xb700_40e2_c205_56f4, + 0x149d_7861_e581_393b, + ]), + c1: Fq::from_raw_unchecked([ + 0xaff2_aaaa_aaa6_38e8, + 0x439f_ffee_91b5_5551, + 0xb535_a30c_d937_7c8c, + 0x90e1_4442_0443_a4a2, + 0x941b_66d3_8146_55e2, + 0x0563_9988_53fe_ad5e, + ]), + }, + Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0x5fe5_5555_554c_71d0, + 0x873f_ffdd_236a_aaa3, + 0x6a6b_4619_b26e_f918, + 0x21c2_8884_0887_4945, + 0x2836_cda7_028c_abc5, + 0x0ac7_3310_a7fd_5abd, + ]), + }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x47f6_71c7_1ce0_5e62, + 0x06dd_5707_1206_393e, + 0x7c80_cd2a_f3fd_71a2, + 0x0481_03ea_9e6c_d062, + 0xc545_16ac_c8d0_37f6, + 0x1380_8f55_0920_ea41, + ]), + c1: Fq::from_raw_unchecked([ + 0x47f6_71c7_1ce0_5e62, + 0x06dd_5707_1206_393e, + 0x7c80_cd2a_f3fd_71a2, + 0x0481_03ea_9e6c_d062, + 0xc545_16ac_c8d0_37f6, + 0x1380_8f55_0920_ea41, + ]), + }, + ]; + + /// Coefficients of the 3-isogeny x map's denominator + const ISO_XDEN: [Self::Base; 3] = [ + // Self::Fp::zero(), + Self::Base { c0: Fq::one(), c1: Fq::zero() }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x4476_0000_0027_552e, + 0xdcb8_009a_4348_0020, + 0x6f7e_e9ce_4a6e_8b59, + 0xb103_30b7_c0a9_5bc6, + 0x6140_b1fc_fb1e_54b7, + 0x0381_be09_7f0b_b4e1, + ]), + c1: Fq::from_raw_unchecked([ + 0x7588_ffff_ffd8_557d, + 0x41f3_ff64_6e0b_ffdf, + 0xf7b1_e8d2_ac42_6aca, + 0xb374_1acd_32db_b6f8, + 0xe9da_f5b9_482d_581f, + 0x167f_53e0_ba74_31b8, + ]), + }, + Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0x1f3a_ffff_ff13_ab97, + 0xf25b_fc61_1da3_ff3e, + 0xca37_57cb_3819_b208, + 0x3e64_2736_6f8c_ec18, + 0x0397_7bc8_6095_b089, + 0x04f6_9db1_3f39_a952, + ]), + }, + ]; + + /// Coefficients of the 3-isogeny y map's numerator + const ISO_YNUM: [Self::Base; 4] = [ + Self::Base { + c0: Fq::from_raw_unchecked([ + 0xa470_bda1_2f67_f35c, + 0xc0fe_38e2_3327_b425, + 0xc9d3_d0f2_c6f0_678d, + 0x1c55_c993_5b5a_982e, + 0x27f6_c0e2_f074_6764, + 0x117c_5e6e_28aa_9054, + ]), + c1: Fq::zero(), + }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0xd7f9_5555_5553_1c74, + 0x21cf_fff7_48da_aaa8, + 0x5a9a_d186_6c9b_be46, + 0x4870_a221_0221_d251, + 0x4a0d_b369_c0a3_2af1, + 0x02b1_ccc4_29ff_56af, + ]), + c1: Fq::from_raw_unchecked([ + 0xe205_aaaa_aaac_8e37, + 0xfcdc_0007_6879_5556, + 0x0c96_011a_8a15_37dd, + 0x1c06_a963_f163_406e, + 0x010d_f44c_82a8_81e6, + 0x174f_4526_0f80_8feb, + ]), + }, + Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0xbf0a_71c7_1c91_b406, + 0x4d6d_55d2_8b76_38fd, + 0x9d82_f98e_5f20_5aee, + 0xa27a_a27b_1d1a_18d5, + 0x02c3_b2b2_d293_8e86, + 0x0c7d_1342_0b09_807f, + ]), + }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x96d8_f684_bdfc_77be, + 0xb530_e4f4_3b66_d0e2, + 0x184a_88ff_3796_52fd, + 0x57cb_23ec_fae8_04e1, + 0x0fd2_e39e_ada3_eba9, + 0x08c8_055e_31c5_d5c3, + ]), + c1: Fq::from_raw_unchecked([ + 0x96d8_f684_bdfc_77be, + 0xb530_e4f4_3b66_d0e2, + 0x184a_88ff_3796_52fd, + 0x57cb_23ec_fae8_04e1, + 0x0fd2_e39e_ada3_eba9, + 0x08c8_055e_31c5_d5c3, + ]), + }, + ]; + + /// Coefficients of the 3-isogeny y map's denominator + const ISO_YDEN: [Self::Base; 4] = [ + Self::Base { c0: Fq::one(), c1: Fq::zero() }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x66b1_0000_003a_ffc5, + 0xcb14_00e7_64ec_0030, + 0xa73e_5eb5_6fa5_d106, + 0x8984_c913_a0fe_09a9, + 0x11e1_0afb_78ad_7f13, + 0x0542_9d0e_3e91_8f52, + ]), + c1: Fq::from_raw_unchecked([ + 0x534d_ffff_ffc4_aae6, + 0x5397_ff17_4c67_ffcf, + 0xbff2_73eb_870b_251d, + 0xdaf2_8271_5287_0915, + 0x393a_9cba_ca9e_2dc3, + 0x14be_74db_faee_5748, + ]), + }, + Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0x5db0_ffff_fd3b_02c5, + 0xd713_f523_58eb_fdba, + 0x5ea6_0761_a84d_161a, + 0xbb2c_75a3_4ea6_c44a, + 0x0ac6_7359_21c1_119b, + 0x0ee3_d913_bdac_fbf6, + ]), + }, + Self::Base { + c0: Fq::from_raw_unchecked([ + 0x0162_ffff_fa76_5adf, + 0x8f7b_ea48_0083_fb75, + 0x561b_3c22_59e9_3611, + 0x11e1_9fc1_a9c8_75d5, + 0xca71_3efc_0036_7660, + 0x03c6_a03d_41da_1151, + ]), + c1: Fq::from_raw_unchecked([ + 0x0162_ffff_fa76_5adf, + 0x8f7b_ea48_0083_fb75, + 0x561b_3c22_59e9_3611, + 0x11e1_9fc1_a9c8_75d5, + 0xca71_3efc_0036_7660, + 0x03c6_a03d_41da_1151, + ]), + }, + ]; + + const PSI_X: Self::Base = Self::Base { + c0: Fq::zero(), + c1: Fq::from_raw_unchecked([ + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a, + ]), + }; + + const PSI_Y: Self::Base = Self::Base { + c0: Fq::from_raw_unchecked([ + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8, + ]), + c1: Fq::from_raw_unchecked([ + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2, + ]), + }; + + const PSI2_X: Self::Base = Self::Base { + c0: Fq::from_raw_unchecked([ + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741, + ]), + c1: Fq::zero(), + }; + } +} + +// #[cfg(test)] +// mod test { +// use std::env::var; +// use std::vec; +// use std::{cell::RefCell, marker::PhantomData}; + +// use crate::gadget::crypto::sha256_flex::{SpreadChip, SpreadConfig}; +// use crate::gadget::crypto::ShaCircuitBuilder; +// use crate::gadget::crypto::{Sha256Chip, ShaThreadBuilder}; +// use crate::util::{print_fq2_dev, Challenges, IntoWitness}; + +// use super::*; +// use eth_types::{Mainnet, Testnet}; +// use halo2_base::gates::builder::FlexGateConfigParams; +// use halo2_base::gates::range::RangeConfig; +// use halo2_base::safe_types::RangeChip; +// use halo2_base::SKIP_FIRST_PASS; +// use halo2_base::{ +// gates::{builder::GateThreadBuilder, range::RangeStrategy}, +// halo2_proofs::{ +// circuit::{Layouter, SimpleFloorPlanner}, +// dev::MockProver, +// halo2curves::bn256::Fr, +// plonk::{Circuit, ConstraintSystem}, +// }, +// }; +// use halo2_ecc::bigint::CRTInteger; +// use halo2_proofs::circuit::Value; +// use halo2curves::bls12_381::G2; +// use sha2::{Digest, Sha256}; + +// fn get_circuit( +// k: usize, +// mut builder: ShaThreadBuilder, +// input_vector: &[Vec], +// ) -> Result>, Error> { +// let range = RangeChip::default(8); +// let sha256 = Sha256Chip::new(&range); + +// let h2c_chip = HashToCurveChip::::new(&sha256); +// let fp_chip = halo2_ecc::bls12_381::FpChip::::new(&range, G2::LIMB_BITS, G2::NUM_LIMBS); + +// for input in input_vector { +// let mut cache = HashToCurveCache::::default(); +// let hp = h2c_chip.hash_to_curve::( +// &mut builder, +// &fp_chip, +// input.clone().into_witness(), +// &mut cache, +// )?; + +// print_fq2_dev::(hp.x(), "res_p.x"); +// print_fq2_dev::(hp.y(), "res_p.y"); +// } + +// builder.config(k, None); +// Ok(ShaCircuitBuilder::mock(builder)) +// } + +// #[test] +// fn test_hash_to_g2() { +// let k = 17; + +// let test_input = vec![0u8; 32]; +// let builder = ShaThreadBuilder::::mock(); +// let circuit = get_circuit(k, builder, &[test_input]).unwrap(); + +// let prover = MockProver::run(k as u32, &circuit, vec![]).unwrap(); +// prover.assert_satisfied(); +// } +// } diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index c1d6583f..013bf948 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -1,19 +1,26 @@ use crate::bigint::ProperCrtUint; +use crate::ecc::EcPoint; use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; pub mod bls_signature; pub mod final_exp; +pub mod hash_to_curve; pub mod pairing; +pub(crate) mod utils; 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>; +pub type FpPoint = ProperCrtUint; +pub type FqPoint = FieldVector>; +pub type Fp2Point = FieldVector>; +pub type G1Point = EcPoint>; +pub type G2Point = EcPoint>>; + #[cfg(test)] pub(crate) mod tests; diff --git a/halo2-ecc/src/bls12_381/utils.rs b/halo2-ecc/src/bls12_381/utils.rs new file mode 100644 index 00000000..1b7904a2 --- /dev/null +++ b/halo2-ecc/src/bls12_381/utils.rs @@ -0,0 +1,106 @@ +use halo2_base::{ + gates::GateInstructions, utils::BigPrimeField, AssignedValue, Context, QuantumCell, +}; +use itertools::Itertools; +use num_bigint::BigUint; + +use crate::bigint::{ProperCrtUint, ProperUint}; + +/// Integer to Octet Stream (numberToBytesBE) +pub fn i2osp( + mut value: u128, + length: usize, + mut f: impl FnMut(F) -> AssignedValue, +) -> Vec> { + let mut octet_string = vec![0; length]; + for i in (0..length).rev() { + octet_string[i] = value & 0xff; + value >>= 8; + } + octet_string.into_iter().map(|b| f(F::from(b as u64))).collect() +} + +pub fn strxor( + a: impl IntoIterator>, + b: impl IntoIterator>, + gate: &impl GateInstructions, + ctx: &mut Context, +) -> Vec> { + a.into_iter().zip(b.into_iter()).map(|(a, b)| bitwise_xor::<_, 8>(a, b, gate, ctx)).collect() +} + +pub fn bitwise_xor( + a: AssignedValue, + b: AssignedValue, + gate: &impl GateInstructions, + ctx: &mut Context, +) -> AssignedValue { + let one = ctx.load_constant(F::ONE); + let mut a_bits = gate.num_to_bits(ctx, a, BITS); + let mut b_bits = gate.num_to_bits(ctx, b, BITS); + + let xor_bits = + a_bits.into_iter().zip(b_bits.into_iter()).map(|(a, b)| gate.xor(ctx, a, b)).collect_vec(); + + bits_to_num(gate, ctx, xor_bits) +} + +pub fn bits_to_num>>( + gate: &impl GateInstructions, + ctx: &mut Context, + bits: I, +) -> AssignedValue +where + I::IntoIter: DoubleEndedIterator + ExactSizeIterator, +{ + let bits_iter = bits.into_iter(); + assert!(bits_iter.len() <= F::NUM_BITS as usize); + bits_iter.rev().fold(ctx.load_zero(), |acc, bit| { + gate.mul_add(ctx, acc, QuantumCell::Constant(F::from(2u64)), bit) + }) +} + +/// Converts assigned bytes into biginterger +/// Warning: method does not perform any checks on input `bytes`. +pub fn decode_into_field( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: Vec>, + limb_bases: &[F], + limb_bits: usize, +) -> ProperCrtUint { + let limb_bytes = limb_bits / 8; + let bits = limb_bases.len() * limb_bits; + + let value = + BigUint::from_bytes_le(&bytes.iter().map(|v| v.value().get_lower_32() as u8).collect_vec()); + + // inputs is a bool or uint8. + let assigned_uint = if bits == 1 || limb_bytes == 8 { + ProperUint(bytes) + } else { + let byte_base = + (0..limb_bytes).map(|i| QuantumCell::Constant(gate.pow_of_two()[i * 8])).collect_vec(); + let limbs = bytes + .chunks(limb_bytes) + .map(|chunk| gate.inner_product(ctx, chunk.to_vec(), byte_base[..chunk.len()].to_vec())) + .collect::>(); + ProperUint(limbs) + }; + + assigned_uint.into_crt(ctx, gate, value, limb_bases, limb_bits) +} + +pub fn decode_into_field_be>>( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: I, + limb_bases: &[F], + limb_bits: usize, +) -> ProperCrtUint +where + I::IntoIter: DoubleEndedIterator, +{ + let bytes = bytes.into_iter().rev().collect_vec(); + decode_into_field::(ctx, gate, bytes, limb_bases, limb_bits) +} diff --git a/halo2-ecc/src/bn254/final_exp.rs b/halo2-ecc/src/bn254/final_exp.rs index ae2ecac9..bb0d76e4 100644 --- a/halo2-ecc/src/bn254/final_exp.rs +++ b/halo2-ecc/src/bn254/final_exp.rs @@ -1,8 +1,8 @@ use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use crate::halo2_proofs::{ +use crate::{halo2_proofs::{ arithmetic::Field, halo2curves::bn256::{Fq, Fq2, BN_X, FROBENIUS_COEFF_FQ12_C1}, -}; +}, fields::FieldChipExt}; use crate::{ ecc::get_naf, fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, diff --git a/halo2-ecc/src/bn254/pairing.rs b/halo2-ecc/src/bn254/pairing.rs index dbd7382f..8f08b753 100644 --- a/halo2-ecc/src/bn254/pairing.rs +++ b/halo2-ecc/src/bn254/pairing.rs @@ -1,5 +1,6 @@ #![allow(non_snake_case)] use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; +use crate::fields::FieldChipExt; use crate::fields::vector::FieldVector; use crate::halo2_proofs::halo2curves::bn256::{ Fq12, G1Affine, G2Affine, FROBENIUS_COEFF_FQ12_C1, SIX_U_PLUS_2_NAF, diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index b410b1e0..e970346b 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -586,6 +586,93 @@ where */ } +pub fn scalar_multiply_bits( + chip: &FC, + ctx: &mut Context, + P: EcPoint, + bits: Vec>, + max_bits: usize, + window_bits: usize, + scalar_is_safe: bool, +) -> EcPoint +where + FC: FieldChip + Selectable, +{ + assert!(!bits.is_empty()); + assert!((max_bits as u64) <= modulus::().bits()); + + let total_bits = bits.len(); + let num_windows = (total_bits + window_bits - 1) / window_bits; + let rounded_bitlen = num_windows * window_bits; + + let mut rounded_bits = bits; + let zero_cell = ctx.load_zero(); + rounded_bits.resize(rounded_bitlen, zero_cell); + + // is_started[idx] holds whether there is a 1 in bits with index at least (rounded_bitlen - idx) + let mut is_started = Vec::with_capacity(rounded_bitlen); + is_started.resize(rounded_bitlen - total_bits + 1, zero_cell); + for idx in 1..total_bits { + let or = chip.gate().or(ctx, *is_started.last().unwrap(), rounded_bits[total_bits - idx]); + is_started.push(or); + } + + // is_zero_window[idx] is 0/1 depending on whether bits [rounded_bitlen - window_bits * (idx + 1), rounded_bitlen - window_bits * idx) are all 0 + let mut is_zero_window = Vec::with_capacity(num_windows); + for idx in 0..num_windows { + let temp_bits = rounded_bits + [rounded_bitlen - window_bits * (idx + 1)..rounded_bitlen - window_bits * idx] + .iter() + .copied(); + let bit_sum = chip.gate().sum(ctx, temp_bits); + let is_zero = chip.gate().is_zero(ctx, bit_sum); + is_zero_window.push(is_zero); + } + + // cached_points[idx] stores idx * P, with cached_points[0] = P + let cache_size = 1usize << window_bits; + let mut cached_points = Vec::with_capacity(cache_size); + cached_points.push(P.clone()); + cached_points.push(P.clone()); + for idx in 2..cache_size { + if idx == 2 { + let double = ec_double(chip, ctx, &P); + cached_points.push(double); + } else { + let new_point = ec_add_unequal(chip, ctx, &cached_points[idx - 1], &P, !scalar_is_safe); + cached_points.push(new_point); + } + } + + // if all the starting window bits are 0, get start_point = P + let mut curr_point = ec_select_from_bits( + chip, + ctx, + &cached_points, + &rounded_bits[rounded_bitlen - window_bits..rounded_bitlen], + ); + + for idx in 1..num_windows { + let mut mult_point = curr_point.clone(); + for _ in 0..window_bits { + mult_point = ec_double(chip, ctx, mult_point); + } + let add_point = ec_select_from_bits( + chip, + ctx, + &cached_points, + &rounded_bits + [rounded_bitlen - window_bits * (idx + 1)..rounded_bitlen - window_bits * idx], + ); + let mult_and_add = ec_add_unequal(chip, ctx, &mult_point, &add_point, !scalar_is_safe); + let is_started_point = ec_select(chip, ctx, mult_point, mult_and_add, is_zero_window[idx]); + + curr_point = + ec_select(chip, ctx, is_started_point, add_point, is_started[window_bits * idx]); + } + curr_point +} + /// Checks that `P` is indeed a point on the elliptic curve `C`. pub fn check_is_on_curve(chip: &FC, ctx: &mut Context, P: &EcPoint) where @@ -1009,6 +1096,21 @@ impl<'chip, F: BigPrimeField, FC: FieldChip> EccChip<'chip, F, FC> { } self.sub_unequal(ctx, acc, rand_point, true) } + + // See [`scalar_mult_bits`] for more details. + pub fn scalar_mult_bits( + &self, + ctx: &mut Context, + P: EcPoint, + bits: Vec>, + window_bits: usize, + ) -> EcPoint + where + FC: Selectable, + { + let max_bits = bits.len(); + scalar_multiply_bits::(self.field_chip, ctx, P, bits, max_bits, window_bits, true) + } } impl<'chip, F: BigPrimeField, FC: FieldChip> EccChip<'chip, F, FC> diff --git a/halo2-ecc/src/fields/fp.rs b/halo2-ecc/src/fields/fp.rs index 579ac39a..18c1bdd0 100644 --- a/halo2-ecc/src/fields/fp.rs +++ b/halo2-ecc/src/fields/fp.rs @@ -1,4 +1,4 @@ -use super::{FieldChip, PrimeFieldChip, Selectable}; +use super::{FieldChip, FieldChipExt, PrimeFieldChip, Selectable}; use crate::bigint::{ add_no_carry, big_is_equal, big_is_even, big_is_zero, carry_mod, check_carry_mod_to_zero, mul_no_carry, scalar_mul_and_add_no_carry, scalar_mul_no_carry, select, select_by_indicator, @@ -482,6 +482,27 @@ impl<'range, F: BigPrimeField, Fp: BigPrimeField> Selectable } } +impl<'range, F: BigPrimeField, Fp: BigPrimeField> FieldChipExt for FpChip<'range, F, Fp> { + fn sgn0(&self, ctx: &mut Context, a: impl Into) -> AssignedValue { + let a: Self::FieldPoint = a.into(); + let range = self.range(); + let gate = range.gate(); + + let msl = a.limbs()[0]; // most significant limb + + let lsb = range.div_mod(ctx, msl, BigUint::from(256u64), self.limb_bits()).1; // get least significant *byte* + range.div_mod(ctx, lsb, BigUint::from(2u64), 8).1 // sgn0 = lsb % 2 + } + + fn conjugate( + &self, + ctx: &mut Context, + a: impl Into + ) -> Self::FieldPoint { + self.negate(ctx, a.into()) + } +} + impl Selectable> for FC where FC: Selectable, diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 8068e2f5..c7fab703 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -1,13 +1,15 @@ use std::fmt::Debug; use std::marker::PhantomData; -use crate::ff::PrimeField as _; use crate::impl_field_ext_chip_common; +use crate::{bigint::ProperCrtUint, ff::PrimeField as _}; +use super::FieldChipExt; use super::{ vector::{FieldVector, FieldVectorChip}, BigPrimeField, FieldChip, FieldExtConstructor, PrimeFieldChip, }; +use halo2_base::gates::GateInstructions; use halo2_base::{utils::modulus, AssignedValue, Context}; use num_bigint::BigUint; @@ -40,18 +42,6 @@ where self.0.fp_chip } - pub fn conjugate( - &self, - ctx: &mut Context, - a: FieldVector, - ) -> FieldVector { - let mut a = a.0; - assert_eq!(a.len(), 2); - - let neg_a1 = self.fp_chip().negate(ctx, a.pop().unwrap()); - FieldVector(vec![a.pop().unwrap(), neg_a1]) - } - pub fn neg_conjugate( &self, ctx: &mut Context, @@ -117,6 +107,71 @@ where impl_field_ext_chip_common!(); } +impl<'a, F, FpChip, Fp2> FieldChipExt for Fp2Chip<'a, F, FpChip, Fp2> +where + F: BigPrimeField, + FpChip: FieldChipExt, + FpChip::FieldType: BigPrimeField, + FpChip: PrimeFieldChip, + Fp2: crate::ff::Field + FieldExtConstructor, + FieldVector: From>, + FieldVector: From>, +{ + fn conjugate( + &self, + ctx: &mut Context, + a: impl Into + ) -> Self::FieldPoint { + let a: Self::FieldPoint = a.into(); + let mut a = a.0; + assert_eq!(a.len(), 2); + + let neg_a1 = self.fp_chip().negate(ctx, a.pop().unwrap()); + FieldVector(vec![a.pop().unwrap(), neg_a1]) + } + + fn sgn0(&self, ctx: &mut Context, a: impl Into) -> AssignedValue { + let x: Self::FieldPoint = a.into(); + let gate = self.gate(); + + let c0 = x.0[0].clone(); + let c1 = x.0[1].clone(); + + let c0_zero = self.fp_chip().is_zero(ctx, &c0); + let c0_sgn = self.fp_chip().sgn0(ctx, c0); + let c1_sgn = self.fp_chip().sgn0(ctx, c1); + let sgn = gate.select(ctx, c1_sgn, c0_sgn, c0_zero); + gate.assert_bit(ctx, sgn); + sgn + } +} + +impl<'range, F: BigPrimeField, FC: PrimeFieldChip, Fp> + super::Selectable>> for Fp2Chip<'range, F, FC, Fp> +where + FC: super::Selectable>, + >::FieldType: BigPrimeField, +{ + fn select( + &self, + ctx: &mut Context, + a: FieldVector>, + b: FieldVector>, + sel: AssignedValue, + ) -> FieldVector> { + self.0.select(ctx, a, b, sel) + } + + fn select_by_indicator( + &self, + ctx: &mut Context, + a: &impl AsRef<[FieldVector>]>, + coeffs: &[AssignedValue], + ) -> FieldVector> { + self.0.select_by_indicator(ctx, a, coeffs) + } +} + mod bn254 { use crate::fields::FieldExtConstructor; use crate::halo2_proofs::halo2curves::bn256::{Fq, Fq2}; diff --git a/halo2-ecc/src/fields/mod.rs b/halo2-ecc/src/fields/mod.rs index 10e1ae6f..5d6b7152 100644 --- a/halo2-ecc/src/fields/mod.rs +++ b/halo2-ecc/src/fields/mod.rs @@ -283,6 +283,42 @@ pub trait FieldChip: Clone + Send + Sync { } } +pub trait FieldChipExt: FieldChip { + fn add( + &self, + ctx: &mut Context, + a: impl Into, + b: impl Into, + ) -> Self::FieldPoint { + let no_carry = self.add_no_carry(ctx, a, b); + self.carry_mod(ctx, no_carry) + } + + fn sub( + &self, + ctx: &mut Context, + a: impl Into, + b: impl Into, + ) -> Self::FieldPoint { + let no_carry = self.sub_no_carry(ctx, a, b); + self.carry_mod(ctx, no_carry) + } + + fn conjugate( + &self, + ctx: &mut Context, + a: impl Into, + ) -> Self::FieldPoint; + + /// This function returns either 0 or 1 indicating the "sign" of x, where sgn0(x) == 1 just when x is "negative". + /// (In other words, this function always considers 0 to be positive.) + fn sgn0( + &self, + ctx: &mut Context, + a: impl Into, + ) -> AssignedValue; +} + pub trait Selectable { fn select(&self, ctx: &mut Context, a: Pt, b: Pt, sel: AssignedValue) -> Pt; diff --git a/halo2-ecc/src/fields/vector.rs b/halo2-ecc/src/fields/vector.rs index d27dc25f..bfaf8366 100644 --- a/halo2-ecc/src/fields/vector.rs +++ b/halo2-ecc/src/fields/vector.rs @@ -348,6 +348,32 @@ where self.fp_chip.assert_equal(ctx, a_coeff, b_coeff) } } + + pub fn select_by_indicator( + &self, + ctx: &mut Context, + v: &impl AsRef<[FieldVector]>, + coeffs: &[AssignedValue], + ) -> FieldVector + where + FpChip: Selectable, + { + let v = v.as_ref().to_vec(); + let len = v[0].0.len(); + let mut iters = v.into_iter().map(|n| n.into_iter()).collect_vec(); + let v_transpoed = (0..len) + .map(|_| { + iters + .iter_mut() + .map(|n| n.next().unwrap()) + .collect_vec() + }); + + + FieldVector( + v_transpoed.map(|x| self.fp_chip.select_by_indicator(ctx, &x, coeffs)).collect(), + ) + } } #[macro_export] From a7ed71b1774e450062185d55e72dfabd6e4091df Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 6 Oct 2023 20:51:23 +0200 Subject: [PATCH 13/51] refactor --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 71 ------------------------ halo2-ecc/src/bls12_381/utils.rs | 9 ++- 2 files changed, 4 insertions(+), 76 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 403c22f9..efa1b15c 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -881,74 +881,3 @@ mod bls12_381 { } } -// #[cfg(test)] -// mod test { -// use std::env::var; -// use std::vec; -// use std::{cell::RefCell, marker::PhantomData}; - -// use crate::gadget::crypto::sha256_flex::{SpreadChip, SpreadConfig}; -// use crate::gadget::crypto::ShaCircuitBuilder; -// use crate::gadget::crypto::{Sha256Chip, ShaThreadBuilder}; -// use crate::util::{print_fq2_dev, Challenges, IntoWitness}; - -// use super::*; -// use eth_types::{Mainnet, Testnet}; -// use halo2_base::gates::builder::FlexGateConfigParams; -// use halo2_base::gates::range::RangeConfig; -// use halo2_base::safe_types::RangeChip; -// use halo2_base::SKIP_FIRST_PASS; -// use halo2_base::{ -// gates::{builder::GateThreadBuilder, range::RangeStrategy}, -// halo2_proofs::{ -// circuit::{Layouter, SimpleFloorPlanner}, -// dev::MockProver, -// halo2curves::bn256::Fr, -// plonk::{Circuit, ConstraintSystem}, -// }, -// }; -// use halo2_ecc::bigint::CRTInteger; -// use halo2_proofs::circuit::Value; -// use halo2curves::bls12_381::G2; -// use sha2::{Digest, Sha256}; - -// fn get_circuit( -// k: usize, -// mut builder: ShaThreadBuilder, -// input_vector: &[Vec], -// ) -> Result>, Error> { -// let range = RangeChip::default(8); -// let sha256 = Sha256Chip::new(&range); - -// let h2c_chip = HashToCurveChip::::new(&sha256); -// let fp_chip = halo2_ecc::bls12_381::FpChip::::new(&range, G2::LIMB_BITS, G2::NUM_LIMBS); - -// for input in input_vector { -// let mut cache = HashToCurveCache::::default(); -// let hp = h2c_chip.hash_to_curve::( -// &mut builder, -// &fp_chip, -// input.clone().into_witness(), -// &mut cache, -// )?; - -// print_fq2_dev::(hp.x(), "res_p.x"); -// print_fq2_dev::(hp.y(), "res_p.y"); -// } - -// builder.config(k, None); -// Ok(ShaCircuitBuilder::mock(builder)) -// } - -// #[test] -// fn test_hash_to_g2() { -// let k = 17; - -// let test_input = vec![0u8; 32]; -// let builder = ShaThreadBuilder::::mock(); -// let circuit = get_circuit(k, builder, &[test_input]).unwrap(); - -// let prover = MockProver::run(k as u32, &circuit, vec![]).unwrap(); -// prover.assert_satisfied(); -// } -// } diff --git a/halo2-ecc/src/bls12_381/utils.rs b/halo2-ecc/src/bls12_381/utils.rs index 1b7904a2..71ff8cec 100644 --- a/halo2-ecc/src/bls12_381/utils.rs +++ b/halo2-ecc/src/bls12_381/utils.rs @@ -26,7 +26,7 @@ pub fn strxor( gate: &impl GateInstructions, ctx: &mut Context, ) -> Vec> { - a.into_iter().zip(b.into_iter()).map(|(a, b)| bitwise_xor::<_, 8>(a, b, gate, ctx)).collect() + a.into_iter().zip(b).map(|(a, b)| bitwise_xor::<_, 8>(a, b, gate, ctx)).collect() } pub fn bitwise_xor( @@ -35,12 +35,11 @@ pub fn bitwise_xor( gate: &impl GateInstructions, ctx: &mut Context, ) -> AssignedValue { - let one = ctx.load_constant(F::ONE); - let mut a_bits = gate.num_to_bits(ctx, a, BITS); - let mut b_bits = gate.num_to_bits(ctx, b, BITS); + let a_bits = gate.num_to_bits(ctx, a, BITS); + let b_bits = gate.num_to_bits(ctx, b, BITS); let xor_bits = - a_bits.into_iter().zip(b_bits.into_iter()).map(|(a, b)| gate.xor(ctx, a, b)).collect_vec(); + a_bits.into_iter().zip(b_bits).map(|(a, b)| gate.xor(ctx, a, b)).collect_vec(); bits_to_num(gate, ctx, xor_bits) } From 43a464a218e7db1e4726bc530dd22dfb2527ae73 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 6 Oct 2023 21:27:15 +0200 Subject: [PATCH 14/51] add test --- halo2-ecc/Cargo.toml | 1 + .../bls12_381/hash_to_curve_circuit.config | 1 + halo2-ecc/src/bls12_381/hash_to_curve.rs | 10 +- .../src/bls12_381/tests/hash_to_curve.rs | 107 ++++++++++++++++++ halo2-ecc/src/bls12_381/tests/mod.rs | 1 + 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config create mode 100644 halo2-ecc/src/bls12_381/tests/hash_to_curve.rs diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index d3da45fc..0d2b335d 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -27,6 +27,7 @@ halo2-base={ path="../halo2-base", default-features=false, features=["test-utils test-log="0.2.12" env_logger="0.10.0" pairing="0.23.0" +sha2="0.10.6" [features] default=["jemallocator", "halo2-axiom", "display"] diff --git a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config new file mode 100644 index 00000000..09d9a232 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config @@ -0,0 +1 @@ +{"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":30} diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index efa1b15c..c1283a51 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -13,7 +13,7 @@ use crate::{ ecc::EccChip, fields::{vector::FieldVector, FieldChip, Selectable}, }; -use halo2_base::gates::{GateInstructions, RangeChip, RangeInstructions}; +use halo2_base::gates::{GateInstructions, RangeInstructions}; use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; use halo2_base::halo2_proofs::halo2curves::CurveExt; use halo2_base::utils::BigPrimeField; @@ -37,8 +37,6 @@ pub trait HashInstructions { input: impl Iterator>, strict: bool, ) -> Result, Error>; - - fn range(&self) -> &RangeChip; } pub trait HashEccChip, C: HashCurveExt> @@ -328,7 +326,7 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> dst: impl AsRef<[u8]>, ) -> Result<[Fp2Point; 2], Error> { let fp_chip = self.field_chip().fp_chip(); - let range = hash_chip.range(); + let range = fp_chip.range(); let gate = range.gate(); // constants @@ -344,7 +342,7 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> .collect_vec(); let len_in_bytes = 2 * G2_EXT_DEGREE * L; - let extended_msg = expand_message_xmd(ctx, hash_chip, assigned_msg, len_in_bytes, dst)?; + let extended_msg = expand_message_xmd(ctx, hash_chip, range, assigned_msg, len_in_bytes, dst)?; // 2^256 let two_pow_256 = fp_chip.load_constant_uint(ctx, BigUint::from(2u8).pow(256)); @@ -478,11 +476,11 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> fn expand_message_xmd>>( ctx: &mut HC::ThreadBuidler, hash_chip: &HC, + range: &impl RangeInstructions, msg: Vec>, len_in_bytes: usize, dst: impl AsRef<[u8]>, ) -> Result>, Error> { - let range = hash_chip.range(); let gate = range.gate(); let zero = ctx.load_zero(); diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs new file mode 100644 index 00000000..bb993eec --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -0,0 +1,107 @@ +use std::{fs::File, marker::PhantomData}; + +use super::*; +use crate::{ + bls12_381::hash_to_curve::{AssignedHashResult, HashInstructions, HashToCurveChip}, + fields::{FpStrategy, FieldChip}, +}; +use halo2_base::{ + gates::RangeChip, halo2_proofs::plonk::Error, utils::BigPrimeField, Context, QuantumCell, +}; +extern crate pairing; +use itertools::Itertools; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct HashToCurveCircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, +} + +#[derive(Clone, Copy, Debug, Default)] +struct Sha256MockChip(PhantomData); + +impl HashInstructions for Sha256MockChip { + const BLOCK_SIZE: usize = 64; + const DIGEST_SIZE: usize = 32; + + type ThreadBuidler = Context; + + fn digest( + &self, + ctx: &mut Self::ThreadBuidler, + input: impl Iterator>, + _strict: bool, + ) -> Result, Error> { + use sha2::{Digest, Sha256}; + let input_bytes = input + .map(|b| match b { + QuantumCell::Witness(b) => b.get_lower_32() as u8, + QuantumCell::Constant(b) => b.get_lower_32() as u8, + QuantumCell::Existing(av) => av.value().get_lower_32() as u8, + _ => unreachable!(), + }) + .collect_vec(); + + let output_bytes = Sha256::digest(input_bytes) + .into_iter() + .map(|b| ctx.load_witness(F::from(b as u64))) + .collect_vec() + .try_into() + .unwrap(); + Ok(AssignedHashResult { input_bytes: vec![], output_bytes }) + } +} + +fn hash_to_g2_test( + ctx: &mut Context, + range: &RangeChip, + params: HashToCurveCircuitParams, + msg: Vec, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let fp2_chip = Fp2Chip::new(&fp_chip); + + let sha256 = Sha256MockChip::::default(); + + let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); + + let hp = h2c_chip + .hash_to_curve( + ctx, + msg.into_iter().map(|b| QuantumCell::Witness(F::from(b as u64))), + b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_", + ) + .unwrap(); + + println!( + "msghash: {:?}", + (fp2_chip.get_assigned_value(&hp.x.into()), fp2_chip.get_assigned_value(&hp.y.into())) + ); + + // Verify off-circuit + + // Compare the 2 results +} + +#[test] +fn test_hash_to_g2() { + let run_path = "configs/bls12_381/hash_to_curve_circuit.config"; + let path = run_path; + let params: HashToCurveCircuitParams = 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 test_input = vec![0u8; 32]; + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + hash_to_g2_test(ctx, range, params, test_input); + }) +} diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index ce937258..c52e7529 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -16,3 +16,4 @@ use std::io::Write; pub mod bls_signature; pub mod ec_add; pub mod pairing; +pub mod hash_to_curve; From bd6d76e0ce202a6bf667786a6384d1a45dc4b9e9 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 17:08:48 +0200 Subject: [PATCH 15/51] expand message chip trait --- .../bls12_381/hash_to_curve_circuit.config | 2 +- halo2-ecc/src/bls12_381/hash_to_curve.rs | 190 ++++++++++-------- .../src/bls12_381/tests/hash_to_curve.rs | 4 +- 3 files changed, 105 insertions(+), 91 deletions(-) diff --git a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config index 09d9a232..308d058b 100644 --- a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config +++ b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config @@ -1 +1 @@ -{"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":30} +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":112,"num_limbs":4,"num_aggregation":30} diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index c1283a51..e6986d98 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -54,12 +54,12 @@ where window_bits: usize, ) -> EcPoint; - fn hash_to_field>>( + fn hash_to_field>, XC: ExpandMessageChip>( &self, ctx: &mut HC::ThreadBuidler, hash_chip: &HC, msg: impl Iterator>, - dst: impl AsRef<[u8]>, + dst: &[u8], ) -> Result<[FC::FieldPoint; 2], Error>; fn isogeny_map( @@ -158,13 +158,13 @@ where Self { hash_chip, ecc_chip: EccChip::new(field_chip), _curve: PhantomData } } - pub fn hash_to_curve( + pub fn hash_to_curve( &self, ctx: &mut HC::ThreadBuidler, msg: impl Iterator>, - dst: impl AsRef<[u8]>, + dst: &[u8], ) -> Result, Error> { - let u = self.ecc_chip.hash_to_field(ctx, self.hash_chip, msg, dst)?; + let u = self.ecc_chip.hash_to_field::<_, XC>(ctx, self.hash_chip, msg, dst)?; let p = self.map_to_curve(ctx, u)?; Ok(p) } @@ -318,12 +318,12 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L49 /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L128 /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L11 - fn hash_to_field>>( + fn hash_to_field>, XC: ExpandMessageChip>( &self, ctx: &mut HC::ThreadBuidler, hash_chip: &HC, msg: impl Iterator>, - dst: impl AsRef<[u8]>, + dst: &[u8], ) -> Result<[Fp2Point; 2], Error> { let fp_chip = self.field_chip().fp_chip(); let range = fp_chip.range(); @@ -332,17 +332,8 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> // constants let zero = ctx.load_zero(); - let assigned_msg = msg - .map(|cell| match cell { - QuantumCell::Existing(v) => v, - QuantumCell::Witness(v) => ctx.load_witness(v), - QuantumCell::Constant(v) => ctx.load_constant(v), - _ => unreachable!(), - }) - .collect_vec(); - - let len_in_bytes = 2 * G2_EXT_DEGREE * L; - let extended_msg = expand_message_xmd(ctx, hash_chip, range, assigned_msg, len_in_bytes, dst)?; + let extended_msg = + XC::expand_message(ctx, hash_chip, range, msg, dst, 2 * G2_EXT_DEGREE * L)?; // 2^256 let two_pow_256 = fp_chip.load_constant_uint(ctx, BigUint::from(2u8).pow(256)); @@ -465,80 +456,104 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> } } -/// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. -/// -/// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3 -/// -/// References: -/// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 -/// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 -/// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 -fn expand_message_xmd>>( - ctx: &mut HC::ThreadBuidler, - hash_chip: &HC, - range: &impl RangeInstructions, - msg: Vec>, - len_in_bytes: usize, - dst: impl AsRef<[u8]>, -) -> Result>, Error> { - let gate = range.gate(); - - let zero = ctx.load_zero(); - let one = ctx.load_constant(F::ONE); - - // assign DST bytes & cache them - let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); - let dst_prime = dst - .as_ref() - .iter() - .map(|&b| ctx.load_constant(F::from(b as u64))) - .chain(iter::once(dst_len)) - .collect_vec(); - - // padding and length strings - let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); - let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); - - // compute blocks - let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; - let mut b_vals = Vec::with_capacity(ell); - let msg_prime = z_pad - .into_iter() - .chain(msg) - .chain(l_i_b_str) - .chain(iter::once(zero)) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing); - - let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; - - b_vals.insert( - 0, - hash_chip - .digest::<77>( - ctx, - b_0.into_iter() - .chain(iter::once(one)) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing), - false, - )? - .output_bytes, - ); - - for i in 1..ell { - let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) +pub trait ExpandMessageChip { + fn expand_message>>( + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + len_in_bytes: usize, + ) -> Result>, Error>; +} + +pub struct ExpandMsgXmd; + +impl ExpandMessageChip for ExpandMsgXmd { + /// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. + /// + /// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3 + /// + /// References: + /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 + /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 + fn expand_message>>( + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + len_in_bytes: usize, + ) -> Result>, Error> { + let gate = range.gate(); + + let zero = ctx.load_zero(); + let one = ctx.load_constant(F::ONE); + + // assign DST bytes & cache them + let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); + let dst_prime = dst + .as_ref() + .iter() + .map(|&b| ctx.load_constant(F::from(b as u64))) + .chain(iter::once(dst_len)) + .collect_vec(); + + // padding and length strings + let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); + let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); + + let assigned_msg = msg + .map(|cell| match cell { + QuantumCell::Existing(v) => v, + QuantumCell::Witness(v) => ctx.load_witness(v), + QuantumCell::Constant(v) => ctx.load_constant(v), + _ => unreachable!(), + }) + .collect_vec(); + + // compute blocks + let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; + let mut b_vals = Vec::with_capacity(ell); + let msg_prime = z_pad .into_iter() - .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) + .chain(assigned_msg) + .chain(l_i_b_str) + .chain(iter::once(zero)) .chain(dst_prime.clone()) .map(QuantumCell::Existing); - b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); + let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; + + b_vals.insert( + 0, + hash_chip + .digest::<77>( + ctx, + b_0.into_iter() + .chain(iter::once(one)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing), + false, + )? + .output_bytes, + ); + + for i in 1..ell { + let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) + .into_iter() + .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); + + b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); + } + + let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); + + Ok(uniform_bytes) } - - let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); - - Ok(uniform_bytes) } pub trait HashCurveExt: CurveExt @@ -878,4 +893,3 @@ mod bls12_381 { }; } } - diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index bb993eec..81b5988b 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -2,7 +2,7 @@ use std::{fs::File, marker::PhantomData}; use super::*; use crate::{ - bls12_381::hash_to_curve::{AssignedHashResult, HashInstructions, HashToCurveChip}, + bls12_381::hash_to_curve::{AssignedHashResult, HashInstructions, HashToCurveChip, ExpandMsgXmd}, fields::{FpStrategy, FieldChip}, }; use halo2_base::{ @@ -72,7 +72,7 @@ fn hash_to_g2_test( let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); let hp = h2c_chip - .hash_to_curve( + .hash_to_curve::( ctx, msg.into_iter().map(|b| QuantumCell::Witness(F::from(b as u64))), b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_", From 7d067d06ed3e40bb1e4345272af1b1edab2e8893 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 17:29:32 +0200 Subject: [PATCH 16/51] move traits and general impls to ecc module --- halo2-base/src/gates/flex_gate/mod.rs | 32 +++ halo2-ecc/src/bigint/mod.rs | 1 + halo2-ecc/src/bigint/utils.rs | 37 +++ halo2-ecc/src/bls12_381/hash_to_curve.rs | 245 +--------------- halo2-ecc/src/bls12_381/mod.rs | 1 - .../src/bls12_381/tests/hash_to_curve.rs | 4 +- halo2-ecc/src/bls12_381/utils.rs | 105 ------- halo2-ecc/src/ecc/hash_to_curve.rs | 265 ++++++++++++++++++ halo2-ecc/src/ecc/mod.rs | 1 + 9 files changed, 346 insertions(+), 345 deletions(-) create mode 100644 halo2-ecc/src/bigint/utils.rs delete mode 100644 halo2-ecc/src/bls12_381/utils.rs create mode 100644 halo2-ecc/src/ecc/hash_to_curve.rs diff --git a/halo2-base/src/gates/flex_gate/mod.rs b/halo2-base/src/gates/flex_gate/mod.rs index 2938381b..d6a315ab 100644 --- a/halo2-base/src/gates/flex_gate/mod.rs +++ b/halo2-base/src/gates/flex_gate/mod.rs @@ -595,6 +595,22 @@ pub trait GateInstructions { ctx.last().unwrap() } + /// Constrains and returns `a ^ b`. + fn bitwise_xor( + &self, + ctx: &mut Context, + a: AssignedValue, + b: AssignedValue, + ) -> AssignedValue { + let a_bits = self.num_to_bits(ctx, a, BITS); + let b_bits = self.num_to_bits(ctx, b, BITS); + + let xor_bits = + a_bits.into_iter().zip(b_bits).map(|(a, b)| self.xor(ctx, a, b)).collect_vec(); + + self.bits_to_num(ctx, xor_bits) + } + /// Constrains and returns `!a` assumeing `a` is boolean. /// /// Defines a vertical gate of form | 1 - a | a | 1 | 1 |, where 1 - a = out. @@ -868,6 +884,22 @@ pub trait GateInstructions { range_bits: usize, ) -> Vec>; + /// Constrains and returns the number represented by the little-endian bit vector `bits`. + fn bits_to_num>>( + &self, + ctx: &mut Context, + bits: I, + ) -> AssignedValue + where + I::IntoIter: DoubleEndedIterator + ExactSizeIterator, + { + let bits_iter = bits.into_iter(); + assert!(bits_iter.len() <= F::NUM_BITS as usize); + bits_iter.rev().fold(ctx.load_zero(), |acc, bit| { + self.mul_add(ctx, acc, QuantumCell::Constant(F::from(2u64)), bit) + }) + } + /// Constrains and computes `a``exp` where both `a, exp` are witnesses. The exponent is computed in the native field `F`. /// /// Constrains that `exp` has at most `max_bits` bits. diff --git a/halo2-ecc/src/bigint/mod.rs b/halo2-ecc/src/bigint/mod.rs index 37c32ecf..09780efe 100644 --- a/halo2-ecc/src/bigint/mod.rs +++ b/halo2-ecc/src/bigint/mod.rs @@ -23,6 +23,7 @@ pub mod select; pub mod select_by_indicator; pub mod sub; pub mod sub_no_carry; +pub mod utils; #[derive(Clone, Debug, PartialEq, Default)] pub enum BigIntStrategy { diff --git a/halo2-ecc/src/bigint/utils.rs b/halo2-ecc/src/bigint/utils.rs new file mode 100644 index 00000000..a25f9669 --- /dev/null +++ b/halo2-ecc/src/bigint/utils.rs @@ -0,0 +1,37 @@ +use halo2_base::{utils::BigPrimeField, Context, gates::GateInstructions, AssignedValue, QuantumCell}; +use itertools::Itertools; +use num_bigint::BigUint; + +use super::{ProperCrtUint, ProperUint}; + + +/// Converts assigned bytes in little-endian into biginterger +/// Warning: method does not perform any checks on input `bytes`. +pub fn decode_into_bn( + ctx: &mut Context, + gate: &impl GateInstructions, + bytes: Vec>, + limb_bases: &[F], + limb_bits: usize, +) -> ProperCrtUint { + let limb_bytes = limb_bits / 8; + let bits = limb_bases.len() * limb_bits; + + let value = + BigUint::from_bytes_le(&bytes.iter().map(|v| v.value().get_lower_32() as u8).collect_vec()); + + // inputs is a bool or uint8. + let assigned_uint = if bits == 1 || limb_bytes == 8 { + ProperUint(bytes) + } else { + let byte_base = + (0..limb_bytes).map(|i| QuantumCell::Constant(gate.pow_of_two()[i * 8])).collect_vec(); + let limbs = bytes + .chunks(limb_bytes) + .map(|chunk| gate.inner_product(ctx, chunk.to_vec(), byte_base[..chunk.len()].to_vec())) + .collect::>(); + ProperUint(limbs) + }; + + assigned_uint.into_crt(ctx, gate, value, limb_bases, limb_bits) +} diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index e6986d98..eef4ad98 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -1,9 +1,10 @@ //! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` //! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 -use std::{iter, marker::PhantomData}; +use std::marker::PhantomData; -use super::utils::*; +use crate::bigint::utils::decode_into_bn; +use crate::ecc::hash_to_curve::{ExpandMessageChip, HashCurveExt, HashEccChip, HashInstructions}; use crate::ecc::EcPoint; use crate::ff::Field; use crate::fields::FieldChipExt; @@ -15,120 +16,12 @@ use crate::{ }; use halo2_base::gates::{GateInstructions, RangeInstructions}; use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; -use halo2_base::halo2_proofs::halo2curves::CurveExt; use halo2_base::utils::BigPrimeField; use itertools::Itertools; use num_bigint::BigUint; use super::{Fp2Chip, Fp2Point, G2Point}; -pub trait HashInstructions { - const BLOCK_SIZE: usize; - const DIGEST_SIZE: usize; - - type ThreadBuidler; - - /// Digests input using hash function and returns finilized output. - /// `MAX_INPUT_SIZE` is the maximum size of input that can be processed by the hash function. - /// `strict` flag indicates whether to perform range check on input bytes. - fn digest( - &self, - ctx: &mut Self::ThreadBuidler, - input: impl Iterator>, - strict: bool, - ) -> Result, Error>; -} - -pub trait HashEccChip, C: HashCurveExt> -where - FC::FieldType: crate::ff::PrimeField, - FC: Selectable, -{ - fn field_chip(&self) -> &FC; - - fn scalar_mult_bits( - &self, - ctx: &mut Context, - p: EcPoint, - bits: Vec>, - window_bits: usize, - ) -> EcPoint; - - fn hash_to_field>, XC: ExpandMessageChip>( - &self, - ctx: &mut HC::ThreadBuidler, - hash_chip: &HC, - msg: impl Iterator>, - dst: &[u8], - ) -> Result<[FC::FieldPoint; 2], Error>; - - fn isogeny_map( - &self, - ctx: &mut Context, - p: EcPoint, - ) -> EcPoint; - - fn clear_cofactor( - &self, - ctx: &mut Context, - p: EcPoint, - ) -> EcPoint; - - fn mul_by_bls_x( - &self, - ctx: &mut Context, - p: EcPoint, - ) -> EcPoint { - let bls_x_bits = (0..64) - .map(|i| ((C::BLS_X >> i) & 1) as u8) - .map(|b| ctx.load_constant(F::from(b as u64))) - .collect_vec(); - - self.scalar_mult_bits(ctx, p, bls_x_bits, 4) - } - - fn psi( - &self, - ctx: &mut Context, - p: EcPoint, - ) -> EcPoint { - // 1 / ((u+1) ^ ((q-1)/3)) - let psi_x = self.field_chip().load_constant(ctx, C::PSI_X); - - // 1 / ((u+1) ^ (p-1)/2) - let psi_y = self.field_chip().load_constant(ctx, C::PSI_Y); - - let x_frob = self.field_chip().conjugate(ctx, p.x); - let y_frob = self.field_chip().conjugate(ctx, p.y); - - let x = self.field_chip().mul(ctx, x_frob, psi_x.clone()); - let y = self.field_chip().mul(ctx, y_frob, psi_y.clone()); - - EcPoint::new(x, y) - } - - fn psi2( - &self, - ctx: &mut Context, - p: EcPoint, - ) -> EcPoint { - // 1 / 2 ^ ((q-1)/3) - let psi2_x = self.field_chip().load_constant(ctx, C::PSI2_X); - - let x = self.field_chip().mul(ctx, p.x, psi2_x.clone()); - let y = self.field_chip().negate(ctx, p.y); - - EcPoint::new(x, y) - } -} - -#[derive(Debug, Clone)] -pub struct AssignedHashResult { - // pub input_len: AssignedValue, - pub input_bytes: Vec>, - pub output_bytes: [AssignedValue; 32], -} - #[derive(Debug)] pub struct HashToCurveChip< 'chip, @@ -288,8 +181,8 @@ where } } -const G2_EXT_DEGREE: usize = 2; +const G2_EXT_DEGREE: usize = 2; // L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) const L: usize = 64; @@ -350,19 +243,19 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> let mut buf = vec![zero; fq_bytes]; let rem = fq_bytes - 32; buf[rem..].copy_from_slice(&tv[..32]); - let lo = decode_into_field_be::( + let lo = decode_into_bn::( ctx, gate, - buf.to_vec(), + buf.iter().copied().rev().collect_vec(), &fp_chip.limb_bases, fp_chip.limb_bits(), ); buf[rem..].copy_from_slice(&tv[32..]); - let hi = decode_into_field_be::( + let hi = decode_into_bn::( ctx, gate, - buf.to_vec(), + buf.into_iter().rev().collect_vec(), &fp_chip.limb_bases, fp_chip.limb_bits(), ); @@ -456,128 +349,6 @@ impl<'chip, F: BigPrimeField> HashEccChip, G2> } } -pub trait ExpandMessageChip { - fn expand_message>>( - ctx: &mut HC::ThreadBuidler, - hash_chip: &HC, - range: &impl RangeInstructions, - msg: impl Iterator>, - dst: &[u8], - len_in_bytes: usize, - ) -> Result>, Error>; -} - -pub struct ExpandMsgXmd; - -impl ExpandMessageChip for ExpandMsgXmd { - /// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. - /// - /// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3 - /// - /// References: - /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 - /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 - /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 - fn expand_message>>( - ctx: &mut HC::ThreadBuidler, - hash_chip: &HC, - range: &impl RangeInstructions, - msg: impl Iterator>, - dst: &[u8], - len_in_bytes: usize, - ) -> Result>, Error> { - let gate = range.gate(); - - let zero = ctx.load_zero(); - let one = ctx.load_constant(F::ONE); - - // assign DST bytes & cache them - let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); - let dst_prime = dst - .as_ref() - .iter() - .map(|&b| ctx.load_constant(F::from(b as u64))) - .chain(iter::once(dst_len)) - .collect_vec(); - - // padding and length strings - let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); - let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); - - let assigned_msg = msg - .map(|cell| match cell { - QuantumCell::Existing(v) => v, - QuantumCell::Witness(v) => ctx.load_witness(v), - QuantumCell::Constant(v) => ctx.load_constant(v), - _ => unreachable!(), - }) - .collect_vec(); - - // compute blocks - let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; - let mut b_vals = Vec::with_capacity(ell); - let msg_prime = z_pad - .into_iter() - .chain(assigned_msg) - .chain(l_i_b_str) - .chain(iter::once(zero)) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing); - - let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; - - b_vals.insert( - 0, - hash_chip - .digest::<77>( - ctx, - b_0.into_iter() - .chain(iter::once(one)) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing), - false, - )? - .output_bytes, - ); - - for i in 1..ell { - let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) - .into_iter() - .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing); - - b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); - } - - let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); - - Ok(uniform_bytes) - } -} - -pub trait HashCurveExt: CurveExt -where - Self::Base: crate::ff::PrimeField, -{ - type Fp: crate::ff::PrimeField + crate::ff::WithSmallOrderMulGroup<3>; - - const BLS_X: u64; - - const SWU_A: Self::Base; - const SWU_B: Self::Base; - const SWU_Z: Self::Base; - - const ISO_XNUM: [Self::Base; 4]; - const ISO_XDEN: [Self::Base; 3]; - const ISO_YNUM: [Self::Base; 4]; - const ISO_YDEN: [Self::Base; 4]; - - const PSI_X: Self::Base; - const PSI_Y: Self::Base; - const PSI2_X: Self::Base; -} - mod bls12_381 { use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq, G2}; diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index 013bf948..3b4da817 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -8,7 +8,6 @@ pub mod bls_signature; pub mod final_exp; pub mod hash_to_curve; pub mod pairing; -pub(crate) mod utils; pub(crate) const XI_0: i64 = 1; diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 81b5988b..433201c8 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -2,8 +2,8 @@ use std::{fs::File, marker::PhantomData}; use super::*; use crate::{ - bls12_381::hash_to_curve::{AssignedHashResult, HashInstructions, HashToCurveChip, ExpandMsgXmd}, - fields::{FpStrategy, FieldChip}, + bls12_381::hash_to_curve::HashToCurveChip, + fields::{FieldChip, FpStrategy}, ecc::hash_to_curve::{AssignedHashResult, HashInstructions, ExpandMsgXmd}, }; use halo2_base::{ gates::RangeChip, halo2_proofs::plonk::Error, utils::BigPrimeField, Context, QuantumCell, diff --git a/halo2-ecc/src/bls12_381/utils.rs b/halo2-ecc/src/bls12_381/utils.rs deleted file mode 100644 index 71ff8cec..00000000 --- a/halo2-ecc/src/bls12_381/utils.rs +++ /dev/null @@ -1,105 +0,0 @@ -use halo2_base::{ - gates::GateInstructions, utils::BigPrimeField, AssignedValue, Context, QuantumCell, -}; -use itertools::Itertools; -use num_bigint::BigUint; - -use crate::bigint::{ProperCrtUint, ProperUint}; - -/// Integer to Octet Stream (numberToBytesBE) -pub fn i2osp( - mut value: u128, - length: usize, - mut f: impl FnMut(F) -> AssignedValue, -) -> Vec> { - let mut octet_string = vec![0; length]; - for i in (0..length).rev() { - octet_string[i] = value & 0xff; - value >>= 8; - } - octet_string.into_iter().map(|b| f(F::from(b as u64))).collect() -} - -pub fn strxor( - a: impl IntoIterator>, - b: impl IntoIterator>, - gate: &impl GateInstructions, - ctx: &mut Context, -) -> Vec> { - a.into_iter().zip(b).map(|(a, b)| bitwise_xor::<_, 8>(a, b, gate, ctx)).collect() -} - -pub fn bitwise_xor( - a: AssignedValue, - b: AssignedValue, - gate: &impl GateInstructions, - ctx: &mut Context, -) -> AssignedValue { - let a_bits = gate.num_to_bits(ctx, a, BITS); - let b_bits = gate.num_to_bits(ctx, b, BITS); - - let xor_bits = - a_bits.into_iter().zip(b_bits).map(|(a, b)| gate.xor(ctx, a, b)).collect_vec(); - - bits_to_num(gate, ctx, xor_bits) -} - -pub fn bits_to_num>>( - gate: &impl GateInstructions, - ctx: &mut Context, - bits: I, -) -> AssignedValue -where - I::IntoIter: DoubleEndedIterator + ExactSizeIterator, -{ - let bits_iter = bits.into_iter(); - assert!(bits_iter.len() <= F::NUM_BITS as usize); - bits_iter.rev().fold(ctx.load_zero(), |acc, bit| { - gate.mul_add(ctx, acc, QuantumCell::Constant(F::from(2u64)), bit) - }) -} - -/// Converts assigned bytes into biginterger -/// Warning: method does not perform any checks on input `bytes`. -pub fn decode_into_field( - ctx: &mut Context, - gate: &impl GateInstructions, - bytes: Vec>, - limb_bases: &[F], - limb_bits: usize, -) -> ProperCrtUint { - let limb_bytes = limb_bits / 8; - let bits = limb_bases.len() * limb_bits; - - let value = - BigUint::from_bytes_le(&bytes.iter().map(|v| v.value().get_lower_32() as u8).collect_vec()); - - // inputs is a bool or uint8. - let assigned_uint = if bits == 1 || limb_bytes == 8 { - ProperUint(bytes) - } else { - let byte_base = - (0..limb_bytes).map(|i| QuantumCell::Constant(gate.pow_of_two()[i * 8])).collect_vec(); - let limbs = bytes - .chunks(limb_bytes) - .map(|chunk| gate.inner_product(ctx, chunk.to_vec(), byte_base[..chunk.len()].to_vec())) - .collect::>(); - ProperUint(limbs) - }; - - assigned_uint.into_crt(ctx, gate, value, limb_bases, limb_bits) -} - -pub fn decode_into_field_be>>( - ctx: &mut Context, - gate: &impl GateInstructions, - bytes: I, - limb_bases: &[F], - limb_bits: usize, -) -> ProperCrtUint -where - I::IntoIter: DoubleEndedIterator, -{ - let bytes = bytes.into_iter().rev().collect_vec(); - decode_into_field::(ctx, gate, bytes, limb_bases, limb_bits) -} diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs new file mode 100644 index 00000000..c1508f4e --- /dev/null +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -0,0 +1,265 @@ +use std::iter; + +use halo2_base::{ + gates::{GateInstructions, RangeInstructions}, + halo2_proofs::{halo2curves::CurveExt, plonk::Error}, + utils::BigPrimeField, + AssignedValue, Context, QuantumCell, +}; +use itertools::Itertools; + +use crate::fields::{FieldChipExt, Selectable}; + +use super::EcPoint; + +#[derive(Debug, Clone)] +pub struct AssignedHashResult { + // pub input_len: AssignedValue, + pub input_bytes: Vec>, + pub output_bytes: [AssignedValue; 32], +} + +pub trait HashInstructions { + const BLOCK_SIZE: usize; + const DIGEST_SIZE: usize; + + type ThreadBuidler; + + /// Digests input using hash function and returns finilized output. + /// `MAX_INPUT_SIZE` is the maximum size of input that can be processed by the hash function. + /// `strict` flag indicates whether to perform range check on input bytes. + fn digest( + &self, + ctx: &mut Self::ThreadBuidler, + input: impl Iterator>, + strict: bool, + ) -> Result, Error>; +} + +pub trait HashCurveExt: CurveExt +where + Self::Base: crate::ff::PrimeField, +{ + type Fp: crate::ff::PrimeField + crate::ff::WithSmallOrderMulGroup<3>; + + const BLS_X: u64; + + const SWU_A: Self::Base; + const SWU_B: Self::Base; + const SWU_Z: Self::Base; + + const ISO_XNUM: [Self::Base; 4]; + const ISO_XDEN: [Self::Base; 3]; + const ISO_YNUM: [Self::Base; 4]; + const ISO_YDEN: [Self::Base; 4]; + + const PSI_X: Self::Base; + const PSI_Y: Self::Base; + const PSI2_X: Self::Base; +} + +pub trait ExpandMessageChip { + fn expand_message>>( + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + len_in_bytes: usize, + ) -> Result>, Error>; +} + +pub trait HashEccChip, C: HashCurveExt> +where + FC::FieldType: crate::ff::PrimeField, + FC: Selectable, +{ + fn field_chip(&self) -> &FC; + + fn scalar_mult_bits( + &self, + ctx: &mut Context, + p: EcPoint, + bits: Vec>, + window_bits: usize, + ) -> EcPoint; + + fn hash_to_field>, XC: ExpandMessageChip>( + &self, + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + msg: impl Iterator>, + dst: &[u8], + ) -> Result<[FC::FieldPoint; 2], Error>; + + fn isogeny_map( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint; + + fn clear_cofactor( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint; + + fn mul_by_bls_x( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + let bls_x_bits = (0..64) + .map(|i| ((C::BLS_X >> i) & 1) as u8) + .map(|b| ctx.load_constant(F::from(b as u64))) + .collect_vec(); + + self.scalar_mult_bits(ctx, p, bls_x_bits, 4) + } + + fn psi( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + // 1 / ((u+1) ^ ((q-1)/3)) + let psi_x = self.field_chip().load_constant(ctx, C::PSI_X); + + // 1 / ((u+1) ^ (p-1)/2) + let psi_y = self.field_chip().load_constant(ctx, C::PSI_Y); + + let x_frob = self.field_chip().conjugate(ctx, p.x); + let y_frob = self.field_chip().conjugate(ctx, p.y); + + let x = self.field_chip().mul(ctx, x_frob, psi_x.clone()); + let y = self.field_chip().mul(ctx, y_frob, psi_y.clone()); + + EcPoint::new(x, y) + } + + fn psi2( + &self, + ctx: &mut Context, + p: EcPoint, + ) -> EcPoint { + // 1 / 2 ^ ((q-1)/3) + let psi2_x = self.field_chip().load_constant(ctx, C::PSI2_X); + + let x = self.field_chip().mul(ctx, p.x, psi2_x.clone()); + let y = self.field_chip().negate(ctx, p.y); + + EcPoint::new(x, y) + } +} + +pub struct ExpandMsgXmd; + +impl ExpandMessageChip for ExpandMsgXmd { + /// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. + /// + /// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3 + /// + /// References: + /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 + /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 + fn expand_message>>( + ctx: &mut HC::ThreadBuidler, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + len_in_bytes: usize, + ) -> Result>, Error> { + let gate = range.gate(); + + let zero = ctx.load_zero(); + let one = ctx.load_constant(F::ONE); + + // assign DST bytes & cache them + let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); + let dst_prime = dst + .as_ref() + .iter() + .map(|&b| ctx.load_constant(F::from(b as u64))) + .chain(iter::once(dst_len)) + .collect_vec(); + + // padding and length strings + let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); + let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); + + let assigned_msg = msg + .map(|cell| match cell { + QuantumCell::Existing(v) => v, + QuantumCell::Witness(v) => ctx.load_witness(v), + QuantumCell::Constant(v) => ctx.load_constant(v), + _ => unreachable!(), + }) + .collect_vec(); + + // compute blocks + let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; + let mut b_vals = Vec::with_capacity(ell); + let msg_prime = z_pad + .into_iter() + .chain(assigned_msg) + .chain(l_i_b_str) + .chain(iter::once(zero)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); + + let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; + + b_vals.insert( + 0, + hash_chip + .digest::<77>( + ctx, + b_0.into_iter() + .chain(iter::once(one)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing), + false, + )? + .output_bytes, + ); + + for i in 1..ell { + let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) + .into_iter() + .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); + + b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); + } + + let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); + + Ok(uniform_bytes) + } +} + +/// Integer to Octet Stream (numberToBytesBE) +pub fn i2osp( + mut value: u128, + length: usize, + mut f: impl FnMut(F) -> AssignedValue, +) -> Vec> { + let mut octet_string = vec![0; length]; + for i in (0..length).rev() { + octet_string[i] = value & 0xff; + value >>= 8; + } + octet_string.into_iter().map(|b| f(F::from(b as u64))).collect() +} + +pub fn strxor( + a: impl IntoIterator>, + b: impl IntoIterator>, + gate: &impl GateInstructions, + ctx: &mut Context, +) -> Vec> { + a.into_iter().zip(b).map(|(a, b)| gate.bitwise_xor::<8>(ctx, a, b)).collect() +} diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index e970346b..44d1c281 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -20,6 +20,7 @@ pub mod fixed_base; pub mod schnorr_signature; // pub mod fixed_base_pippenger; pub mod pippenger; +pub mod hash_to_curve; // EcPoint and EccChip take in a generic `FieldChip` to implement generic elliptic curve operations on arbitrary field extensions (provided chip exists) for short Weierstrass curves (currently further assuming a4 = 0 for optimization purposes) #[derive(Debug)] From 7e2866d3cf28d57ef6bb7598c0a4acb0f9422651 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 17:41:49 +0200 Subject: [PATCH 17/51] generalize output for hash trait --- .../src/bls12_381/tests/hash_to_curve.rs | 15 +++++++------ halo2-ecc/src/ecc/hash_to_curve.rs | 22 ++++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 433201c8..3f0157e7 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -3,10 +3,12 @@ use std::{fs::File, marker::PhantomData}; use super::*; use crate::{ bls12_381::hash_to_curve::HashToCurveChip, - fields::{FieldChip, FpStrategy}, ecc::hash_to_curve::{AssignedHashResult, HashInstructions, ExpandMsgXmd}, + ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, + fields::{FieldChip, FpStrategy}, }; use halo2_base::{ - gates::RangeChip, halo2_proofs::plonk::Error, utils::BigPrimeField, Context, QuantumCell, + gates::RangeChip, halo2_proofs::plonk::Error, utils::BigPrimeField, AssignedValue, Context, + QuantumCell, }; extern crate pairing; use itertools::Itertools; @@ -31,13 +33,14 @@ impl HashInstructions for Sha256MockChip { const DIGEST_SIZE: usize = 32; type ThreadBuidler = Context; + type Output = Vec>; fn digest( &self, ctx: &mut Self::ThreadBuidler, input: impl Iterator>, _strict: bool, - ) -> Result, Error> { + ) -> Result>, Error> { use sha2::{Digest, Sha256}; let input_bytes = input .map(|b| match b { @@ -51,10 +54,8 @@ impl HashInstructions for Sha256MockChip { let output_bytes = Sha256::digest(input_bytes) .into_iter() .map(|b| ctx.load_witness(F::from(b as u64))) - .collect_vec() - .try_into() - .unwrap(); - Ok(AssignedHashResult { input_bytes: vec![], output_bytes }) + .collect_vec(); + Ok(output_bytes) } } diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index c1508f4e..9f3c39d2 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -12,18 +12,12 @@ use crate::fields::{FieldChipExt, Selectable}; use super::EcPoint; -#[derive(Debug, Clone)] -pub struct AssignedHashResult { - // pub input_len: AssignedValue, - pub input_bytes: Vec>, - pub output_bytes: [AssignedValue; 32], -} - pub trait HashInstructions { const BLOCK_SIZE: usize; const DIGEST_SIZE: usize; type ThreadBuidler; + type Output: IntoIterator>; /// Digests input using hash function and returns finilized output. /// `MAX_INPUT_SIZE` is the maximum size of input that can be processed by the hash function. @@ -33,7 +27,7 @@ pub trait HashInstructions { ctx: &mut Self::ThreadBuidler, input: impl Iterator>, strict: bool, - ) -> Result, Error>; + ) -> Result; } pub trait HashCurveExt: CurveExt @@ -209,30 +203,32 @@ impl ExpandMessageChip for ExpandMsgXmd { .chain(dst_prime.clone()) .map(QuantumCell::Existing); - let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.output_bytes; + let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.into_iter().collect_vec(); b_vals.insert( 0, hash_chip .digest::<77>( ctx, - b_0.into_iter() + b_0.iter() + .copied() .chain(iter::once(one)) .chain(dst_prime.clone()) .map(QuantumCell::Existing), false, )? - .output_bytes, + .into_iter() + .collect_vec(), ); for i in 1..ell { - let preimg = strxor(b_0, b_vals[i - 1], gate, ctx) + let preimg = strxor(b_0.iter().copied(), b_vals[i - 1].iter().copied(), gate, ctx) .into_iter() .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) .chain(dst_prime.clone()) .map(QuantumCell::Existing); - b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.output_bytes); + b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.into_iter().collect_vec()); } let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); From eeb652ca3c4823bae6f24d4eada3e8f4cbca5642 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 17:47:02 +0200 Subject: [PATCH 18/51] refactor --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 18 ++++-------------- halo2-ecc/src/ecc/hash_to_curve.rs | 14 ++++++++++---- halo2-ecc/src/ecc/mod.rs | 15 --------------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index eef4ad98..1945c504 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use crate::bigint::utils::decode_into_bn; -use crate::ecc::hash_to_curve::{ExpandMessageChip, HashCurveExt, HashEccChip, HashInstructions}; +use crate::ecc::hash_to_curve::{ExpandMessageChip, HashCurveExt, HashToCurveInstructions, HashInstructions}; use crate::ecc::EcPoint; use crate::ff::Field; use crate::fields::FieldChipExt; @@ -45,7 +45,7 @@ impl< where FC::FieldType: crate::ff::PrimeField, FC: Selectable, - EccChip<'chip, F, FC>: HashEccChip, + EccChip<'chip, F, FC>: HashToCurveInstructions, { pub fn new(hash_chip: &'chip HC, field_chip: &'chip FC) -> Self { Self { hash_chip, ecc_chip: EccChip::new(field_chip), _curve: PhantomData } @@ -186,23 +186,13 @@ const G2_EXT_DEGREE: usize = 2; // L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) const L: usize = 64; -impl<'chip, F: BigPrimeField> HashEccChip, G2> +impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> for EccChip<'chip, F, Fp2Chip<'chip, F>> { fn field_chip(&self) -> &Fp2Chip<'chip, F> { self.field_chip } - - fn scalar_mult_bits( - &self, - ctx: &mut Context, - p: G2Point, - bits: Vec>, - window_bits: usize, - ) -> G2Point { - self.scalar_mult_bits(ctx, p, bits, window_bits) - } - + /// Implements [section 5.2 of `draft-irtf-cfrg-hash-to-curve-16`][hash_to_field]. /// /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.2 diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 9f3c39d2..0b71c0ef 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use crate::fields::{FieldChipExt, Selectable}; -use super::EcPoint; +use super::{scalar_multiply_bits, EcPoint}; pub trait HashInstructions { const BLOCK_SIZE: usize; @@ -63,8 +63,11 @@ pub trait ExpandMessageChip { ) -> Result>, Error>; } -pub trait HashEccChip, C: HashCurveExt> -where +pub trait HashToCurveInstructions< + F: BigPrimeField, + FC: FieldChipExt, + C: HashCurveExt, +> where FC::FieldType: crate::ff::PrimeField, FC: Selectable, { @@ -76,7 +79,10 @@ where p: EcPoint, bits: Vec>, window_bits: usize, - ) -> EcPoint; + ) -> EcPoint { + let max_bits = bits.len(); + scalar_multiply_bits(self.field_chip(), ctx, p, bits, max_bits, window_bits, true) + } fn hash_to_field>, XC: ExpandMessageChip>( &self, diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index 44d1c281..b386f698 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -1097,21 +1097,6 @@ impl<'chip, F: BigPrimeField, FC: FieldChip> EccChip<'chip, F, FC> { } self.sub_unequal(ctx, acc, rand_point, true) } - - // See [`scalar_mult_bits`] for more details. - pub fn scalar_mult_bits( - &self, - ctx: &mut Context, - P: EcPoint, - bits: Vec>, - window_bits: usize, - ) -> EcPoint - where - FC: Selectable, - { - let max_bits = bits.len(); - scalar_multiply_bits::(self.field_chip, ctx, P, bits, max_bits, window_bits, true) - } } impl<'chip, F: BigPrimeField, FC: FieldChip> EccChip<'chip, F, FC> From e432e88a98486f87d037a4dacacc297b31f6fbfd Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 22:14:03 +0200 Subject: [PATCH 19/51] complete test --- Cargo.toml | 5 +- halo2-ecc/Cargo.toml | 2 +- .../bls12_381/bench_hash_to_curve.config | 8 ++ .../src/bls12_381/tests/hash_to_curve.rs | 81 ++++++++++++++++--- 4 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 halo2-ecc/configs/bls12_381/bench_hash_to_curve.config diff --git a/Cargo.toml b/Cargo.toml index 01b1acb7..ee7e215d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,8 @@ halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } [patch."https://github.com/privacy-scaling-explorations/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } +halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "bls12-381/hash_to_curve" } + [patch."https://github.com/axiom-crypto/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } +halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "bls12-381/hash_to_curve" } diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index 0d2b335d..da0f6511 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -27,7 +27,7 @@ halo2-base={ path="../halo2-base", default-features=false, features=["test-utils test-log="0.2.12" env_logger="0.10.0" pairing="0.23.0" -sha2="0.10.6" +sha2="0.9" [features] default=["jemallocator", "halo2-axiom", "display"] diff --git a/halo2-ecc/configs/bls12_381/bench_hash_to_curve.config b/halo2-ecc/configs/bls12_381/bench_hash_to_curve.config new file mode 100644 index 00000000..6e68e0f6 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_hash_to_curve.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/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 3f0157e7..f4872769 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -1,4 +1,4 @@ -use std::{fs::File, marker::PhantomData}; +use std::{fs::{File, self}, marker::PhantomData, io::{BufReader, BufRead}}; use super::*; use crate::{ @@ -7,10 +7,16 @@ use crate::{ fields::{FieldChip, FpStrategy}, }; use halo2_base::{ - gates::RangeChip, halo2_proofs::plonk::Error, utils::BigPrimeField, AssignedValue, Context, - QuantumCell, + gates::RangeChip, + halo2_proofs::{ + halo2curves::{bls12_381::{hash_to_curve::HashToCurve, G2Affine, G2}, CurveAffine}, + plonk::Error, + }, + utils::BigPrimeField, + AssignedValue, Context, QuantumCell, }; extern crate pairing; +use crate::group::Curve; use itertools::Itertools; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -51,7 +57,7 @@ impl HashInstructions for Sha256MockChip { }) .collect_vec(); - let output_bytes = Sha256::digest(input_bytes) + let output_bytes = Sha256::digest(&input_bytes) .into_iter() .map(|b| ctx.load_witness(F::from(b as u64))) .collect_vec(); @@ -65,6 +71,8 @@ fn hash_to_g2_test( params: HashToCurveCircuitParams, msg: Vec, ) { + use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); let fp2_chip = Fp2Chip::new(&fp_chip); @@ -72,22 +80,25 @@ fn hash_to_g2_test( let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); - let hp = h2c_chip + let assigned_msghash = h2c_chip .hash_to_curve::( ctx, - msg.into_iter().map(|b| QuantumCell::Witness(F::from(b as u64))), - b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_", + msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), + DST, ) .unwrap(); - println!( - "msghash: {:?}", - (fp2_chip.get_assigned_value(&hp.x.into()), fp2_chip.get_assigned_value(&hp.y.into())) - ); + let msghash = G2Affine::from_xy( + fp2_chip.get_assigned_value(&assigned_msghash.x.into()), + fp2_chip.get_assigned_value(&assigned_msghash.y.into()), + ).unwrap(); // Verify off-circuit + let msghash_control = + >>::hash_to_curve(&msg, DST).to_affine(); // Compare the 2 results + assert_eq!(msghash, msghash_control); } #[test] @@ -106,3 +117,51 @@ fn test_hash_to_g2() { hash_to_g2_test(ctx, range, params, test_input); }) } + + +#[test] +fn bench_pairing() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_hash_to_curve.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 bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: HashToCurveCircuitParams = + serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + + let test_input = vec![0u8; 32]; + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + test_input.clone(), + test_input, + |pool, range, test_input| { + hash_to_g2_test(pool.main(), range, bench_params, test_input); + }, + ); + + 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(()) +} + From 9a7c9411d69cf34fcead852f827f1f08b13e3dcd Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 7 Oct 2023 23:23:27 +0200 Subject: [PATCH 20/51] abstract thread manager --- halo2-base/src/gates/flex_gate/threads/mod.rs | 36 ++++++++++++++++ halo2-ecc/src/bls12_381/hash_to_curve.rs | 26 +++++++----- .../src/bls12_381/tests/hash_to_curve.rs | 20 ++++----- halo2-ecc/src/ecc/hash_to_curve.rs | 42 +++++++++---------- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/halo2-base/src/gates/flex_gate/threads/mod.rs b/halo2-base/src/gates/flex_gate/threads/mod.rs index 675f57ab..0b082d61 100644 --- a/halo2-base/src/gates/flex_gate/threads/mod.rs +++ b/halo2-base/src/gates/flex_gate/threads/mod.rs @@ -16,3 +16,39 @@ pub mod single_phase; pub use multi_phase::{GateStatistics, MultiPhaseCoreManager}; pub use parallelize::parallelize_core; pub use single_phase::SinglePhaseCoreManager; + +use crate::{Context, utils::BigPrimeField}; + +/// Abstracts basic context management for custom gate builders. +pub trait ThreadManager { + /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. + fn main(&mut self) -> &mut Context; + + /// Returns the number of threads + fn thread_count(&self) -> usize; + + /// Creates new context but does not append to `self.threads` + fn new_context(&self, context_id: usize) -> Context; + + /// Spawns a new thread for a new given `phase`. Returns a mutable reference to the [Context] of the new thread. + /// * `phase`: The phase (index) of the gate thread. + fn new_thread(&mut self) -> &mut Context; +} + +impl ThreadManager for SinglePhaseCoreManager { + fn main(&mut self) -> &mut Context { + self.main() + } + + fn thread_count(&self) -> usize { + self.thread_count() + } + + fn new_context(&self, context_id: usize) -> Context { + self.new_context(context_id) + } + + fn new_thread(&mut self) -> &mut Context { + self.new_thread() + } +} diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 1945c504..691f743d 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -4,7 +4,9 @@ use std::marker::PhantomData; use crate::bigint::utils::decode_into_bn; -use crate::ecc::hash_to_curve::{ExpandMessageChip, HashCurveExt, HashToCurveInstructions, HashInstructions}; +use crate::ecc::hash_to_curve::{ + ExpandMessageChip, HashCurveExt, HashInstructions, HashToCurveInstructions, +}; use crate::ecc::EcPoint; use crate::ff::Field; use crate::fields::FieldChipExt; @@ -14,6 +16,7 @@ use crate::{ ecc::EccChip, fields::{vector::FieldVector, FieldChip, Selectable}, }; +use halo2_base::gates::flex_gate::threads::ThreadManager; use halo2_base::gates::{GateInstructions, RangeInstructions}; use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; use halo2_base::utils::BigPrimeField; @@ -40,7 +43,7 @@ impl< F: BigPrimeField, C: HashCurveExt, FC: FieldChipExt, - HC: HashInstructions> + 'chip, + HC: HashInstructions + 'chip, > HashToCurveChip<'chip, F, FC, HC, C> where FC::FieldType: crate::ff::PrimeField, @@ -53,12 +56,12 @@ where pub fn hash_to_curve( &self, - ctx: &mut HC::ThreadBuidler, + thread_pool: &mut HC::ThreadManager, msg: impl Iterator>, dst: &[u8], ) -> Result, Error> { - let u = self.ecc_chip.hash_to_field::<_, XC>(ctx, self.hash_chip, msg, dst)?; - let p = self.map_to_curve(ctx, u)?; + let u = self.ecc_chip.hash_to_field::<_, XC>(thread_pool, self.hash_chip, msg, dst)?; + let p = self.map_to_curve(thread_pool.main(), u)?; Ok(p) } @@ -181,7 +184,6 @@ where } } - const G2_EXT_DEGREE: usize = 2; // L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) const L: usize = 64; @@ -192,7 +194,7 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> fn field_chip(&self) -> &Fp2Chip<'chip, F> { self.field_chip } - + /// Implements [section 5.2 of `draft-irtf-cfrg-hash-to-curve-16`][hash_to_field]. /// /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.2 @@ -201,9 +203,9 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L49 /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L128 /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L11 - fn hash_to_field>, XC: ExpandMessageChip>( + fn hash_to_field, XC: ExpandMessageChip>( &self, - ctx: &mut HC::ThreadBuidler, + thread_pool: &mut HC::ThreadManager, hash_chip: &HC, msg: impl Iterator>, dst: &[u8], @@ -213,10 +215,12 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> let gate = range.gate(); // constants - let zero = ctx.load_zero(); + let zero = thread_pool.main().load_zero(); let extended_msg = - XC::expand_message(ctx, hash_chip, range, msg, dst, 2 * G2_EXT_DEGREE * L)?; + XC::expand_message(thread_pool, hash_chip, range, msg, dst, 2 * G2_EXT_DEGREE * L)?; + + let ctx = thread_pool.main(); // 2^256 let two_pow_256 = fp_chip.load_constant_uint(ctx, BigUint::from(2u8).pow(256)); diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index f4872769..83cd4ffe 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -7,13 +7,13 @@ use crate::{ fields::{FieldChip, FpStrategy}, }; use halo2_base::{ - gates::RangeChip, + gates::{RangeChip, flex_gate::threads::SinglePhaseCoreManager}, halo2_proofs::{ halo2curves::{bls12_381::{hash_to_curve::HashToCurve, G2Affine, G2}, CurveAffine}, plonk::Error, }, utils::BigPrimeField, - AssignedValue, Context, QuantumCell, + AssignedValue, QuantumCell, }; extern crate pairing; use crate::group::Curve; @@ -38,12 +38,12 @@ impl HashInstructions for Sha256MockChip { const BLOCK_SIZE: usize = 64; const DIGEST_SIZE: usize = 32; - type ThreadBuidler = Context; + type ThreadManager = SinglePhaseCoreManager; type Output = Vec>; fn digest( &self, - ctx: &mut Self::ThreadBuidler, + thread_pool: &mut Self::ThreadManager, input: impl Iterator>, _strict: bool, ) -> Result>, Error> { @@ -59,14 +59,14 @@ impl HashInstructions for Sha256MockChip { let output_bytes = Sha256::digest(&input_bytes) .into_iter() - .map(|b| ctx.load_witness(F::from(b as u64))) + .map(|b| thread_pool.main().load_witness(F::from(b as u64))) .collect_vec(); Ok(output_bytes) } } fn hash_to_g2_test( - ctx: &mut Context, + thread_pool: &mut SinglePhaseCoreManager, range: &RangeChip, params: HashToCurveCircuitParams, msg: Vec, @@ -82,7 +82,7 @@ fn hash_to_g2_test( let assigned_msghash = h2c_chip .hash_to_curve::( - ctx, + thread_pool, msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), DST, ) @@ -113,8 +113,8 @@ fn test_hash_to_g2() { let test_input = vec![0u8; 32]; - base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { - hash_to_g2_test(ctx, range, params, test_input); + base_test().k(params.degree).lookup_bits(params.lookup_bits).run_builder(|builder, range| { + hash_to_g2_test(builder, range, params, test_input); }) } @@ -143,7 +143,7 @@ fn bench_pairing() -> Result<(), Box> { test_input.clone(), test_input, |pool, range, test_input| { - hash_to_g2_test(pool.main(), range, bench_params, test_input); + hash_to_g2_test(pool, range, bench_params, test_input); }, ); diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 0b71c0ef..f2ec893c 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -1,7 +1,7 @@ use std::iter; use halo2_base::{ - gates::{GateInstructions, RangeInstructions}, + gates::{GateInstructions, RangeInstructions, flex_gate::threads::ThreadManager}, halo2_proofs::{halo2curves::CurveExt, plonk::Error}, utils::BigPrimeField, AssignedValue, Context, QuantumCell, @@ -16,7 +16,7 @@ pub trait HashInstructions { const BLOCK_SIZE: usize; const DIGEST_SIZE: usize; - type ThreadBuidler; + type ThreadManager: ThreadManager; type Output: IntoIterator>; /// Digests input using hash function and returns finilized output. @@ -24,7 +24,7 @@ pub trait HashInstructions { /// `strict` flag indicates whether to perform range check on input bytes. fn digest( &self, - ctx: &mut Self::ThreadBuidler, + ctx: &mut Self::ThreadManager, input: impl Iterator>, strict: bool, ) -> Result; @@ -53,8 +53,8 @@ where } pub trait ExpandMessageChip { - fn expand_message>>( - ctx: &mut HC::ThreadBuidler, + fn expand_message>( + ctx: &mut HC::ThreadManager, hash_chip: &HC, range: &impl RangeInstructions, msg: impl Iterator>, @@ -84,9 +84,9 @@ pub trait HashToCurveInstructions< scalar_multiply_bits(self.field_chip(), ctx, p, bits, max_bits, window_bits, true) } - fn hash_to_field>, XC: ExpandMessageChip>( + fn hash_to_field, XC: ExpandMessageChip>( &self, - ctx: &mut HC::ThreadBuidler, + thread_pool: &mut HC::ThreadManager, hash_chip: &HC, msg: impl Iterator>, dst: &[u8], @@ -163,8 +163,8 @@ impl ExpandMessageChip for ExpandMsgXmd { /// - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/6ce20a1/poc/hash_to_field.py#L89 /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 - fn expand_message>>( - ctx: &mut HC::ThreadBuidler, + fn expand_message>( + thread_pool: &mut HC::ThreadManager, hash_chip: &HC, range: &impl RangeInstructions, msg: impl Iterator>, @@ -173,27 +173,27 @@ impl ExpandMessageChip for ExpandMsgXmd { ) -> Result>, Error> { let gate = range.gate(); - let zero = ctx.load_zero(); - let one = ctx.load_constant(F::ONE); + let zero = thread_pool.main().load_zero(); + let one = thread_pool.main().load_constant(F::ONE); // assign DST bytes & cache them - let dst_len = ctx.load_constant(F::from(dst.as_ref().len() as u64)); + let dst_len = thread_pool.main().load_constant(F::from(dst.as_ref().len() as u64)); let dst_prime = dst .as_ref() .iter() - .map(|&b| ctx.load_constant(F::from(b as u64))) + .map(|&b| thread_pool.main().load_constant(F::from(b as u64))) .chain(iter::once(dst_len)) .collect_vec(); // padding and length strings let z_pad = i2osp(0, HC::BLOCK_SIZE, |_| zero); - let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| ctx.load_constant(b)); + let l_i_b_str = i2osp(len_in_bytes as u128, 2, |b| thread_pool.main().load_constant(b)); let assigned_msg = msg .map(|cell| match cell { QuantumCell::Existing(v) => v, - QuantumCell::Witness(v) => ctx.load_witness(v), - QuantumCell::Constant(v) => ctx.load_constant(v), + QuantumCell::Witness(v) => thread_pool.main().load_witness(v), + QuantumCell::Constant(v) => thread_pool.main().load_constant(v), _ => unreachable!(), }) .collect_vec(); @@ -209,13 +209,13 @@ impl ExpandMessageChip for ExpandMsgXmd { .chain(dst_prime.clone()) .map(QuantumCell::Existing); - let b_0 = hash_chip.digest::<143>(ctx, msg_prime, false)?.into_iter().collect_vec(); + let b_0 = hash_chip.digest::<143>(thread_pool, msg_prime, false)?.into_iter().collect_vec(); b_vals.insert( 0, hash_chip .digest::<77>( - ctx, + thread_pool, b_0.iter() .copied() .chain(iter::once(one)) @@ -228,13 +228,13 @@ impl ExpandMessageChip for ExpandMsgXmd { ); for i in 1..ell { - let preimg = strxor(b_0.iter().copied(), b_vals[i - 1].iter().copied(), gate, ctx) + let preimg = strxor(b_0.iter().copied(), b_vals[i - 1].iter().copied(), gate, thread_pool.main()) .into_iter() - .chain(iter::once(ctx.load_constant(F::from(i as u64 + 1)))) + .chain(iter::once(thread_pool.main().load_constant(F::from(i as u64 + 1)))) .chain(dst_prime.clone()) .map(QuantumCell::Existing); - b_vals.insert(i, hash_chip.digest::<77>(ctx, preimg, false)?.into_iter().collect_vec()); + b_vals.insert(i, hash_chip.digest::<77>(thread_pool, preimg, false)?.into_iter().collect_vec()); } let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); From 2732d171cb031804bb57c3fef594094470a70484 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 9 Oct 2023 15:17:24 +0200 Subject: [PATCH 21/51] add comments --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 173 +------------ .../src/bls12_381/tests/hash_to_curve.rs | 2 +- halo2-ecc/src/ecc/hash_to_curve.rs | 232 ++++++++++++++++-- 3 files changed, 222 insertions(+), 185 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 691f743d..6a1f4d9f 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -1,23 +1,19 @@ -//! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` +//! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` for BLS12-381 (G2). //! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 -use std::marker::PhantomData; - use crate::bigint::utils::decode_into_bn; use crate::ecc::hash_to_curve::{ ExpandMessageChip, HashCurveExt, HashInstructions, HashToCurveInstructions, }; -use crate::ecc::EcPoint; use crate::ff::Field; -use crate::fields::FieldChipExt; -use crate::halo2_base::{AssignedValue, Context, QuantumCell}; +use crate::halo2_base::{Context, QuantumCell}; use crate::halo2_proofs::plonk::Error; use crate::{ ecc::EccChip, - fields::{vector::FieldVector, FieldChip, Selectable}, + fields::{vector::FieldVector, FieldChip}, }; use halo2_base::gates::flex_gate::threads::ThreadManager; -use halo2_base::gates::{GateInstructions, RangeInstructions}; +use halo2_base::gates::RangeInstructions; use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; use halo2_base::utils::BigPrimeField; use itertools::Itertools; @@ -25,165 +21,6 @@ use num_bigint::BigUint; use super::{Fp2Chip, Fp2Point, G2Point}; -#[derive(Debug)] -pub struct HashToCurveChip< - 'chip, - F: BigPrimeField, - FC: FieldChip, - HC: HashInstructions, - C: HashCurveExt, -> { - hash_chip: &'chip HC, - ecc_chip: EccChip<'chip, F, FC>, - _curve: PhantomData, -} - -impl< - 'chip, - F: BigPrimeField, - C: HashCurveExt, - FC: FieldChipExt, - HC: HashInstructions + 'chip, - > HashToCurveChip<'chip, F, FC, HC, C> -where - FC::FieldType: crate::ff::PrimeField, - FC: Selectable, - EccChip<'chip, F, FC>: HashToCurveInstructions, -{ - pub fn new(hash_chip: &'chip HC, field_chip: &'chip FC) -> Self { - Self { hash_chip, ecc_chip: EccChip::new(field_chip), _curve: PhantomData } - } - - pub fn hash_to_curve( - &self, - thread_pool: &mut HC::ThreadManager, - msg: impl Iterator>, - dst: &[u8], - ) -> Result, Error> { - let u = self.ecc_chip.hash_to_field::<_, XC>(thread_pool, self.hash_chip, msg, dst)?; - let p = self.map_to_curve(thread_pool.main(), u)?; - Ok(p) - } - - fn map_to_curve( - &self, - ctx: &mut Context, - u: [FC::FieldPoint; 2], - ) -> Result, Error> { - let [u0, u1] = u; - - let p1 = self.map_to_curve_simple_swu(ctx, u0); - let p2 = self.map_to_curve_simple_swu(ctx, u1); - - let p_sum = self.ecc_chip.add_unequal(ctx, p1, p2, false); - - let iso_p = self.ecc_chip.isogeny_map(ctx, p_sum); - - Ok(self.ecc_chip.clear_cofactor(ctx, iso_p)) - } - - /// Implements [section 6.2 of draft-irtf-cfrg-hash-to-curve-16][map_to_curve_simple_swu] - /// - /// [map_to_curve_simple_swu]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2 - /// - /// References: - /// - https://github.com/mikelodder7/bls12_381_plus/blob/ml/0.5.6/src/hash_to_curve/map_g2.rs#L388 - /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/weierstrass.ts#L1175 - fn map_to_curve_simple_swu( - &self, - ctx: &mut Context, - u: FC::FieldPoint, - ) -> EcPoint { - let field_chip = self.ecc_chip.field_chip(); - let gate = field_chip.range().gate(); - - // constants - let swu_a = field_chip.load_constant(ctx, C::SWU_A); - let swu_b = field_chip.load_constant(ctx, C::SWU_B); - let swu_z = field_chip.load_constant(ctx, C::SWU_Z); - let fq2_one = field_chip.load_constant(ctx, ::ONE); - - let usq = field_chip.mul(ctx, u.clone(), u.clone()); // 1. tv1 = u^2 - let z_usq = field_chip.mul(ctx, usq, swu_z.clone()); // 2. tv1 = Z * tv1 - let zsq_u4 = field_chip.mul(ctx, z_usq.clone(), z_usq.clone()); // 3. tv2 = tv1^2 - let tv2 = field_chip.add(ctx, zsq_u4, z_usq.clone()); // 4. tv2 = tv2 + tv1 - let tv3 = field_chip.add_no_carry(ctx, tv2.clone(), fq2_one); // 5. tv3 = tv2 + 1 - let x0_num = field_chip.mul(ctx, tv3, swu_b.clone()); // 6. tv3 = B * tv3 - - let x_den = { - let tv2_is_zero = field_chip.is_zero(ctx, tv2.clone()); - let tv2_neg = field_chip.negate(ctx, tv2); - - field_chip.select(ctx, swu_z, tv2_neg, tv2_is_zero) // tv2_is_zero ? swu_z : tv2_neg - }; // 7. tv4 = tv2 != 0 ? -tv2 : Z - - let x_den = field_chip.mul(ctx, x_den, swu_a.clone()); // 8. tv4 = A * tv4 - - let x0_num_sqr = field_chip.mul(ctx, x0_num.clone(), x0_num.clone()); // 9. tv2 = tv3^2 - let x_densq = field_chip.mul(ctx, x_den.clone(), x_den.clone()); // 10. tv6 = tv4^2 - let ax_densq = field_chip.mul(ctx, x_densq.clone(), swu_a); // 11. tv5 = A * tv6 - let tv2 = field_chip.add_no_carry(ctx, x0_num_sqr, ax_densq); // 12. tv2 = tv2 + tv5 - let tv2 = field_chip.mul(ctx, tv2, x0_num.clone()); // 13. tv2 = tv2 * tv3 - let gx_den = field_chip.mul(ctx, x_densq, x_den.clone()); // 14. tv6 = tv6 * tv4 - let tv5 = field_chip.mul(ctx, gx_den.clone(), swu_b); // 15. tv5 = B * tv6 - let gx0_num = field_chip.add(ctx, tv2, tv5); // 16. tv2 = tv2 + tv5 - - let x = field_chip.mul(ctx, &z_usq, &x0_num); // 17. x = tv1 * tv3 - - let (is_gx1_square, y1) = self.sqrt_ratio(ctx, gx0_num, gx_den); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) - - let y = field_chip.mul(ctx, &z_usq, &u); // 19. y = tv1 * u - let y = field_chip.mul(ctx, y, y1.clone()); // 20. y = y * y1 - let x = field_chip.select(ctx, x0_num, x, is_gx1_square); // 21. x = is_gx1_square ? tv3 : x - let y = field_chip.select(ctx, y1, y, is_gx1_square); // 22. y = is_gx1_square ? y1 : y - - let to_neg = { - let u_sgn = field_chip.sgn0(ctx, u); - let y_sgn = field_chip.sgn0(ctx, y.clone()); - gate.xor(ctx, u_sgn, y_sgn) - }; // 23. e1 = sgn0(u) == sgn0(y) // we implement an opposite condition: !e1 = sgn0(u) ^ sgn0(y) - - let y_neg = field_chip.negate(ctx, y.clone()); - let y = field_chip.select(ctx, y_neg, y, to_neg); // 24. y = !e1 ? -y : y - let x = field_chip.divide(ctx, x, x_den); // 25. x = x / tv4 - - EcPoint::new(x, y) - } - - // Implements [Appendix F.2.1 of draft-irtf-cfrg-hash-to-curve-16][sqrt_ration] - // - // [sqrt_ration]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2.1 - fn sqrt_ratio( - &self, - ctx: &mut Context, - num: FC::FieldPoint, - div: FC::FieldPoint, - ) -> (AssignedValue, FC::FieldPoint) { - let field_chip = self.ecc_chip.field_chip(); - let num_v = field_chip.get_assigned_value(&num.clone().into()); - let div_v = field_chip.get_assigned_value(&div.clone().into()); - - let (is_square, y) = FC::FieldType::sqrt_ratio(&num_v, &div_v); - - let is_square = ctx.load_witness(F::from(is_square.unwrap_u8() as u64)); - field_chip.gate().assert_bit(ctx, is_square); // assert is_square is boolean - - let y_assigned = field_chip.load_private(ctx, y); - let y_sqr = field_chip.mul(ctx, y_assigned.clone(), y_assigned.clone()); // y_sqr = y1^2 - - let ratio = field_chip.divide(ctx, num, div); // r = u / v - - let swu_z = field_chip.load_constant(ctx, C::SWU_Z); - let ratio_z = field_chip.mul(ctx, ratio.clone(), swu_z.clone()); // r_z = r * z - - let y_check = field_chip.select(ctx, ratio, ratio_z, is_square); // y_check = is_square ? ratio : r_z - - field_chip.assert_equal(ctx, y_check, y_sqr); // assert y_check == y_sqr - - (is_square, y_assigned) - } -} - const G2_EXT_DEGREE: usize = 2; // L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) const L: usize = 64; @@ -349,8 +186,6 @@ mod bls12_381 { use super::HashCurveExt; impl HashCurveExt for G2 { - type Fp = Fq; - const BLS_X: u64 = 0xd201000000010000; const SWU_A: Self::Base = Self::Base { diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 83cd4ffe..6867ac72 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -2,7 +2,7 @@ use std::{fs::{File, self}, marker::PhantomData, io::{BufReader, BufRead}}; use super::*; use crate::{ - bls12_381::hash_to_curve::HashToCurveChip, + ecc::hash_to_curve::HashToCurveChip, ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, fields::{FieldChip, FpStrategy}, }; diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index f2ec893c..36ef6b64 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -1,22 +1,31 @@ -use std::iter; +//! Implements `draft-irtf-cfrg-hash-to-curve-16`. +//! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 + +use std::{iter, marker::PhantomData}; use halo2_base::{ - gates::{GateInstructions, RangeInstructions, flex_gate::threads::ThreadManager}, + gates::{flex_gate::threads::ThreadManager, GateInstructions, RangeInstructions}, halo2_proofs::{halo2curves::CurveExt, plonk::Error}, utils::BigPrimeField, AssignedValue, Context, QuantumCell, }; use itertools::Itertools; -use crate::fields::{FieldChipExt, Selectable}; +use crate::ff::Field; +use crate::fields::{FieldChip, FieldChipExt, Selectable}; -use super::{scalar_multiply_bits, EcPoint}; +use super::{scalar_multiply_bits, EcPoint, EccChip}; +/// Trait that defines basic interfaces of hash chips supporting custom region manangers (e.g. [`SinglePhaseCoreManager`]). pub trait HashInstructions { + // Number of bytes absorbed in a single rount of hashing. const BLOCK_SIZE: usize; + // Number of bytes in the output. const DIGEST_SIZE: usize; + // Type of region manager used by the hash function. type ThreadManager: ThreadManager; + // Type of output produced by the hash function. type Output: IntoIterator>; /// Digests input using hash function and returns finilized output. @@ -30,14 +39,11 @@ pub trait HashInstructions { ) -> Result; } +/// Trait that extneds [`CurveExt`] with constants specific to hash to curve operations. pub trait HashCurveExt: CurveExt where Self::Base: crate::ff::PrimeField, { - type Fp: crate::ff::PrimeField + crate::ff::WithSmallOrderMulGroup<3>; - - const BLS_X: u64; - const SWU_A: Self::Base; const SWU_B: Self::Base; const SWU_Z: Self::Base; @@ -50,11 +56,14 @@ where const PSI_X: Self::Base; const PSI_Y: Self::Base; const PSI2_X: Self::Base; + + const BLS_X: u64; } +/// A trait for message expansion methods supported by [`HashToCurveChip`]. pub trait ExpandMessageChip { fn expand_message>( - ctx: &mut HC::ThreadManager, + thread_pool: &mut HC::ThreadManager, hash_chip: &HC, range: &impl RangeInstructions, msg: impl Iterator>, @@ -63,6 +72,7 @@ pub trait ExpandMessageChip { ) -> Result>, Error>; } +/// Trait that defines methods used by the [`HashToCurveChip`] to the curve point for [`EccChip`]. pub trait HashToCurveInstructions< F: BigPrimeField, FC: FieldChipExt, @@ -73,6 +83,7 @@ pub trait HashToCurveInstructions< { fn field_chip(&self) -> &FC; + /// Computes `scalar * P` where scalar is represented as vector of [bits]. fn scalar_mult_bits( &self, ctx: &mut Context, @@ -84,6 +95,12 @@ pub trait HashToCurveInstructions< scalar_multiply_bits(self.field_chip(), ctx, p, bits, max_bits, window_bits, true) } + /// Hashes a byte string of arbitrary length into one or more elements of `Self`, + /// using [`ExpandMessage`] variant `X`. + /// + /// Implements [section 5.2 of `draft-irtf-cfrg-hash-to-curve-16`][hash_to_field]. + /// + /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.2 fn hash_to_field, XC: ExpandMessageChip>( &self, thread_pool: &mut HC::ThreadManager, @@ -92,18 +109,25 @@ pub trait HashToCurveInstructions< dst: &[u8], ) -> Result<[FC::FieldPoint; 2], Error>; + /// Implements [Appendix E.3 of draft-irtf-cfrg-hash-to-curve-16][isogeny_map_g2] + /// + /// [isogeny_map_g2]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-E.3 fn isogeny_map( &self, ctx: &mut Context, p: EcPoint, ) -> EcPoint; + /// Implements [Appendix G.3 of draft-irtf-cfrg-hash-to-curve-16][clear_cofactor] + /// + /// [clear_cofactor]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-G.3 fn clear_cofactor( &self, ctx: &mut Context, p: EcPoint, ) -> EcPoint; + /// Computes `scalar * P` where scalar is [C::BLS_X] variant of [C: HashCurveExt]. fn mul_by_bls_x( &self, ctx: &mut Context, @@ -117,6 +141,7 @@ pub trait HashToCurveInstructions< self.scalar_mult_bits(ctx, p, bls_x_bits, 4) } + /// Computes the endomorphism psi for point [p]. fn psi( &self, ctx: &mut Context, @@ -137,6 +162,7 @@ pub trait HashToCurveInstructions< EcPoint::new(x, y) } + /// Efficiently omputes psi(psi(P)) for point [p]. fn psi2( &self, ctx: &mut Context, @@ -152,6 +178,174 @@ pub trait HashToCurveInstructions< } } +/// Implementation of random oracle maps to the curve. +#[derive(Debug)] +pub struct HashToCurveChip< + 'chip, + F: BigPrimeField, + FC: FieldChip, + HC: HashInstructions, + C: HashCurveExt, +> { + hash_chip: &'chip HC, + ecc_chip: EccChip<'chip, F, FC>, + _curve: PhantomData, +} + +impl< + 'chip, + F: BigPrimeField, + C: HashCurveExt, + FC: FieldChipExt, + HC: HashInstructions + 'chip, + > HashToCurveChip<'chip, F, FC, HC, C> +where + FC::FieldType: crate::ff::PrimeField, + FC: Selectable, + EccChip<'chip, F, FC>: HashToCurveInstructions, +{ + pub fn new(hash_chip: &'chip HC, field_chip: &'chip FC) -> Self { + Self { hash_chip, ecc_chip: EccChip::new(field_chip), _curve: PhantomData } + } + + /// Implements a uniform encoding from byte strings to elements of [`EcPoint`]. + pub fn hash_to_curve( + &self, + thread_pool: &mut HC::ThreadManager, + msg: impl Iterator>, + dst: &[u8], + ) -> Result, Error> { + let u = self.ecc_chip.hash_to_field::<_, XC>(thread_pool, self.hash_chip, msg, dst)?; + let p = self.map_to_curve(thread_pool.main(), u)?; + Ok(p) + } + + /// Maps an element of the finite field `FC::FieldPoint` to a point on the curve [`EcPoint`]. + fn map_to_curve( + &self, + ctx: &mut Context, + u: [FC::FieldPoint; 2], + ) -> Result, Error> { + let [u0, u1] = u; + + let p1 = self.map_to_curve_simple_swu(ctx, u0); + let p2 = self.map_to_curve_simple_swu(ctx, u1); + + let p_sum = self.ecc_chip.add_unequal(ctx, p1, p2, false); + + let iso_p = self.ecc_chip.isogeny_map(ctx, p_sum); + + Ok(self.ecc_chip.clear_cofactor(ctx, iso_p)) + } + + /// Implements [section 6.2 of draft-irtf-cfrg-hash-to-curve-16][map_to_curve_simple_swu] + /// + /// [map_to_curve_simple_swu]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2 + /// + /// References: + /// - https://github.com/mikelodder7/bls12_381_plus/blob/ml/0.5.6/src/hash_to_curve/map_g2.rs#L388 + /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/weierstrass.ts#L1175 + fn map_to_curve_simple_swu( + &self, + ctx: &mut Context, + u: FC::FieldPoint, + ) -> EcPoint { + let field_chip = self.ecc_chip.field_chip(); + let gate = field_chip.range().gate(); + + // constants + let swu_a = field_chip.load_constant(ctx, C::SWU_A); + let swu_b = field_chip.load_constant(ctx, C::SWU_B); + let swu_z = field_chip.load_constant(ctx, C::SWU_Z); + let fq2_one = field_chip.load_constant(ctx, ::ONE); + + let usq = field_chip.mul(ctx, u.clone(), u.clone()); // 1. tv1 = u^2 + let z_usq = field_chip.mul(ctx, usq, swu_z.clone()); // 2. tv1 = Z * tv1 + let zsq_u4 = field_chip.mul(ctx, z_usq.clone(), z_usq.clone()); // 3. tv2 = tv1^2 + let tv2 = field_chip.add(ctx, zsq_u4, z_usq.clone()); // 4. tv2 = tv2 + tv1 + let tv3 = field_chip.add_no_carry(ctx, tv2.clone(), fq2_one); // 5. tv3 = tv2 + 1 + let x0_num = field_chip.mul(ctx, tv3, swu_b.clone()); // 6. tv3 = B * tv3 + + let x_den = { + let tv2_is_zero = field_chip.is_zero(ctx, tv2.clone()); + let tv2_neg = field_chip.negate(ctx, tv2); + + field_chip.select(ctx, swu_z, tv2_neg, tv2_is_zero) // tv2_is_zero ? swu_z : tv2_neg + }; // 7. tv4 = tv2 != 0 ? -tv2 : Z + + let x_den = field_chip.mul(ctx, x_den, swu_a.clone()); // 8. tv4 = A * tv4 + + let x0_num_sqr = field_chip.mul(ctx, x0_num.clone(), x0_num.clone()); // 9. tv2 = tv3^2 + let x_densq = field_chip.mul(ctx, x_den.clone(), x_den.clone()); // 10. tv6 = tv4^2 + let ax_densq = field_chip.mul(ctx, x_densq.clone(), swu_a); // 11. tv5 = A * tv6 + let tv2 = field_chip.add_no_carry(ctx, x0_num_sqr, ax_densq); // 12. tv2 = tv2 + tv5 + let tv2 = field_chip.mul(ctx, tv2, x0_num.clone()); // 13. tv2 = tv2 * tv3 + let gx_den = field_chip.mul(ctx, x_densq, x_den.clone()); // 14. tv6 = tv6 * tv4 + let tv5 = field_chip.mul(ctx, gx_den.clone(), swu_b); // 15. tv5 = B * tv6 + let gx0_num = field_chip.add(ctx, tv2, tv5); // 16. tv2 = tv2 + tv5 + + let x = field_chip.mul(ctx, &z_usq, &x0_num); // 17. x = tv1 * tv3 + + let (is_gx1_square, y1) = self.sqrt_ratio(ctx, gx0_num, gx_den); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) + + let y = field_chip.mul(ctx, &z_usq, &u); // 19. y = tv1 * u + let y = field_chip.mul(ctx, y, y1.clone()); // 20. y = y * y1 + let x = field_chip.select(ctx, x0_num, x, is_gx1_square); // 21. x = is_gx1_square ? tv3 : x + let y = field_chip.select(ctx, y1, y, is_gx1_square); // 22. y = is_gx1_square ? y1 : y + + let to_neg = { + let u_sgn = field_chip.sgn0(ctx, u); + let y_sgn = field_chip.sgn0(ctx, y.clone()); + gate.xor(ctx, u_sgn, y_sgn) + }; // 23. e1 = sgn0(u) == sgn0(y) // we implement an opposite condition: !e1 = sgn0(u) ^ sgn0(y) + + let y_neg = field_chip.negate(ctx, y.clone()); + let y = field_chip.select(ctx, y_neg, y, to_neg); // 24. y = !e1 ? -y : y + let x = field_chip.divide(ctx, x, x_den); // 25. x = x / tv4 + + EcPoint::new(x, y) + } + + // Implements [Appendix F.2.1 of draft-irtf-cfrg-hash-to-curve-16][sqrt_ration] + // + // [sqrt_ration]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2.1 + fn sqrt_ratio( + &self, + ctx: &mut Context, + num: FC::FieldPoint, + div: FC::FieldPoint, + ) -> (AssignedValue, FC::FieldPoint) { + let field_chip = self.ecc_chip.field_chip(); + let num_v = field_chip.get_assigned_value(&num.clone().into()); + let div_v = field_chip.get_assigned_value(&div.clone().into()); + + let (is_square, y) = FC::FieldType::sqrt_ratio(&num_v, &div_v); + + let is_square = ctx.load_witness(F::from(is_square.unwrap_u8() as u64)); + field_chip.gate().assert_bit(ctx, is_square); // assert is_square is boolean + + let y_assigned = field_chip.load_private(ctx, y); + let y_sqr = field_chip.mul(ctx, y_assigned.clone(), y_assigned.clone()); // y_sqr = y1^2 + + let ratio = field_chip.divide(ctx, num, div); // r = u / v + + let swu_z = field_chip.load_constant(ctx, C::SWU_Z); + let ratio_z = field_chip.mul(ctx, ratio.clone(), swu_z.clone()); // r_z = r * z + + let y_check = field_chip.select(ctx, ratio, ratio_z, is_square); // y_check = is_square ? ratio : r_z + + field_chip.assert_equal(ctx, y_check, y_sqr); // assert y_check == y_sqr + + (is_square, y_assigned) + } +} + +/// Constructor for `expand_message_xmd` for a given digest hash function, message, DST, +/// and output length. +/// +/// Implements [section 5.3.1 of `draft-irtf-cfrg-hash-to-curve-16`][expand_message_xmd]. +/// +/// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.3.1 pub struct ExpandMsgXmd; impl ExpandMessageChip for ExpandMsgXmd { @@ -228,13 +422,21 @@ impl ExpandMessageChip for ExpandMsgXmd { ); for i in 1..ell { - let preimg = strxor(b_0.iter().copied(), b_vals[i - 1].iter().copied(), gate, thread_pool.main()) - .into_iter() - .chain(iter::once(thread_pool.main().load_constant(F::from(i as u64 + 1)))) - .chain(dst_prime.clone()) - .map(QuantumCell::Existing); + let preimg = strxor( + b_0.iter().copied(), + b_vals[i - 1].iter().copied(), + gate, + thread_pool.main(), + ) + .into_iter() + .chain(iter::once(thread_pool.main().load_constant(F::from(i as u64 + 1)))) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing); - b_vals.insert(i, hash_chip.digest::<77>(thread_pool, preimg, false)?.into_iter().collect_vec()); + b_vals.insert( + i, + hash_chip.digest::<77>(thread_pool, preimg, false)?.into_iter().collect_vec(), + ); } let uniform_bytes = b_vals.into_iter().flatten().take(len_in_bytes).collect_vec(); From 21c4e4368b6d4d79ed34cf68ca4ae4dc0208696e Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 9 Oct 2023 15:19:00 +0200 Subject: [PATCH 22/51] cargo fix + fmt --- halo2-base/src/gates/flex_gate/mod.rs | 4 ++-- halo2-base/src/gates/flex_gate/threads/mod.rs | 2 +- halo2-ecc/src/bls12_381/tests/hash_to_curve.rs | 18 ++++++++++++------ halo2-ecc/src/bls12_381/tests/mod.rs | 2 +- halo2-ecc/src/bn254/final_exp.rs | 11 +++++++---- halo2-ecc/src/bn254/pairing.rs | 2 +- halo2-ecc/src/ecc/mod.rs | 2 +- halo2-ecc/src/fields/fp.rs | 8 ++------ halo2-ecc/src/fields/fp2.rs | 6 +----- halo2-ecc/src/fields/mod.rs | 12 ++---------- halo2-ecc/src/fields/vector.rs | 10 ++-------- 11 files changed, 32 insertions(+), 45 deletions(-) diff --git a/halo2-base/src/gates/flex_gate/mod.rs b/halo2-base/src/gates/flex_gate/mod.rs index d6a315ab..63cc888a 100644 --- a/halo2-base/src/gates/flex_gate/mod.rs +++ b/halo2-base/src/gates/flex_gate/mod.rs @@ -604,10 +604,10 @@ pub trait GateInstructions { ) -> AssignedValue { let a_bits = self.num_to_bits(ctx, a, BITS); let b_bits = self.num_to_bits(ctx, b, BITS); - + let xor_bits = a_bits.into_iter().zip(b_bits).map(|(a, b)| self.xor(ctx, a, b)).collect_vec(); - + self.bits_to_num(ctx, xor_bits) } diff --git a/halo2-base/src/gates/flex_gate/threads/mod.rs b/halo2-base/src/gates/flex_gate/threads/mod.rs index 0b082d61..243b0445 100644 --- a/halo2-base/src/gates/flex_gate/threads/mod.rs +++ b/halo2-base/src/gates/flex_gate/threads/mod.rs @@ -17,7 +17,7 @@ pub use multi_phase::{GateStatistics, MultiPhaseCoreManager}; pub use parallelize::parallelize_core; pub use single_phase::SinglePhaseCoreManager; -use crate::{Context, utils::BigPrimeField}; +use crate::{utils::BigPrimeField, Context}; /// Abstracts basic context management for custom gate builders. pub trait ThreadManager { diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 6867ac72..7e77f4a9 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -1,4 +1,8 @@ -use std::{fs::{File, self}, marker::PhantomData, io::{BufReader, BufRead}}; +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + marker::PhantomData, +}; use super::*; use crate::{ @@ -7,9 +11,12 @@ use crate::{ fields::{FieldChip, FpStrategy}, }; use halo2_base::{ - gates::{RangeChip, flex_gate::threads::SinglePhaseCoreManager}, + gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, halo2_proofs::{ - halo2curves::{bls12_381::{hash_to_curve::HashToCurve, G2Affine, G2}, CurveAffine}, + halo2curves::{ + bls12_381::{hash_to_curve::HashToCurve, G2Affine, G2}, + CurveAffine, + }, plonk::Error, }, utils::BigPrimeField, @@ -91,7 +98,8 @@ fn hash_to_g2_test( let msghash = G2Affine::from_xy( fp2_chip.get_assigned_value(&assigned_msghash.x.into()), fp2_chip.get_assigned_value(&assigned_msghash.y.into()), - ).unwrap(); + ) + .unwrap(); // Verify off-circuit let msghash_control = @@ -118,7 +126,6 @@ fn test_hash_to_g2() { }) } - #[test] fn bench_pairing() -> Result<(), Box> { let config_path = "configs/bls12_381/bench_hash_to_curve.config"; @@ -164,4 +171,3 @@ fn bench_pairing() -> Result<(), Box> { } Ok(()) } - diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index c52e7529..4c0ebf1a 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -15,5 +15,5 @@ use std::io::Write; pub mod bls_signature; pub mod ec_add; -pub mod pairing; pub mod hash_to_curve; +pub mod pairing; diff --git a/halo2-ecc/src/bn254/final_exp.rs b/halo2-ecc/src/bn254/final_exp.rs index bb0d76e4..7374424f 100644 --- a/halo2-ecc/src/bn254/final_exp.rs +++ b/halo2-ecc/src/bn254/final_exp.rs @@ -1,12 +1,15 @@ use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use crate::{halo2_proofs::{ - arithmetic::Field, - halo2curves::bn256::{Fq, Fq2, BN_X, FROBENIUS_COEFF_FQ12_C1}, -}, fields::FieldChipExt}; use crate::{ ecc::get_naf, fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, }; +use crate::{ + fields::FieldChipExt, + halo2_proofs::{ + arithmetic::Field, + halo2curves::bn256::{Fq, Fq2, BN_X, FROBENIUS_COEFF_FQ12_C1}, + }, +}; use halo2_base::{ gates::GateInstructions, utils::{modulus, BigPrimeField}, diff --git a/halo2-ecc/src/bn254/pairing.rs b/halo2-ecc/src/bn254/pairing.rs index 8f08b753..28229218 100644 --- a/halo2-ecc/src/bn254/pairing.rs +++ b/halo2-ecc/src/bn254/pairing.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; -use crate::fields::FieldChipExt; use crate::fields::vector::FieldVector; +use crate::fields::FieldChipExt; use crate::halo2_proofs::halo2curves::bn256::{ Fq12, G1Affine, G2Affine, FROBENIUS_COEFF_FQ12_C1, SIX_U_PLUS_2_NAF, }; diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index b386f698..ee75e854 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -19,8 +19,8 @@ pub mod ecdsa; pub mod fixed_base; pub mod schnorr_signature; // pub mod fixed_base_pippenger; -pub mod pippenger; pub mod hash_to_curve; +pub mod pippenger; // EcPoint and EccChip take in a generic `FieldChip` to implement generic elliptic curve operations on arbitrary field extensions (provided chip exists) for short Weierstrass curves (currently further assuming a4 = 0 for optimization purposes) #[derive(Debug)] diff --git a/halo2-ecc/src/fields/fp.rs b/halo2-ecc/src/fields/fp.rs index 18c1bdd0..e102b6be 100644 --- a/halo2-ecc/src/fields/fp.rs +++ b/halo2-ecc/src/fields/fp.rs @@ -486,7 +486,7 @@ impl<'range, F: BigPrimeField, Fp: BigPrimeField> FieldChipExt for FpChip<'ra fn sgn0(&self, ctx: &mut Context, a: impl Into) -> AssignedValue { let a: Self::FieldPoint = a.into(); let range = self.range(); - let gate = range.gate(); + let _gate = range.gate(); let msl = a.limbs()[0]; // most significant limb @@ -494,11 +494,7 @@ impl<'range, F: BigPrimeField, Fp: BigPrimeField> FieldChipExt for FpChip<'ra range.div_mod(ctx, lsb, BigUint::from(2u64), 8).1 // sgn0 = lsb % 2 } - fn conjugate( - &self, - ctx: &mut Context, - a: impl Into - ) -> Self::FieldPoint { + fn conjugate(&self, ctx: &mut Context, a: impl Into) -> Self::FieldPoint { self.negate(ctx, a.into()) } } diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index c7fab703..90915045 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -117,11 +117,7 @@ where FieldVector: From>, FieldVector: From>, { - fn conjugate( - &self, - ctx: &mut Context, - a: impl Into - ) -> Self::FieldPoint { + fn conjugate(&self, ctx: &mut Context, a: impl Into) -> Self::FieldPoint { let a: Self::FieldPoint = a.into(); let mut a = a.0; assert_eq!(a.len(), 2); diff --git a/halo2-ecc/src/fields/mod.rs b/halo2-ecc/src/fields/mod.rs index 5d6b7152..ea7deb0b 100644 --- a/halo2-ecc/src/fields/mod.rs +++ b/halo2-ecc/src/fields/mod.rs @@ -304,19 +304,11 @@ pub trait FieldChipExt: FieldChip { self.carry_mod(ctx, no_carry) } - fn conjugate( - &self, - ctx: &mut Context, - a: impl Into, - ) -> Self::FieldPoint; + fn conjugate(&self, ctx: &mut Context, a: impl Into) -> Self::FieldPoint; /// This function returns either 0 or 1 indicating the "sign" of x, where sgn0(x) == 1 just when x is "negative". /// (In other words, this function always considers 0 to be positive.) - fn sgn0( - &self, - ctx: &mut Context, - a: impl Into, - ) -> AssignedValue; + fn sgn0(&self, ctx: &mut Context, a: impl Into) -> AssignedValue; } pub trait Selectable { diff --git a/halo2-ecc/src/fields/vector.rs b/halo2-ecc/src/fields/vector.rs index bfaf8366..f4fa9db0 100644 --- a/halo2-ecc/src/fields/vector.rs +++ b/halo2-ecc/src/fields/vector.rs @@ -361,14 +361,8 @@ where let v = v.as_ref().to_vec(); let len = v[0].0.len(); let mut iters = v.into_iter().map(|n| n.into_iter()).collect_vec(); - let v_transpoed = (0..len) - .map(|_| { - iters - .iter_mut() - .map(|n| n.next().unwrap()) - .collect_vec() - }); - + let v_transpoed = + (0..len).map(|_| iters.iter_mut().map(|n| n.next().unwrap()).collect_vec()); FieldVector( v_transpoed.map(|x| self.fp_chip.select_by_indicator(ctx, &x, coeffs)).collect(), From f689ab830db716dcf897033c45155415cd51b808 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Thu, 12 Oct 2023 15:26:10 +0200 Subject: [PATCH 23/51] use separte halo2curves with `halo2-pse` feature --- Cargo.toml | 3 --- halo2-base/src/utils/mod.rs | 2 +- halo2-ecc/Cargo.toml | 5 ++++- halo2-ecc/results/bls12_381/pairing_bench.csv | 1 - halo2-ecc/src/bls12_381/bls_signature.rs | 3 +-- halo2-ecc/src/bls12_381/final_exp.rs | 2 +- halo2-ecc/src/bls12_381/mod.rs | 10 +++++++++- halo2-ecc/src/bls12_381/pairing.rs | 4 ++-- halo2-ecc/src/bls12_381/tests/bls_signature.rs | 12 ++---------- halo2-ecc/src/bls12_381/tests/ec_add.rs | 1 - halo2-ecc/src/bls12_381/tests/mod.rs | 12 ++++++++---- halo2-ecc/src/bls12_381/tests/pairing.rs | 5 +---- halo2-ecc/src/bn254/tests/bls_signature.rs | 6 +++++- halo2-ecc/src/fields/fp12.rs | 4 ++++ halo2-ecc/src/fields/fp2.rs | 5 +++++ 15 files changed, 43 insertions(+), 32 deletions(-) delete mode 100644 halo2-ecc/results/bls12_381/pairing_bench.csv diff --git a/Cargo.toml b/Cargo.toml index 01b1acb7..43d78ff1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,5 @@ debug = true halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } -[patch."https://github.com/privacy-scaling-explorations/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } - [patch."https://github.com/axiom-crypto/halo2curves"] halo2curves = { 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 fb1d7235..116459e6 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -150,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 d3da45fc..cf15855f 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -17,6 +17,9 @@ rayon="1.6.1" test-case="3.1.0" halo2-base={ path="../halo2-base", default-features=false } +# Use additiona 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 patchign it is not possible +halo2curves = { git = "https://github.com/axiom-crypto/halo2curves", optional=true } [dev-dependencies] ark-std={ version="0.3.0", features=["print-trace"] } @@ -33,7 +36,7 @@ default=["jemallocator", "halo2-axiom", "display"] dev-graph=["halo2-base/dev-graph"] 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/results/bls12_381/pairing_bench.csv b/halo2-ecc/results/bls12_381/pairing_bench.csv deleted file mode 100644 index 04656bac..00000000 --- a/halo2-ecc/results/bls12_381/pairing_bench.csv +++ /dev/null @@ -1 +0,0 @@ -degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time diff --git a/halo2-ecc/src/bls12_381/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs index cfd11a93..13700390 100644 --- a/halo2-ecc/src/bls12_381/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -2,12 +2,11 @@ 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 crate::halo2_proofs::halo2curves::bls12_381::{G1Affine, G2Affine}; -use halo2_base::halo2_proofs::halo2curves::bls12_381::Fq12; use halo2_base::utils::BigPrimeField; use halo2_base::{AssignedValue, Context}; diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index acc712fd..a3608684 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,6 +1,6 @@ use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; +use super::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ ecc::get_naf, fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index c1d6583f..d8988f85 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -1,12 +1,20 @@ use crate::bigint::ProperCrtUint; use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; 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>; diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index be44bad5..bd4ded7d 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -1,13 +1,13 @@ #![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::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; use crate::{ ecc::{EcPoint, EccChip}, fields::fp12::mul_no_carry_w6, fields::FieldChip, }; -use halo2_base::halo2_proofs::halo2curves::bls12_381::BLS_X_IS_NEGATIVE; + use halo2_base::utils::BigPrimeField; use halo2_base::Context; diff --git a/halo2-ecc/src/bls12_381/tests/bls_signature.rs b/halo2-ecc/src/bls12_381/tests/bls_signature.rs index baef5e9b..6d620be8 100644 --- a/halo2-ecc/src/bls12_381/tests/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/tests/bls_signature.rs @@ -5,16 +5,8 @@ use std::{ }; use super::*; -use crate::{ - bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::G2Affine, -}; -use halo2_base::{ - gates::RangeChip, - halo2_proofs::halo2curves::bls12_381::{multi_miller_loop, Gt}, - utils::BigPrimeField, - Context, -}; +use crate::{bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy}; +use halo2_base::{gates::RangeChip, utils::BigPrimeField, Context}; extern crate pairing; use pairing::group::ff::Field; use rand_core::OsRng; diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs index 9d0b3692..8a2a566d 100644 --- a/halo2-ecc/src/bls12_381/tests/ec_add.rs +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -4,7 +4,6 @@ use std::io::{BufRead, BufReader}; use super::*; use crate::fields::{FieldChip, FpStrategy}; -use crate::halo2_proofs::halo2curves::bls12_381::G2Affine; use halo2_base::gates::RangeChip; use halo2_base::utils::testing::base_test; use halo2_base::utils::BigPrimeField; diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index ce937258..5cf43b14 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -2,11 +2,8 @@ use super::pairing::PairingChip; use super::*; use crate::ecc::EccChip; +use crate::fields::FpStrategy; use crate::group::Curve; -use crate::{ - fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar, G1Affine}, -}; use halo2_base::utils::testing::base_test; use rand::rngs::StdRng; use rand_core::SeedableRng; @@ -14,5 +11,12 @@ 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 index b46e6037..cc9ffe78 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -7,10 +7,7 @@ use crate::fields::FieldChip; use super::*; use halo2_base::{ - gates::RangeChip, - halo2_proofs::{arithmetic::Field, halo2curves::bls12_381::G2Affine}, - utils::BigPrimeField, - Context, + gates::RangeChip, halo2_proofs::arithmetic::Field, utils::BigPrimeField, Context, }; use serde::{Deserialize, Serialize}; diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 8475f677..730340d0 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -15,7 +15,11 @@ use halo2_base::{ Context, }; extern crate pairing; -use pairing::{group::ff::Field, MillerLoopResult}; +use crate::group::ff::Field; +#[cfg(feature = "halo2-pse")] +use halo2_base::halo2_proofs::halo2curves::pairing::MillerLoopResult; +#[cfg(feature = "halo2-axiom")] +use pairing::MillerLoopResult; use rand_core::OsRng; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/halo2-ecc/src/fields/fp12.rs b/halo2-ecc/src/fields/fp12.rs index 64b6a200..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; @@ -252,7 +253,10 @@ 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 { diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 8068e2f5..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; @@ -133,7 +134,11 @@ 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] } From 1a01328043ab3ccf0b9b26b861fec93859d7a2ed Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Thu, 12 Oct 2023 15:08:08 +0200 Subject: [PATCH 24/51] use separte halo2curves with `halo2-pse` feature --- Cargo.toml | 3 --- halo2-base/src/utils/mod.rs | 2 +- halo2-ecc/Cargo.toml | 5 ++++- halo2-ecc/results/bls12_381/pairing_bench.csv | 1 - halo2-ecc/src/bls12_381/final_exp.rs | 2 +- halo2-ecc/src/bls12_381/mod.rs | 10 +++++++++- halo2-ecc/src/bls12_381/pairing.rs | 4 ++-- halo2-ecc/src/bls12_381/tests/ec_add.rs | 1 - halo2-ecc/src/bls12_381/tests/mod.rs | 10 ++++++---- halo2-ecc/src/bls12_381/tests/pairing.rs | 5 +---- halo2-ecc/src/bn254/tests/bls_signature.rs | 6 +++++- halo2-ecc/src/fields/fp12.rs | 4 ++++ halo2-ecc/src/fields/fp2.rs | 5 +++++ 13 files changed, 38 insertions(+), 20 deletions(-) delete mode 100644 halo2-ecc/results/bls12_381/pairing_bench.csv diff --git a/Cargo.toml b/Cargo.toml index 01b1acb7..43d78ff1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,5 @@ debug = true halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } -[patch."https://github.com/privacy-scaling-explorations/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } - [patch."https://github.com/axiom-crypto/halo2curves"] halo2curves = { 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 fb1d7235..116459e6 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -150,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 d3da45fc..cf15855f 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -17,6 +17,9 @@ rayon="1.6.1" test-case="3.1.0" halo2-base={ path="../halo2-base", default-features=false } +# Use additiona 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 patchign it is not possible +halo2curves = { git = "https://github.com/axiom-crypto/halo2curves", optional=true } [dev-dependencies] ark-std={ version="0.3.0", features=["print-trace"] } @@ -33,7 +36,7 @@ default=["jemallocator", "halo2-axiom", "display"] dev-graph=["halo2-base/dev-graph"] 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/results/bls12_381/pairing_bench.csv b/halo2-ecc/results/bls12_381/pairing_bench.csv deleted file mode 100644 index 04656bac..00000000 --- a/halo2-ecc/results/bls12_381/pairing_bench.csv +++ /dev/null @@ -1 +0,0 @@ -degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index acc712fd..a3608684 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,6 +1,6 @@ use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; +use super::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ ecc::get_naf, fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index e39d079f..3c04ef01 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -1,11 +1,19 @@ use crate::bigint::ProperCrtUint; use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; 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>; diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index be44bad5..bd4ded7d 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -1,13 +1,13 @@ #![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::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; use crate::{ ecc::{EcPoint, EccChip}, fields::fp12::mul_no_carry_w6, fields::FieldChip, }; -use halo2_base::halo2_proofs::halo2curves::bls12_381::BLS_X_IS_NEGATIVE; + use halo2_base::utils::BigPrimeField; use halo2_base::Context; diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs index 9d0b3692..8a2a566d 100644 --- a/halo2-ecc/src/bls12_381/tests/ec_add.rs +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -4,7 +4,6 @@ use std::io::{BufRead, BufReader}; use super::*; use crate::fields::{FieldChip, FpStrategy}; -use crate::halo2_proofs::halo2curves::bls12_381::G2Affine; use halo2_base::gates::RangeChip; use halo2_base::utils::testing::base_test; use halo2_base::utils::BigPrimeField; diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index 53c966ae..785a96ea 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -2,16 +2,18 @@ use super::pairing::PairingChip; use super::*; use crate::ecc::EccChip; +use crate::fields::FpStrategy; use crate::group::Curve; -use crate::{ - fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar, G1Affine}, -}; use halo2_base::utils::testing::base_test; use rand::rngs::StdRng; use rand_core::SeedableRng; use serde::{Deserialize, Serialize}; use std::io::Write; +#[cfg(feature = "halo2-axiom")] +pub(crate) use crate::halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar}; +#[cfg(feature = "halo2-pse")] +pub(crate) use halo2curves::bls12_381::{pairing, Fr as Scalar}; + 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 index b46e6037..cc9ffe78 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -7,10 +7,7 @@ use crate::fields::FieldChip; use super::*; use halo2_base::{ - gates::RangeChip, - halo2_proofs::{arithmetic::Field, halo2curves::bls12_381::G2Affine}, - utils::BigPrimeField, - Context, + gates::RangeChip, halo2_proofs::arithmetic::Field, utils::BigPrimeField, Context, }; use serde::{Deserialize, Serialize}; diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 8475f677..730340d0 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -15,7 +15,11 @@ use halo2_base::{ Context, }; extern crate pairing; -use pairing::{group::ff::Field, MillerLoopResult}; +use crate::group::ff::Field; +#[cfg(feature = "halo2-pse")] +use halo2_base::halo2_proofs::halo2curves::pairing::MillerLoopResult; +#[cfg(feature = "halo2-axiom")] +use pairing::MillerLoopResult; use rand_core::OsRng; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/halo2-ecc/src/fields/fp12.rs b/halo2-ecc/src/fields/fp12.rs index 64b6a200..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; @@ -252,7 +253,10 @@ 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 { diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 8068e2f5..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; @@ -133,7 +134,11 @@ 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] } From c3718f5ca03b8d488cd0568dcc6c5039239cfbe5 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Thu, 12 Oct 2023 15:37:59 +0200 Subject: [PATCH 25/51] use separte halo2curves with `halo2-pse` feature --- Cargo.toml | 4 ---- halo2-base/src/utils/mod.rs | 2 +- halo2-ecc/Cargo.toml | 5 ++++- halo2-ecc/results/bls12_381/pairing_bench.csv | 1 - halo2-ecc/src/bls12_381/bls_signature.rs | 3 +-- halo2-ecc/src/bls12_381/final_exp.rs | 2 +- halo2-ecc/src/bls12_381/hash_to_curve.rs | 5 ++++- halo2-ecc/src/bls12_381/mod.rs | 10 +++++++++- halo2-ecc/src/bls12_381/pairing.rs | 4 ++-- halo2-ecc/src/bls12_381/tests/bls_signature.rs | 12 ++---------- halo2-ecc/src/bls12_381/tests/ec_add.rs | 1 - halo2-ecc/src/bls12_381/tests/hash_to_curve.rs | 12 +++++------- halo2-ecc/src/bls12_381/tests/mod.rs | 14 ++++++++++---- halo2-ecc/src/bls12_381/tests/pairing.rs | 5 +---- halo2-ecc/src/bn254/tests/bls_signature.rs | 6 +++++- halo2-ecc/src/fields/fp12.rs | 4 ++++ halo2-ecc/src/fields/fp2.rs | 8 +++++++- 17 files changed, 56 insertions(+), 42 deletions(-) delete mode 100644 halo2-ecc/results/bls12_381/pairing_bench.csv diff --git a/Cargo.toml b/Cargo.toml index ee7e215d..5c4cabe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,5 @@ debug = true halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } -[patch."https://github.com/privacy-scaling-explorations/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "bls12-381/hash_to_curve" } - - [patch."https://github.com/axiom-crypto/halo2curves"] halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "bls12-381/hash_to_curve" } diff --git a/halo2-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index fb1d7235..116459e6 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -150,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 da0f6511..9e35f403 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -17,6 +17,9 @@ rayon="1.6.1" test-case="3.1.0" halo2-base={ path="../halo2-base", default-features=false } +# Use additiona 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 patchign it is not possible +halo2curves = { git = "https://github.com/axiom-crypto/halo2curves", optional=true } [dev-dependencies] ark-std={ version="0.3.0", features=["print-trace"] } @@ -34,7 +37,7 @@ default=["jemallocator", "halo2-axiom", "display"] dev-graph=["halo2-base/dev-graph"] 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/results/bls12_381/pairing_bench.csv b/halo2-ecc/results/bls12_381/pairing_bench.csv deleted file mode 100644 index 04656bac..00000000 --- a/halo2-ecc/results/bls12_381/pairing_bench.csv +++ /dev/null @@ -1 +0,0 @@ -degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time diff --git a/halo2-ecc/src/bls12_381/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs index cfd11a93..13700390 100644 --- a/halo2-ecc/src/bls12_381/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -2,12 +2,11 @@ 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 crate::halo2_proofs::halo2curves::bls12_381::{G1Affine, G2Affine}; -use halo2_base::halo2_proofs::halo2curves::bls12_381::Fq12; use halo2_base::utils::BigPrimeField; use halo2_base::{AssignedValue, Context}; diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index 0721ea3a..96abb12c 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,7 +1,7 @@ use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; use crate::fields::FieldChipExt; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; +use super::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; use crate::{ ecc::get_naf, fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 6a1f4d9f..1fb42d8c 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -1,6 +1,7 @@ //! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` for BLS12-381 (G2). //! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 +use super::{Fq2, G2}; use crate::bigint::utils::decode_into_bn; use crate::ecc::hash_to_curve::{ ExpandMessageChip, HashCurveExt, HashInstructions, HashToCurveInstructions, @@ -14,7 +15,6 @@ use crate::{ }; use halo2_base::gates::flex_gate::threads::ThreadManager; use halo2_base::gates::RangeInstructions; -use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq2, G2}; use halo2_base::utils::BigPrimeField; use itertools::Itertools; use num_bigint::BigUint; @@ -181,7 +181,10 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> } mod bls12_381 { + #[cfg(feature = "halo2-axiom")] use halo2_base::halo2_proofs::halo2curves::bls12_381::{Fq, G2}; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::{Fq, G2}; use super::HashCurveExt; diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs index 3b4da817..3749b61d 100644 --- a/halo2-ecc/src/bls12_381/mod.rs +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -2,13 +2,21 @@ use crate::bigint::ProperCrtUint; use crate::ecc::EcPoint; use crate::fields::vector::FieldVector; use crate::fields::{fp, fp12, fp2}; -use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2}; pub mod bls_signature; pub mod final_exp; pub mod hash_to_curve; 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, G2, +}; +#[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, G2, +}; + pub(crate) const XI_0: i64 = 1; pub type FpChip<'range, F> = fp::FpChip<'range, F, Fq>; diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index be44bad5..bd4ded7d 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -1,13 +1,13 @@ #![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::halo2_proofs::halo2curves::bls12_381::{Fq12, G1Affine, G2Affine, BLS_X}; use crate::{ ecc::{EcPoint, EccChip}, fields::fp12::mul_no_carry_w6, fields::FieldChip, }; -use halo2_base::halo2_proofs::halo2curves::bls12_381::BLS_X_IS_NEGATIVE; + use halo2_base::utils::BigPrimeField; use halo2_base::Context; diff --git a/halo2-ecc/src/bls12_381/tests/bls_signature.rs b/halo2-ecc/src/bls12_381/tests/bls_signature.rs index baef5e9b..6d620be8 100644 --- a/halo2-ecc/src/bls12_381/tests/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/tests/bls_signature.rs @@ -5,16 +5,8 @@ use std::{ }; use super::*; -use crate::{ - bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::G2Affine, -}; -use halo2_base::{ - gates::RangeChip, - halo2_proofs::halo2curves::bls12_381::{multi_miller_loop, Gt}, - utils::BigPrimeField, - Context, -}; +use crate::{bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy}; +use halo2_base::{gates::RangeChip, utils::BigPrimeField, Context}; extern crate pairing; use pairing::group::ff::Field; use rand_core::OsRng; diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs index 9d0b3692..8a2a566d 100644 --- a/halo2-ecc/src/bls12_381/tests/ec_add.rs +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -4,7 +4,6 @@ use std::io::{BufRead, BufReader}; use super::*; use crate::fields::{FieldChip, FpStrategy}; -use crate::halo2_proofs::halo2curves::bls12_381::G2Affine; use halo2_base::gates::RangeChip; use halo2_base::utils::testing::base_test; use halo2_base::utils::BigPrimeField; diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 7e77f4a9..2b62d699 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -12,13 +12,7 @@ use crate::{ }; use halo2_base::{ gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, - halo2_proofs::{ - halo2curves::{ - bls12_381::{hash_to_curve::HashToCurve, G2Affine, G2}, - CurveAffine, - }, - plonk::Error, - }, + halo2_proofs::{halo2curves::CurveAffine, plonk::Error}, utils::BigPrimeField, AssignedValue, QuantumCell, }; @@ -78,7 +72,11 @@ fn hash_to_g2_test( params: HashToCurveCircuitParams, msg: Vec, ) { + #[cfg(feature = "halo2-axiom")] use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); let fp2_chip = Fp2Chip::new(&fp_chip); diff --git a/halo2-ecc/src/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index 4c0ebf1a..9ad0fc15 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -2,11 +2,8 @@ use super::pairing::PairingChip; use super::*; use crate::ecc::EccChip; +use crate::fields::FpStrategy; use crate::group::Curve; -use crate::{ - fields::FpStrategy, - halo2_proofs::halo2curves::bls12_381::{pairing, Fr as Scalar, G1Affine}, -}; use halo2_base::utils::testing::base_test; use rand::rngs::StdRng; use rand_core::SeedableRng; @@ -14,6 +11,15 @@ 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::{ + hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +}; +#[cfg(feature = "halo2-pse")] +pub(crate) use halo2curves::bls12_381::{ + hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +}; + pub mod ec_add; pub mod hash_to_curve; pub mod pairing; diff --git a/halo2-ecc/src/bls12_381/tests/pairing.rs b/halo2-ecc/src/bls12_381/tests/pairing.rs index b46e6037..cc9ffe78 100644 --- a/halo2-ecc/src/bls12_381/tests/pairing.rs +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -7,10 +7,7 @@ use crate::fields::FieldChip; use super::*; use halo2_base::{ - gates::RangeChip, - halo2_proofs::{arithmetic::Field, halo2curves::bls12_381::G2Affine}, - utils::BigPrimeField, - Context, + gates::RangeChip, halo2_proofs::arithmetic::Field, utils::BigPrimeField, Context, }; use serde::{Deserialize, Serialize}; diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index 8475f677..730340d0 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -15,7 +15,11 @@ use halo2_base::{ Context, }; extern crate pairing; -use pairing::{group::ff::Field, MillerLoopResult}; +use crate::group::ff::Field; +#[cfg(feature = "halo2-pse")] +use halo2_base::halo2_proofs::halo2curves::pairing::MillerLoopResult; +#[cfg(feature = "halo2-axiom")] +use pairing::MillerLoopResult; use rand_core::OsRng; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/halo2-ecc/src/fields/fp12.rs b/halo2-ecc/src/fields/fp12.rs index 64b6a200..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; @@ -252,7 +253,10 @@ 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 { diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 90915045..a1d7c276 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -1,8 +1,10 @@ use std::fmt::Debug; use std::marker::PhantomData; +use crate::bigint::ProperCrtUint; +#[cfg(feature = "halo2-axiom")] +use crate::ff::PrimeField as _; use crate::impl_field_ext_chip_common; -use crate::{bigint::ProperCrtUint, ff::PrimeField as _}; use super::FieldChipExt; use super::{ @@ -184,7 +186,11 @@ 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] } From 765f52c8cefc749b634ddaccf666ccdac6652e77 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Thu, 12 Oct 2023 15:51:16 +0200 Subject: [PATCH 26/51] fix ambiguous use of halo2curves --- halo2-ecc/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/halo2-ecc/src/lib.rs b/halo2-ecc/src/lib.rs index 3f86ba86..4be26eee 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -14,6 +14,7 @@ pub mod secp256k1; pub use halo2_base; pub(crate) use halo2_base::halo2_proofs; +#[cfg(feature = "halo2-axiom")] use halo2_proofs::halo2curves; use halo2curves::ff; use halo2curves::group; From def5b608fa682fb7efdaa24d09bbbcd17d95c69a Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Wed, 8 Nov 2023 17:26:46 +0100 Subject: [PATCH 27/51] refactor naming --- halo2-base/src/gates/flex_gate/threads/mod.rs | 6 +++--- halo2-ecc/src/bls12_381/hash_to_curve.rs | 4 ++-- halo2-ecc/src/bls12_381/tests/hash_to_curve.rs | 9 +++++---- halo2-ecc/src/ecc/hash_to_curve.rs | 16 ++++++++-------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/halo2-base/src/gates/flex_gate/threads/mod.rs b/halo2-base/src/gates/flex_gate/threads/mod.rs index 243b0445..cdbaae76 100644 --- a/halo2-base/src/gates/flex_gate/threads/mod.rs +++ b/halo2-base/src/gates/flex_gate/threads/mod.rs @@ -19,8 +19,8 @@ pub use single_phase::SinglePhaseCoreManager; use crate::{utils::BigPrimeField, Context}; -/// Abstracts basic context management for custom gate builders. -pub trait ThreadManager { +/// Abstracts basic context management for custom circuit builders. +pub trait CommonCircuitBuilder { /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. fn main(&mut self) -> &mut Context; @@ -35,7 +35,7 @@ pub trait ThreadManager { fn new_thread(&mut self) -> &mut Context; } -impl ThreadManager for SinglePhaseCoreManager { +impl CommonCircuitBuilder for SinglePhaseCoreManager { fn main(&mut self) -> &mut Context { self.main() } diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 1fb42d8c..4fdc0c60 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -13,7 +13,7 @@ use crate::{ ecc::EccChip, fields::{vector::FieldVector, FieldChip}, }; -use halo2_base::gates::flex_gate::threads::ThreadManager; +use halo2_base::gates::flex_gate::threads::CommonCircuitBuilder; use halo2_base::gates::RangeInstructions; use halo2_base::utils::BigPrimeField; use itertools::Itertools; @@ -42,7 +42,7 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L11 fn hash_to_field, XC: ExpandMessageChip>( &self, - thread_pool: &mut HC::ThreadManager, + thread_pool: &mut HC::CircuitBuilder, hash_chip: &HC, msg: impl Iterator>, dst: &[u8], diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 2b62d699..6bf03a73 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -39,17 +39,18 @@ impl HashInstructions for Sha256MockChip { const BLOCK_SIZE: usize = 64; const DIGEST_SIZE: usize = 32; - type ThreadManager = SinglePhaseCoreManager; + type CircuitBuilder = SinglePhaseCoreManager; type Output = Vec>; fn digest( &self, - thread_pool: &mut Self::ThreadManager, - input: impl Iterator>, + thread_pool: &mut Self::CircuitBuilder, + input: impl IntoIterator>, _strict: bool, ) -> Result>, Error> { use sha2::{Digest, Sha256}; let input_bytes = input + .into_iter() .map(|b| match b { QuantumCell::Witness(b) => b.get_lower_32() as u8, QuantumCell::Constant(b) => b.get_lower_32() as u8, @@ -76,7 +77,7 @@ fn hash_to_g2_test( use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; #[cfg(feature = "halo2-pse")] use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; - + const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); let fp2_chip = Fp2Chip::new(&fp_chip); diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 36ef6b64..ea86edce 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -4,7 +4,7 @@ use std::{iter, marker::PhantomData}; use halo2_base::{ - gates::{flex_gate::threads::ThreadManager, GateInstructions, RangeInstructions}, + gates::{flex_gate::threads::CommonCircuitBuilder, GateInstructions, RangeInstructions}, halo2_proofs::{halo2curves::CurveExt, plonk::Error}, utils::BigPrimeField, AssignedValue, Context, QuantumCell, @@ -24,7 +24,7 @@ pub trait HashInstructions { const DIGEST_SIZE: usize; // Type of region manager used by the hash function. - type ThreadManager: ThreadManager; + type CircuitBuilder: CommonCircuitBuilder; // Type of output produced by the hash function. type Output: IntoIterator>; @@ -33,8 +33,8 @@ pub trait HashInstructions { /// `strict` flag indicates whether to perform range check on input bytes. fn digest( &self, - ctx: &mut Self::ThreadManager, - input: impl Iterator>, + ctx: &mut Self::CircuitBuilder, + input: impl IntoIterator>, strict: bool, ) -> Result; } @@ -63,7 +63,7 @@ where /// A trait for message expansion methods supported by [`HashToCurveChip`]. pub trait ExpandMessageChip { fn expand_message>( - thread_pool: &mut HC::ThreadManager, + thread_pool: &mut HC::CircuitBuilder, hash_chip: &HC, range: &impl RangeInstructions, msg: impl Iterator>, @@ -103,7 +103,7 @@ pub trait HashToCurveInstructions< /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-5.2 fn hash_to_field, XC: ExpandMessageChip>( &self, - thread_pool: &mut HC::ThreadManager, + thread_pool: &mut HC::CircuitBuilder, hash_chip: &HC, msg: impl Iterator>, dst: &[u8], @@ -211,7 +211,7 @@ where /// Implements a uniform encoding from byte strings to elements of [`EcPoint`]. pub fn hash_to_curve( &self, - thread_pool: &mut HC::ThreadManager, + thread_pool: &mut HC::CircuitBuilder, msg: impl Iterator>, dst: &[u8], ) -> Result, Error> { @@ -358,7 +358,7 @@ impl ExpandMessageChip for ExpandMsgXmd { /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/abstract/hash-to-curve.ts#L63 /// - https://github.com/succinctlabs/telepathy-circuits/blob/d5c7771/circuits/hash_to_field.circom#L139 fn expand_message>( - thread_pool: &mut HC::ThreadManager, + thread_pool: &mut HC::CircuitBuilder, hash_chip: &HC, range: &impl RangeInstructions, msg: impl Iterator>, From 6d931a49121a8d429c9ecd2380a1d71f332dbcf0 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 24 Nov 2023 16:45:02 +0100 Subject: [PATCH 28/51] halo2_proofs -> halo2-axiom --- halo2-base/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 941c6b64..95ed9721 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -18,7 +18,7 @@ getset="0.1.2" ark-std={ version="0.3.0", features=["print-trace"], optional=true } # 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", package="halo2_proofs", optional=true } +halo2_proofs_axiom={ git="https://github.com/axiom-crypto/halo2.git", package="halo2-axiom", 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", rev="7a21656", optional=true } From 1361ce508c9fba3a4c9d5d848b690fa753d64c70 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 24 Nov 2023 18:39:51 +0100 Subject: [PATCH 29/51] halo2curves from crates.io --- .../src/bls12_381/tests/hash_to_curve.rs | 344 +++++++++--------- halo2-ecc/src/bls12_381/tests/mod.rs | 46 +-- 2 files changed, 195 insertions(+), 195 deletions(-) diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 6bf03a73..29ecb9bf 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -1,172 +1,172 @@ -use std::{ - fs::{self, File}, - io::{BufRead, BufReader}, - marker::PhantomData, -}; - -use super::*; -use crate::{ - ecc::hash_to_curve::HashToCurveChip, - ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, - fields::{FieldChip, FpStrategy}, -}; -use halo2_base::{ - gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, - halo2_proofs::{halo2curves::CurveAffine, plonk::Error}, - utils::BigPrimeField, - AssignedValue, QuantumCell, -}; -extern crate pairing; -use crate::group::Curve; -use itertools::Itertools; - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -struct HashToCurveCircuitParams { - strategy: FpStrategy, - degree: u32, - num_advice: usize, - num_lookup_advice: usize, - num_fixed: usize, - lookup_bits: usize, - limb_bits: usize, - num_limbs: usize, -} - -#[derive(Clone, Copy, Debug, Default)] -struct Sha256MockChip(PhantomData); - -impl HashInstructions for Sha256MockChip { - const BLOCK_SIZE: usize = 64; - const DIGEST_SIZE: usize = 32; - - type CircuitBuilder = SinglePhaseCoreManager; - type Output = Vec>; - - fn digest( - &self, - thread_pool: &mut Self::CircuitBuilder, - input: impl IntoIterator>, - _strict: bool, - ) -> Result>, Error> { - use sha2::{Digest, Sha256}; - let input_bytes = input - .into_iter() - .map(|b| match b { - QuantumCell::Witness(b) => b.get_lower_32() as u8, - QuantumCell::Constant(b) => b.get_lower_32() as u8, - QuantumCell::Existing(av) => av.value().get_lower_32() as u8, - _ => unreachable!(), - }) - .collect_vec(); - - let output_bytes = Sha256::digest(&input_bytes) - .into_iter() - .map(|b| thread_pool.main().load_witness(F::from(b as u64))) - .collect_vec(); - Ok(output_bytes) - } -} - -fn hash_to_g2_test( - thread_pool: &mut SinglePhaseCoreManager, - range: &RangeChip, - params: HashToCurveCircuitParams, - msg: Vec, -) { - #[cfg(feature = "halo2-axiom")] - use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; - #[cfg(feature = "halo2-pse")] - use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; - - const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; - let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); - let fp2_chip = Fp2Chip::new(&fp_chip); - - let sha256 = Sha256MockChip::::default(); - - let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); - - let assigned_msghash = h2c_chip - .hash_to_curve::( - thread_pool, - msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), - DST, - ) - .unwrap(); - - let msghash = G2Affine::from_xy( - fp2_chip.get_assigned_value(&assigned_msghash.x.into()), - fp2_chip.get_assigned_value(&assigned_msghash.y.into()), - ) - .unwrap(); - - // Verify off-circuit - let msghash_control = - >>::hash_to_curve(&msg, DST).to_affine(); - - // Compare the 2 results - assert_eq!(msghash, msghash_control); -} - -#[test] -fn test_hash_to_g2() { - let run_path = "configs/bls12_381/hash_to_curve_circuit.config"; - let path = run_path; - let params: HashToCurveCircuitParams = 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 test_input = vec![0u8; 32]; - - base_test().k(params.degree).lookup_bits(params.lookup_bits).run_builder(|builder, range| { - hash_to_g2_test(builder, range, params, test_input); - }) -} - -#[test] -fn bench_pairing() -> Result<(), Box> { - let config_path = "configs/bls12_381/bench_hash_to_curve.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 bench_params_reader = BufReader::new(bench_params_file); - for line in bench_params_reader.lines() { - let bench_params: HashToCurveCircuitParams = - serde_json::from_str(line.unwrap().as_str()).unwrap(); - let k = bench_params.degree; - println!("---------------------- degree = {k} ------------------------------",); - - let test_input = vec![0u8; 32]; - let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( - test_input.clone(), - test_input, - |pool, range, test_input| { - hash_to_g2_test(pool, range, bench_params, test_input); - }, - ); - - 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(()) -} +// use std::{ +// fs::{self, File}, +// io::{BufRead, BufReader}, +// marker::PhantomData, +// }; + +// use super::*; +// use crate::{ +// ecc::hash_to_curve::HashToCurveChip, +// ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, +// fields::{FieldChip, FpStrategy}, +// }; +// use halo2_base::{ +// gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, +// halo2_proofs::{halo2curves::CurveAffine, plonk::Error}, +// utils::BigPrimeField, +// AssignedValue, QuantumCell, +// }; +// extern crate pairing; +// use crate::group::Curve; +// use itertools::Itertools; + +// #[derive(Clone, Copy, Debug, Serialize, Deserialize)] +// struct HashToCurveCircuitParams { +// strategy: FpStrategy, +// degree: u32, +// num_advice: usize, +// num_lookup_advice: usize, +// num_fixed: usize, +// lookup_bits: usize, +// limb_bits: usize, +// num_limbs: usize, +// } + +// #[derive(Clone, Copy, Debug, Default)] +// struct Sha256MockChip(PhantomData); + +// impl HashInstructions for Sha256MockChip { +// const BLOCK_SIZE: usize = 64; +// const DIGEST_SIZE: usize = 32; + +// type CircuitBuilder = SinglePhaseCoreManager; +// type Output = Vec>; + +// fn digest( +// &self, +// thread_pool: &mut Self::CircuitBuilder, +// input: impl IntoIterator>, +// _strict: bool, +// ) -> Result>, Error> { +// use sha2::{Digest, Sha256}; +// let input_bytes = input +// .into_iter() +// .map(|b| match b { +// QuantumCell::Witness(b) => b.get_lower_32() as u8, +// QuantumCell::Constant(b) => b.get_lower_32() as u8, +// QuantumCell::Existing(av) => av.value().get_lower_32() as u8, +// _ => unreachable!(), +// }) +// .collect_vec(); + +// let output_bytes = Sha256::digest(&input_bytes) +// .into_iter() +// .map(|b| thread_pool.main().load_witness(F::from(b as u64))) +// .collect_vec(); +// Ok(output_bytes) +// } +// } + +// fn hash_to_g2_test( +// thread_pool: &mut SinglePhaseCoreManager, +// range: &RangeChip, +// params: HashToCurveCircuitParams, +// msg: Vec, +// ) { +// #[cfg(feature = "halo2-axiom")] +// use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; +// #[cfg(feature = "halo2-pse")] +// use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + +// const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; +// let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); +// let fp2_chip = Fp2Chip::new(&fp_chip); + +// let sha256 = Sha256MockChip::::default(); + +// let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); + +// let assigned_msghash = h2c_chip +// .hash_to_curve::( +// thread_pool, +// msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), +// DST, +// ) +// .unwrap(); + +// let msghash = G2Affine::from_xy( +// fp2_chip.get_assigned_value(&assigned_msghash.x.into()), +// fp2_chip.get_assigned_value(&assigned_msghash.y.into()), +// ) +// .unwrap(); + +// // Verify off-circuit +// let msghash_control = +// >>::hash_to_curve(&msg, DST).to_affine(); + +// // Compare the 2 results +// assert_eq!(msghash, msghash_control); +// } + +// #[test] +// fn test_hash_to_g2() { +// let run_path = "configs/bls12_381/hash_to_curve_circuit.config"; +// let path = run_path; +// let params: HashToCurveCircuitParams = 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 test_input = vec![0u8; 32]; + +// base_test().k(params.degree).lookup_bits(params.lookup_bits).run_builder(|builder, range| { +// hash_to_g2_test(builder, range, params, test_input); +// }) +// } + +// #[test] +// fn bench_pairing() -> Result<(), Box> { +// let config_path = "configs/bls12_381/bench_hash_to_curve.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 bench_params_reader = BufReader::new(bench_params_file); +// for line in bench_params_reader.lines() { +// let bench_params: HashToCurveCircuitParams = +// serde_json::from_str(line.unwrap().as_str()).unwrap(); +// let k = bench_params.degree; +// println!("---------------------- degree = {k} ------------------------------",); + +// let test_input = vec![0u8; 32]; +// let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( +// test_input.clone(), +// test_input, +// |pool, range, test_input| { +// hash_to_g2_test(pool, range, bench_params, test_input); +// }, +// ); + +// 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/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index 9ad0fc15..96f760c3 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -1,25 +1,25 @@ -#![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; +// #![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::{ - hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, -}; -#[cfg(feature = "halo2-pse")] -pub(crate) use halo2curves::bls12_381::{ - hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, -}; +// pub mod bls_signature; +// #[cfg(feature = "halo2-axiom")] +// pub(crate) use crate::halo2_proofs::halo2curves::bls12_381::{ +// hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +// }; +// #[cfg(feature = "halo2-pse")] +// pub(crate) use halo2curves::bls12_381::{ +// hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +// }; -pub mod ec_add; -pub mod hash_to_curve; -pub mod pairing; +// pub mod ec_add; +// pub mod hash_to_curve; +// pub mod pairing; From 9eb229c069a421974577c31fe767a7f35418ebbc Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Thu, 30 Nov 2023 16:34:10 +0100 Subject: [PATCH 30/51] update hash interface --- Cargo.toml | 4 +- halo2-base/Cargo.toml | 78 ++-- halo2-ecc/Cargo.toml | 2 +- .../src/bls12_381/tests/hash_to_curve.rs | 352 +++++++++--------- halo2-ecc/src/bls12_381/tests/mod.rs | 46 +-- halo2-ecc/src/ecc/hash_to_curve.rs | 23 +- 6 files changed, 261 insertions(+), 244 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5c4cabe3..2301fd36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,5 +40,5 @@ debug = true halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } -[patch."https://github.com/axiom-crypto/halo2curves"] -halo2curves = { git = "https://github.com/timoftime/halo2curves", branch = "bls12-381/hash_to_curve" } +[patch.crates-io] +halo2curves-axiom = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } diff --git a/halo2-base/Cargo.toml b/halo2-base/Cargo.toml index 95ed9721..0bff6098 100644 --- a/halo2-base/Cargo.toml +++ b/halo2-base/Cargo.toml @@ -1,30 +1,30 @@ [package] -name="halo2-base" -version="0.4.0" -edition="2021" +name = "halo2-base" +version = "0.4.0" +edition = "2021" [dependencies] -itertools="0.11" -num-bigint={ version="0.4", features=["rand"] } -num-integer="0.1" -num-traits="0.2" -rand_chacha="0.3" -rustc-hash="1.1" -rayon="1.7" -serde={ version="1.0", features=["derive"] } -serde_json="1.0" -log="0.4" -getset="0.1.2" -ark-std={ version="0.3.0", features=["print-trace"], optional=true } +itertools = "0.11" +num-bigint = { version = "0.4", features = ["rand"] } +num-integer = "0.1" +num-traits = "0.2" +rand_chacha = "0.3" +rustc-hash = "1.1" +rayon = "1.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +log = "0.4" +getset = "0.1.2" +ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } # 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", package="halo2-axiom", 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", rev="7a21656", optional=true } +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", rev = "7a21656", optional = true } # This is Scroll's audited poseidon circuit. We only use it for the Native Poseidon spec. We do not use the halo2 circuit at all (and it wouldn't even work because the halo2_proofs tag is not compatbile). # We forked it to upgrade to ff v0.13 and removed the circuit module -poseidon-rs={ git="https://github.com/axiom-crypto/poseidon-circuit.git", rev="1aee4a1" } +poseidon-rs = { git = "https://github.com/axiom-crypto/poseidon-circuit.git", rev = "1aee4a1" } # plotting circuit layout plotters={ version="0.3.0", optional=true } tabbycat={ version="0.1", features=["attributes"], optional=true } @@ -33,17 +33,17 @@ tabbycat={ version="0.1", features=["attributes"], optional=true } rand={ version="0.8", optional=true } [dev-dependencies] -ark-std={ version="0.3.0", features=["print-trace"] } -rand="0.8" -pprof={ version="0.11", features=["criterion", "flamegraph"] } -criterion="0.4" -criterion-macro="0.4" -test-case="3.1.0" -test-log="0.2.12" -env_logger="0.10.0" -proptest="1.1.0" +ark-std = { version = "0.3.0", features = ["print-trace"] } +rand = "0.8" +pprof = { version = "0.11", features = ["criterion", "flamegraph"] } +criterion = "0.4" +criterion-macro = "0.4" +test-case = "3.1.0" +test-log = "0.2.12" +env_logger = "0.10.0" +proptest = "1.1.0" # native poseidon for testing -pse-poseidon={ git="https://github.com/axiom-crypto/pse-poseidon.git" } +pse-poseidon = { git = "https://github.com/axiom-crypto/pse-poseidon.git" } # memory allocation [target.'cfg(not(target_env = "msvc"))'.dependencies] @@ -52,14 +52,18 @@ jemallocator={ version="=0.5", optional=true } mimalloc={ version="=0.1", default-features=false, optional=true } [features] -default=["halo2-axiom", "display", "test-utils"] -asm=["halo2_proofs_axiom?/asm"] -dev-graph=["halo2_proofs?/dev-graph", "halo2_proofs_axiom?/dev-graph", "plotters"] -halo2-pse=["halo2_proofs/circuit-params"] -halo2-axiom=["halo2_proofs_axiom"] -display=[] -profile=["halo2_proofs_axiom?/profile"] -test-utils=["dep:rand", "ark-std"] +default = ["halo2-axiom", "display", "test-utils"] +asm = ["halo2_proofs_axiom?/asm"] +dev-graph = [ + "halo2_proofs?/dev-graph", + "halo2_proofs_axiom?/dev-graph", + "plotters", +] +halo2-pse = ["halo2_proofs/circuit-params"] +halo2-axiom = ["halo2_proofs_axiom"] +display = [] +profile = ["halo2_proofs_axiom?/profile"] +test-utils = ["dep:rand", "ark-std"] [[bench]] name="mul" @@ -70,5 +74,5 @@ name="inner_product" harness=false [[example]] -name="inner_product" -required-features=["test-utils"] +name = "inner_product" +required-features = ["test-utils"] diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index 9e35f403..2304db2b 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -19,7 +19,7 @@ test-case="3.1.0" halo2-base={ path="../halo2-base", default-features=false } # Use additiona 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 patchign it is not possible -halo2curves = { git = "https://github.com/axiom-crypto/halo2curves", optional=true } +halo2curves = { package = "halo2curves-axiom", version = "=0.4.2", optional=true } [dev-dependencies] ark-std={ version="0.3.0", features=["print-trace"] } diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs index 29ecb9bf..4ee0f946 100644 --- a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -1,172 +1,180 @@ -// use std::{ -// fs::{self, File}, -// io::{BufRead, BufReader}, -// marker::PhantomData, -// }; - -// use super::*; -// use crate::{ -// ecc::hash_to_curve::HashToCurveChip, -// ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, -// fields::{FieldChip, FpStrategy}, -// }; -// use halo2_base::{ -// gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, -// halo2_proofs::{halo2curves::CurveAffine, plonk::Error}, -// utils::BigPrimeField, -// AssignedValue, QuantumCell, -// }; -// extern crate pairing; -// use crate::group::Curve; -// use itertools::Itertools; - -// #[derive(Clone, Copy, Debug, Serialize, Deserialize)] -// struct HashToCurveCircuitParams { -// strategy: FpStrategy, -// degree: u32, -// num_advice: usize, -// num_lookup_advice: usize, -// num_fixed: usize, -// lookup_bits: usize, -// limb_bits: usize, -// num_limbs: usize, -// } - -// #[derive(Clone, Copy, Debug, Default)] -// struct Sha256MockChip(PhantomData); - -// impl HashInstructions for Sha256MockChip { -// const BLOCK_SIZE: usize = 64; -// const DIGEST_SIZE: usize = 32; - -// type CircuitBuilder = SinglePhaseCoreManager; -// type Output = Vec>; - -// fn digest( -// &self, -// thread_pool: &mut Self::CircuitBuilder, -// input: impl IntoIterator>, -// _strict: bool, -// ) -> Result>, Error> { -// use sha2::{Digest, Sha256}; -// let input_bytes = input -// .into_iter() -// .map(|b| match b { -// QuantumCell::Witness(b) => b.get_lower_32() as u8, -// QuantumCell::Constant(b) => b.get_lower_32() as u8, -// QuantumCell::Existing(av) => av.value().get_lower_32() as u8, -// _ => unreachable!(), -// }) -// .collect_vec(); - -// let output_bytes = Sha256::digest(&input_bytes) -// .into_iter() -// .map(|b| thread_pool.main().load_witness(F::from(b as u64))) -// .collect_vec(); -// Ok(output_bytes) -// } -// } - -// fn hash_to_g2_test( -// thread_pool: &mut SinglePhaseCoreManager, -// range: &RangeChip, -// params: HashToCurveCircuitParams, -// msg: Vec, -// ) { -// #[cfg(feature = "halo2-axiom")] -// use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; -// #[cfg(feature = "halo2-pse")] -// use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; - -// const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; -// let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); -// let fp2_chip = Fp2Chip::new(&fp_chip); - -// let sha256 = Sha256MockChip::::default(); - -// let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); - -// let assigned_msghash = h2c_chip -// .hash_to_curve::( -// thread_pool, -// msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), -// DST, -// ) -// .unwrap(); - -// let msghash = G2Affine::from_xy( -// fp2_chip.get_assigned_value(&assigned_msghash.x.into()), -// fp2_chip.get_assigned_value(&assigned_msghash.y.into()), -// ) -// .unwrap(); - -// // Verify off-circuit -// let msghash_control = -// >>::hash_to_curve(&msg, DST).to_affine(); - -// // Compare the 2 results -// assert_eq!(msghash, msghash_control); -// } - -// #[test] -// fn test_hash_to_g2() { -// let run_path = "configs/bls12_381/hash_to_curve_circuit.config"; -// let path = run_path; -// let params: HashToCurveCircuitParams = 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 test_input = vec![0u8; 32]; - -// base_test().k(params.degree).lookup_bits(params.lookup_bits).run_builder(|builder, range| { -// hash_to_g2_test(builder, range, params, test_input); -// }) -// } - -// #[test] -// fn bench_pairing() -> Result<(), Box> { -// let config_path = "configs/bls12_381/bench_hash_to_curve.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 bench_params_reader = BufReader::new(bench_params_file); -// for line in bench_params_reader.lines() { -// let bench_params: HashToCurveCircuitParams = -// serde_json::from_str(line.unwrap().as_str()).unwrap(); -// let k = bench_params.degree; -// println!("---------------------- degree = {k} ------------------------------",); - -// let test_input = vec![0u8; 32]; -// let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( -// test_input.clone(), -// test_input, -// |pool, range, test_input| { -// hash_to_g2_test(pool, range, bench_params, test_input); -// }, -// ); - -// 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(()) -// } +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + marker::PhantomData, +}; + +use super::*; +use crate::{ + ecc::hash_to_curve::HashToCurveChip, + ecc::hash_to_curve::{ExpandMsgXmd, HashInstructions}, + fields::{FieldChip, FpStrategy}, +}; +use halo2_base::{ + gates::{flex_gate::threads::SinglePhaseCoreManager, RangeChip}, + halo2_proofs::{halo2curves::CurveAffine, plonk::Error}, + utils::BigPrimeField, + AssignedValue, QuantumCell, +}; +extern crate pairing; +use crate::group::Curve; +use itertools::Itertools; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct HashToCurveCircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, +} + +#[derive(Clone, Copy, Debug, Default)] +struct Sha256MockChip(PhantomData); + +impl HashInstructions for Sha256MockChip { + const BLOCK_SIZE: usize = 64; + const DIGEST_SIZE: usize = 32; + + type CircuitBuilder = SinglePhaseCoreManager; + type Output = Vec>; + + fn digest( + &self, + thread_pool: &mut Self::CircuitBuilder, + input: impl IntoIterator>, + ) -> Result>, Error> { + use sha2::{Digest, Sha256}; + let input_bytes = input + .into_iter() + .map(|b| match b { + QuantumCell::Witness(b) => b.get_lower_32() as u8, + QuantumCell::Constant(b) => b.get_lower_32() as u8, + QuantumCell::Existing(av) => av.value().get_lower_32() as u8, + _ => unreachable!(), + }) + .collect_vec(); + + let output_bytes = Sha256::digest(&input_bytes) + .into_iter() + .map(|b| thread_pool.main().load_witness(F::from(b as u64))) + .collect_vec(); + Ok(output_bytes) + } + + fn digest_varlen( + &self, + _ctx: &mut Self::CircuitBuilder, + _input: impl IntoIterator>, + _max_input_len: usize, + ) -> Result { + unimplemented!() + } +} + +fn hash_to_g2_test( + thread_pool: &mut SinglePhaseCoreManager, + range: &RangeChip, + params: HashToCurveCircuitParams, + msg: Vec, +) { + #[cfg(feature = "halo2-axiom")] + use crate::halo2_base::halo2_proofs::halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::hash_to_curve::ExpandMsgXmd as ExpandMsgXmdNative; + + const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let fp2_chip = Fp2Chip::new(&fp_chip); + + let sha256 = Sha256MockChip::::default(); + + let h2c_chip = HashToCurveChip::new(&sha256, &fp2_chip); + + let assigned_msghash = h2c_chip + .hash_to_curve::( + thread_pool, + msg.iter().copied().map(|b| QuantumCell::Witness(F::from(b as u64))), + DST, + ) + .unwrap(); + + let msghash = G2Affine::from_xy( + fp2_chip.get_assigned_value(&assigned_msghash.x.into()), + fp2_chip.get_assigned_value(&assigned_msghash.y.into()), + ) + .unwrap(); + + // Verify off-circuit + let msghash_control = + >>::hash_to_curve(&msg, DST).to_affine(); + + // Compare the 2 results + assert_eq!(msghash, msghash_control); +} + +#[test] +fn test_hash_to_g2() { + let run_path = "configs/bls12_381/hash_to_curve_circuit.config"; + let path = run_path; + let params: HashToCurveCircuitParams = 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 test_input = vec![0u8; 32]; + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run_builder(|builder, range| { + hash_to_g2_test(builder, range, params, test_input); + }) +} + +#[test] +fn bench_pairing() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_hash_to_curve.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 bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: HashToCurveCircuitParams = + serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + + let test_input = vec![0u8; 32]; + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + test_input.clone(), + test_input, + |pool, range, test_input| { + hash_to_g2_test(pool, range, bench_params, test_input); + }, + ); + + 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/bls12_381/tests/mod.rs b/halo2-ecc/src/bls12_381/tests/mod.rs index 96f760c3..9ad0fc15 100644 --- a/halo2-ecc/src/bls12_381/tests/mod.rs +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -1,25 +1,25 @@ -// #![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; +#![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::{ -// hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, -// }; -// #[cfg(feature = "halo2-pse")] -// pub(crate) use halo2curves::bls12_381::{ -// hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, -// }; +pub mod bls_signature; +#[cfg(feature = "halo2-axiom")] +pub(crate) use crate::halo2_proofs::halo2curves::bls12_381::{ + hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +}; +#[cfg(feature = "halo2-pse")] +pub(crate) use halo2curves::bls12_381::{ + hash_to_curve::HashToCurve, multi_miller_loop, pairing, Fr as Scalar, Gt, +}; -// pub mod ec_add; -// pub mod hash_to_curve; -// pub mod pairing; +pub mod ec_add; +pub mod hash_to_curve; +pub mod pairing; diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index ea86edce..998c465e 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -28,14 +28,20 @@ pub trait HashInstructions { // Type of output produced by the hash function. type Output: IntoIterator>; - /// Digests input using hash function and returns finilized output. - /// `MAX_INPUT_SIZE` is the maximum size of input that can be processed by the hash function. - /// `strict` flag indicates whether to perform range check on input bytes. - fn digest( + /// Hashes the input of fixed size and returns finilized output. + fn digest( &self, ctx: &mut Self::CircuitBuilder, input: impl IntoIterator>, - strict: bool, + ) -> Result; + + /// Hashes the input of dynamic (but capped) size and and returns finilized output. + /// `max_input_len` is the maximum size of input that can be processed by the hash function. + fn digest_varlen( + &self, + ctx: &mut Self::CircuitBuilder, + input: impl IntoIterator>, + max_input_len: usize, ) -> Result; } @@ -403,19 +409,18 @@ impl ExpandMessageChip for ExpandMsgXmd { .chain(dst_prime.clone()) .map(QuantumCell::Existing); - let b_0 = hash_chip.digest::<143>(thread_pool, msg_prime, false)?.into_iter().collect_vec(); + let b_0 = hash_chip.digest(thread_pool, msg_prime)?.into_iter().collect_vec(); b_vals.insert( 0, hash_chip - .digest::<77>( + .digest( thread_pool, b_0.iter() .copied() .chain(iter::once(one)) .chain(dst_prime.clone()) .map(QuantumCell::Existing), - false, )? .into_iter() .collect_vec(), @@ -435,7 +440,7 @@ impl ExpandMessageChip for ExpandMsgXmd { b_vals.insert( i, - hash_chip.digest::<77>(thread_pool, preimg, false)?.into_iter().collect_vec(), + hash_chip.digest(thread_pool, preimg)?.into_iter().collect_vec(), ); } From d1d1b92cf27037d4a217fcefea99114494881f19 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 5 Jan 2024 18:11:21 +0100 Subject: [PATCH 31/51] optimize final exp --- .../configs/bls12_381/pairing_circuit.config | 2 +- halo2-ecc/src/bls12_381/final_exp.rs | 96 ++++++++++--------- halo2-ecc/src/bls12_381/pairing.rs | 17 +++- halo2-ecc/src/bn254/tests/bls_signature.rs | 11 +-- 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/halo2-ecc/configs/bls12_381/pairing_circuit.config b/halo2-ecc/configs/bls12_381/pairing_circuit.config index 070e8642..2fc76b22 100644 --- a/halo2-ecc/configs/bls12_381/pairing_circuit.config +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -1 +1 @@ -{"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":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":112,"num_limbs":4} diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs index a3608684..630dcc33 100644 --- a/halo2-ecc/src/bls12_381/final_exp.rs +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -1,10 +1,7 @@ use super::XI_0; use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; -use super::{Fq, Fq12, Fq2, BLS_X, FROBENIUS_COEFF_FQ12_C1}; -use crate::{ - ecc::get_naf, - fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}, -}; +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; @@ -59,41 +56,6 @@ impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { FieldVector(out_coeffs) } - // exp is in little-endian - /// # Assumptions - /// * `a` is nonzero field point - pub fn pow( - &self, - ctx: &mut Context, - a: &>::FieldPoint, - exp: Vec, - ) -> >::FieldPoint { - let mut res = a.clone(); - let mut is_started = false; - let naf = get_naf(exp); - - for &z in naf.iter().rev() { - if is_started { - res = self.mul(ctx, &res, &res); - } - - if z != 0 { - assert!(z == 1 || z == -1); - if is_started { - res = if z == 1 { - self.mul(ctx, &res, a) - } else { - self.divide_unsafe(ctx, &res, a) - }; - } else { - assert_eq!(z, 1); - is_started = true; - } - } - } - res - } - // 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} @@ -258,6 +220,14 @@ impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { [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 { @@ -280,6 +250,38 @@ impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { 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, @@ -292,23 +294,25 @@ impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { let f3 = self.frobenius_map(ctx, &f2, 2); let t2 = self.mul(ctx, &f3, &f2); - let t1: FieldVector> = { + 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(ctx, t2.clone(), BLS_X); + 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(ctx, t5.clone(), BLS_X); - let t0 = self.cyclotomic_pow(ctx, t1.clone(), BLS_X); - let t6 = self.cyclotomic_pow(ctx, t0.clone(), BLS_X); + 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(ctx, t6.clone(), BLS_X); + 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); diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index bd4ded7d..39e08ed6 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -421,6 +421,18 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { 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 @@ -438,10 +450,9 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { ) { let ecc_chip_fp = EccChip::new(self.fp_chip); let negated_P = ecc_chip_fp.negate(ctx, P); - let mml = self.multi_miller_loop(ctx, vec![(&negated_P, Q), (S, T)]); + let gt = self.batched_pairing(ctx, &[(&negated_P, Q), (S, T)]); let fp12_chip = Fp12Chip::::new(self.fp_chip); - let fe = fp12_chip.final_exp(ctx, mml); let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); - fp12_chip.assert_equal(ctx, fe, fp12_one); + fp12_chip.assert_equal(ctx, gt, fp12_one); } } diff --git a/halo2-ecc/src/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index de29bb76..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, @@ -15,13 +15,12 @@ use halo2_base::{ utils::BigPrimeField, Context, }; -extern crate pairing; -use crate::group::ff::Field; +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; -#[cfg(feature = "halo2-axiom")] -use pairing::MillerLoopResult; -use rand_core::OsRng; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct BlsSignatureCircuitParams { From 90b7594eacb85071ba836e5f599f5b5c713d799a Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sun, 10 Dec 2023 14:51:11 +0100 Subject: [PATCH 32/51] update test config --- halo2-ecc/configs/bls12_381/bls_signature_circuit.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config index 09d9a232..3e6bf1bf 100644 --- a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config +++ b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config @@ -1 +1 @@ -{"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":30} +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":4,"num_aggregation":30} From b371954ae60da8a783facb86ec68f7bd71294c98 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Fri, 5 Jan 2024 23:45:14 +0100 Subject: [PATCH 33/51] update test config --- halo2-ecc/configs/bls12_381/pairing_circuit.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-ecc/configs/bls12_381/pairing_circuit.config b/halo2-ecc/configs/bls12_381/pairing_circuit.config index 2fc76b22..9a160a5a 100644 --- a/halo2-ecc/configs/bls12_381/pairing_circuit.config +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":112,"num_limbs":4} +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":4} From ae455d8745328e651b6c14e52a23d87eca88ee83 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 19 Feb 2024 18:38:33 +0100 Subject: [PATCH 34/51] fix bigint for bls12-381 --- .../configs/bls12_381/pairing_circuit.config | 2 +- halo2-ecc/src/bigint/carry_mod.rs | 38 ++++++++++--------- .../src/bigint/check_carry_mod_to_zero.rs | 28 +++++++------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/halo2-ecc/configs/bls12_381/pairing_circuit.config b/halo2-ecc/configs/bls12_381/pairing_circuit.config index 9a160a5a..1baf52d2 100644 --- a/halo2-ecc/configs/bls12_381/pairing_circuit.config +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":4} +{"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 0d8ddabd..098659c2 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -8,7 +8,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::One; +use num_traits::{One, Signed}; use super::{check_carry_to_zero, CRTInteger, OverflowInteger, ProperCrtUint, ProperUint}; @@ -44,8 +44,7 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - // FIXME: hotfix for BLS12 support - // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // in order for CRT method to work, we need `abs(out + modulus * quotient - a) < 2^{trunc_len - 1} * native_modulus::` // this is ensured if `0 <= out < 2^{n*k}` and @@ -57,12 +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)} - // FIXME: hotfix for BLS12 support - let _quot_last_limb_bits = 0; //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 @@ -71,8 +69,7 @@ pub fn crt( let (quot_val, out_val) = a.value.div_mod_floor(modulus); debug_assert!(out_val < (BigInt::one() << (n * k))); - // FIXME: hotfix for BLS12 support - // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); // decompose_bigint just throws away signed limbs in index >= k let out_vec = decompose_bigint::(&out_val, k, n); @@ -141,19 +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() { - // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { - /* quot_last_limb_bits */ - n - } 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 9659e6d3..26af73af 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -7,7 +7,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::One; +use num_traits::{One, Signed, Zero}; use std::{cmp::max, iter}; // same as carry_mod::crt but `out = 0` so no need to range check @@ -29,14 +29,15 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - // FIXME: hotfix for BLS12 support - // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // 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); - // FIXME: hotfix for BLS12 support - let _quot_last_limb_bits = 0; // 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 @@ -46,9 +47,8 @@ pub fn crt( let (quot_val, _out_val) = a.value.div_mod_floor(modulus); // only perform safety checks in debug mode - // FIXME: hotfix for BLS12 support - // debug_assert_eq!(_out_val, BigInt::zero()); - // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + debug_assert_eq!(_out_val, BigInt::zero()); + debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); let quot_vec = decompose_bigint::("_val, k, n); @@ -93,12 +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() { - // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { - n /* 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] }; From 9ce672590758415be264c38f41691a9655dc0a7d Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 19 Feb 2024 18:39:27 +0100 Subject: [PATCH 35/51] fix bigint for bls12-381 --- .../bls12_381/bls_signature_circuit.config | 2 +- .../configs/bls12_381/pairing_circuit.config | 2 +- halo2-ecc/src/bigint/carry_mod.rs | 38 ++++++++++--------- .../src/bigint/check_carry_mod_to_zero.rs | 28 +++++++------- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config index 3e6bf1bf..fc6686bb 100644 --- a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config +++ b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":4,"num_aggregation":30} +{"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/pairing_circuit.config b/halo2-ecc/configs/bls12_381/pairing_circuit.config index 2fc76b22..1baf52d2 100644 --- a/halo2-ecc/configs/bls12_381/pairing_circuit.config +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":112,"num_limbs":4} +{"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 0d8ddabd..098659c2 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -8,7 +8,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::One; +use num_traits::{One, Signed}; use super::{check_carry_to_zero, CRTInteger, OverflowInteger, ProperCrtUint, ProperUint}; @@ -44,8 +44,7 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - // FIXME: hotfix for BLS12 support - // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // in order for CRT method to work, we need `abs(out + modulus * quotient - a) < 2^{trunc_len - 1} * native_modulus::` // this is ensured if `0 <= out < 2^{n*k}` and @@ -57,12 +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)} - // FIXME: hotfix for BLS12 support - let _quot_last_limb_bits = 0; //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 @@ -71,8 +69,7 @@ pub fn crt( let (quot_val, out_val) = a.value.div_mod_floor(modulus); debug_assert!(out_val < (BigInt::one() << (n * k))); - // FIXME: hotfix for BLS12 support - // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); // decompose_bigint just throws away signed limbs in index >= k let out_vec = decompose_bigint::(&out_val, k, n); @@ -141,19 +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() { - // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { - /* quot_last_limb_bits */ - n - } 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 9659e6d3..26af73af 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -7,7 +7,7 @@ use halo2_base::{ }; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::One; +use num_traits::{One, Signed, Zero}; use std::{cmp::max, iter}; // same as carry_mod::crt but `out = 0` so no need to range check @@ -29,14 +29,15 @@ pub fn crt( let k = a.truncation.limbs.len(); let trunc_len = n * k; - // FIXME: hotfix for BLS12 support - // debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); + debug_assert!(a.value.bits() as usize <= n * k - 1 + (F::NUM_BITS as usize) - 2); // 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); - // FIXME: hotfix for BLS12 support - let _quot_last_limb_bits = 0; // 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 @@ -46,9 +47,8 @@ pub fn crt( let (quot_val, _out_val) = a.value.div_mod_floor(modulus); // only perform safety checks in debug mode - // FIXME: hotfix for BLS12 support - // debug_assert_eq!(_out_val, BigInt::zero()); - // debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); + debug_assert_eq!(_out_val, BigInt::zero()); + debug_assert!(quot_val.abs() < (BigInt::one() << quot_max_bits)); let quot_vec = decompose_bigint::("_val, k, n); @@ -93,12 +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() { - // FIXME: hotfix for BLS12 support - let limb_bits = if q_index == k - 1 { - n /* 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] }; From 8151307a37045430c3f7d95ae0289400f27c271d Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Sat, 9 Dec 2023 17:48:33 +0100 Subject: [PATCH 36/51] optimize `hash_to_field` & `mul_by_bls_x` --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 65 ++++++++++++++---------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 4fdc0c60..e4f878fb 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -1,8 +1,9 @@ //! The chip that implements `draft-irtf-cfrg-hash-to-curve-16` for BLS12-381 (G2). //! https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16 -use super::{Fq2, G2}; +use super::{Fp2Chip, Fp2Point, Fq2, G2Point, G2}; use crate::bigint::utils::decode_into_bn; +use crate::bigint::CRTInteger; use crate::ecc::hash_to_curve::{ ExpandMessageChip, HashCurveExt, HashInstructions, HashToCurveInstructions, }; @@ -17,9 +18,6 @@ use halo2_base::gates::flex_gate::threads::CommonCircuitBuilder; use halo2_base::gates::RangeInstructions; use halo2_base::utils::BigPrimeField; use itertools::Itertools; -use num_bigint::BigUint; - -use super::{Fp2Chip, Fp2Point, G2Point}; const G2_EXT_DEGREE: usize = 2; // L = ceil((ceil(log2(p)) + k) / 8) (see section 5 of ietf draft link above) @@ -51,17 +49,14 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> let range = fp_chip.range(); let gate = range.gate(); - // constants - let zero = thread_pool.main().load_zero(); - let extended_msg = XC::expand_message(thread_pool, hash_chip, range, msg, dst, 2 * G2_EXT_DEGREE * L)?; let ctx = thread_pool.main(); - // 2^256 - let two_pow_256 = fp_chip.load_constant_uint(ctx, BigUint::from(2u8).pow(256)); - let fq_bytes = 48; //((Fq::NUM_BITS as f64) / 8f64).ceil() as usize; + // Extend limb_bases to work with 64 bytes + let mut limb_bases = fp_chip.limb_bases.clone(); + limb_bases.push(limb_bases[1] * limb_bases.last().unwrap()); let u = extended_msg .chunks(L) @@ -71,29 +66,18 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> FieldVector( elm_chunk .map(|tv| { - let mut buf = vec![zero; fq_bytes]; - let rem = fq_bytes - 32; - buf[rem..].copy_from_slice(&tv[..32]); - let lo = decode_into_bn::( + let y = decode_into_bn::( ctx, gate, - buf.iter().copied().rev().collect_vec(), - &fp_chip.limb_bases, + tv.iter().copied().rev().take(64).collect_vec(), + &limb_bases, fp_chip.limb_bits(), ); - buf[rem..].copy_from_slice(&tv[32..]); - let hi = decode_into_bn::( - ctx, - gate, - buf.into_iter().rev().collect_vec(), - &fp_chip.limb_bases, - fp_chip.limb_bits(), - ); + let mut y: CRTInteger = y.into(); + y.truncation.limbs.pop(); - let lo_2_256 = fp_chip.mul_no_carry(ctx, lo, two_pow_256.clone()); - let lo_2_356_hi = fp_chip.add_no_carry(ctx, lo_2_256, hi); - fp_chip.carry_mod(ctx, lo_2_356_hi) + fp_chip.carry_mod(ctx, y) }) .collect_vec(), ) @@ -178,6 +162,33 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P => [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) self.sub_unequal(ctx, t3, p, false) } + + // Optimized implementation from https://eprint.iacr.org/2017/419.pdf, 4.1 + // Reference: https://github.com/celer-network/brevis-circuits/blob/3a7adf8/gadgets/pairing_bls12381/g2.go#L227 + fn mul_by_bls_x( + &self, + ctx: &mut Context, + p: G2Point, + ) -> G2Point { + let mut res = p.clone(); + let p2 = self.double(ctx, &p); + res = self.add_unequal(ctx, &p2, &p, false); + + for i in 1..32 { + res = self.double(ctx, res); + res = self.double(ctx, res); + + if i == 1 { + res = self.add_unequal(ctx, res, &p, false); + } else if i == 3 { + res = self.add_unequal(ctx, res, &p2, false); + } else if i == 7 || i == 23 { + res = self.add_unequal(ctx, res, &p, false); + } + } + + res + } } mod bls12_381 { From 702dd964558686c9a880a1f3c401891437b5d410 Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Tue, 20 Feb 2024 14:29:28 +0100 Subject: [PATCH 37/51] clean comments --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index e4f878fb..d669da63 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -136,8 +136,6 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> /// - https://github.com/paulmillr/noble-curves/blob/bf70ba9/src/bls12-381.ts#L1111 fn clear_cofactor(&self, ctx: &mut Context, p: G2Point) -> G2Point { let t1 = { - // scalar multiplication is very expensive in terms of rows used - // TODO: is there other ways to clear cofactor that avoid scalar multiplication? let tv = self.mul_by_bls_x(ctx, p.clone()); self.negate(ctx, tv) }; // [-x]P @@ -165,12 +163,7 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> // Optimized implementation from https://eprint.iacr.org/2017/419.pdf, 4.1 // Reference: https://github.com/celer-network/brevis-circuits/blob/3a7adf8/gadgets/pairing_bls12381/g2.go#L227 - fn mul_by_bls_x( - &self, - ctx: &mut Context, - p: G2Point, - ) -> G2Point { - let mut res = p.clone(); + fn mul_by_bls_x(&self, ctx: &mut Context, p: G2Point) -> G2Point { let p2 = self.double(ctx, &p); res = self.add_unequal(ctx, &p2, &p, false); From 1b01da4773650aa61bf14ae21cf3f1b9dd76e6ab Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Tue, 20 Feb 2024 14:30:05 +0100 Subject: [PATCH 38/51] update test config --- halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config | 2 +- halo2-ecc/src/bls12_381/hash_to_curve.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config index 308d058b..fc6686bb 100644 --- a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config +++ b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":112,"num_limbs":4,"num_aggregation":30} +{"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/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index d669da63..d5c5d7ae 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -74,8 +74,7 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> fp_chip.limb_bits(), ); - let mut y: CRTInteger = y.into(); - y.truncation.limbs.pop(); + let y: CRTInteger = y.into(); fp_chip.carry_mod(ctx, y) }) From 8b7948296a03d55848fb1c2afaddc4fa848cc2ba Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Tue, 20 Feb 2024 14:51:05 +0100 Subject: [PATCH 39/51] fix build --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 6f6d7b39..1d77724c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2023-08-12 +nightly-2023-11-16 From dd8b0d2828785bb041fa6a480734a7b0775d3acb Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Tue, 20 Feb 2024 15:30:32 +0100 Subject: [PATCH 40/51] fix for halo2-pse --- halo2-ecc/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index 05bf6e75..fc7d87ca 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -27,7 +27,7 @@ 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-axiom = { optional = true } +halo2curves = { package = "halo2curves-axiom", version = "0.5", optional=true } # plotting circuit layout plotters = { version = "0.3.0", optional = true } @@ -46,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", "halo2curves-axiom"] +halo2-pse = ["halo2-base/halo2-pse", "halo2curves"] halo2-axiom = ["halo2-base/halo2-axiom"] jemallocator = ["halo2-base/jemallocator"] mimalloc = ["halo2-base/mimalloc"] From 23197fc41655af6500651c27670247e3effadb3b Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Tue, 20 Feb 2024 15:38:54 +0100 Subject: [PATCH 41/51] add `assign_sha256_rows` --- hashes/zkevm/src/sha256/vanilla/witness.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hashes/zkevm/src/sha256/vanilla/witness.rs b/hashes/zkevm/src/sha256/vanilla/witness.rs index db95d9e6..ec36bdbd 100644 --- a/hashes/zkevm/src/sha256/vanilla/witness.rs +++ b/hashes/zkevm/src/sha256/vanilla/witness.rs @@ -99,6 +99,20 @@ impl Sha256CircuitConfig { start_offset: usize, ) -> Vec> { let virtual_rows = generate_witnesses_multi_sha256(bytes, capacity); + self.assign_sha256_rows(region, virtual_rows, capacity, start_offset) + } + + /// Computes witnesses for computing SHA-256 for each bytearray in `bytes` + /// and assigns the witnesses to Halo2 cells, starting from a blank region. + /// + /// This is a helper method to allow implementing decorator pattern from oustide of this crate. + pub fn assign_sha256_rows<'v>( + &self, + region: &mut Region<'_, F>, + virtual_rows: Vec, + capacity: Option, + start_offset: usize, + ) -> Vec> { let assigned_rows: Vec<_> = virtual_rows .into_iter() .enumerate() From 0350dd758670da6a39e6b074361052145aad136c Mon Sep 17 00:00:00 2001 From: Timofey Luin Date: Mon, 15 Apr 2024 12:29:59 +0200 Subject: [PATCH 42/51] fix ec_add test config --- halo2-ecc/configs/bls12_381/ec_add_circuit.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-ecc/configs/bls12_381/ec_add_circuit.config b/halo2-ecc/configs/bls12_381/ec_add_circuit.config index c17d44b9..f5d38d53 100644 --- a/halo2-ecc/configs/bls12_381/ec_add_circuit.config +++ b/halo2-ecc/configs/bls12_381/ec_add_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":17,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"batch_size":100} +{"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} From 8f59988e1d60ecadb1e22489051001b45bcfcc91 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:14:27 +0200 Subject: [PATCH 43/51] Fix conversion from Bytes to BigInt (#4) --- halo2-ecc/src/bigint/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-ecc/src/bigint/utils.rs b/halo2-ecc/src/bigint/utils.rs index a25f9669..e921bc90 100644 --- a/halo2-ecc/src/bigint/utils.rs +++ b/halo2-ecc/src/bigint/utils.rs @@ -21,7 +21,7 @@ pub fn decode_into_bn( BigUint::from_bytes_le(&bytes.iter().map(|v| v.value().get_lower_32() as u8).collect_vec()); // inputs is a bool or uint8. - let assigned_uint = if bits == 1 || limb_bytes == 8 { + let assigned_uint = if bits == 1 || bits == 8 || limb_bits == 8 { ProperUint(bytes) } else { let byte_base = From dbefb1569327f348864a746d0f442491a9e42fb9 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:15:05 +0200 Subject: [PATCH 44/51] Fix unsafe division by zero (#5) --- halo2-ecc/src/bls12_381/hash_to_curve.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 05454e77..4a4f31d7 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -116,10 +116,10 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> }) }); - let x = { fp2_chip.divide_unsafe(ctx, x_num, x_den) }; + let x = { fp2_chip.divide(ctx, x_num, x_den) }; let y = { - let tv = fp2_chip.divide_unsafe(ctx, y_num, y_den); + let tv = fp2_chip.divide(ctx, y_num, y_den); fp2_chip.mul(ctx, &p.y, tv) }; From f40a58ab472e24ec4192a5017f6025eeeb7b4d48 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:15:39 +0200 Subject: [PATCH 45/51] Fix under-constrained sqrt_ratio (#7) --- halo2-ecc/src/ecc/hash_to_curve.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 998c465e..82fab42d 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -314,6 +314,8 @@ where // Implements [Appendix F.2.1 of draft-irtf-cfrg-hash-to-curve-16][sqrt_ration] // + // Assumption: `num` != 0 + // // [sqrt_ration]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2.1 fn sqrt_ratio( &self, @@ -325,6 +327,9 @@ where let num_v = field_chip.get_assigned_value(&num.clone().into()); let div_v = field_chip.get_assigned_value(&div.clone().into()); + let num_is_zero = field_chip.is_zero(ctx, num.clone()); + field_chip.gate().assert_is_const(ctx, &num_is_zero, &F::ZERO); + let (is_square, y) = FC::FieldType::sqrt_ratio(&num_v, &div_v); let is_square = ctx.load_witness(F::from(is_square.unwrap_u8() as u64)); From 4467ae2bdf0711522a87664ab688dbbadab1509f Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:16:00 +0200 Subject: [PATCH 46/51] Fix potentially under-constraint hash2curve msg (#8) --- halo2-ecc/src/ecc/hash_to_curve.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 82fab42d..b453b2b7 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -397,9 +397,8 @@ impl ExpandMessageChip for ExpandMsgXmd { let assigned_msg = msg .map(|cell| match cell { QuantumCell::Existing(v) => v, - QuantumCell::Witness(v) => thread_pool.main().load_witness(v), QuantumCell::Constant(v) => thread_pool.main().load_constant(v), - _ => unreachable!(), + _ => panic!("passing unassigned witness to this function is insecure"), }) .collect_vec(); From beaee9ff9139fa553485b0c347a48777437d7233 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:16:14 +0200 Subject: [PATCH 47/51] Fix missing add unequal check in map_to_curve (#9) --- halo2-ecc/src/ecc/hash_to_curve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index b453b2b7..ff5233b6 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -237,7 +237,7 @@ where let p1 = self.map_to_curve_simple_swu(ctx, u0); let p2 = self.map_to_curve_simple_swu(ctx, u1); - let p_sum = self.ecc_chip.add_unequal(ctx, p1, p2, false); + let p_sum = self.ecc_chip.add_unequal(ctx, p1, p2, true); let iso_p = self.ecc_chip.isogeny_map(ctx, p_sum); From d992418fe625b565e6d22334faa2206cb20001e9 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:16:36 +0200 Subject: [PATCH 48/51] Add expand message length asserts (#10) * add expand message length assert * add more length checks * update asserts --- halo2-ecc/src/ecc/hash_to_curve.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index ff5233b6..54128da6 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -377,6 +377,12 @@ impl ExpandMessageChip for ExpandMsgXmd { len_in_bytes: usize, ) -> Result>, Error> { let gate = range.gate(); + let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; + + assert!(len_in_bytes >= 32, "Expand length must be at least 32 bytes"); + assert!(len_in_bytes <= 65535, "abort if len_in_bytes > 65535"); + assert!(dst.len() <= 255, "abort if DST len > 255"); + assert!(ell <= 255, "abort if ell > 255"); let zero = thread_pool.main().load_zero(); let one = thread_pool.main().load_constant(F::ONE); @@ -403,7 +409,6 @@ impl ExpandMessageChip for ExpandMsgXmd { .collect_vec(); // compute blocks - let ell = (len_in_bytes as f64 / HC::DIGEST_SIZE as f64).ceil() as usize; let mut b_vals = Vec::with_capacity(ell); let msg_prime = z_pad .into_iter() From 69ad067a05a4e7ab5987a850fb5af1adffe3422f Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:24:28 +0200 Subject: [PATCH 49/51] Warn user about the sign of ratio root (#12) --- halo2-ecc/src/ecc/hash_to_curve.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index 54128da6..cbd45484 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -315,6 +315,8 @@ where // Implements [Appendix F.2.1 of draft-irtf-cfrg-hash-to-curve-16][sqrt_ration] // // Assumption: `num` != 0 + // Warning: `y_assigned` returned value can be sqrt(y_sqr) and -sqrt(y_sqr). + // The sign of `y_assigned` must be constrainted at the callsite according to the composed algorithm. // // [sqrt_ration]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#appendix-F.2.1 fn sqrt_ratio( From 0fd89e1dd60c826599f50032b480ecb815958704 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:24:59 +0200 Subject: [PATCH 50/51] Revise comments & docs (#13) --- halo2-ecc/src/bls12_381/bls_signature.rs | 2 +- halo2-ecc/src/bls12_381/hash_to_curve.rs | 6 ++++-- halo2-ecc/src/ecc/hash_to_curve.rs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/halo2-ecc/src/bls12_381/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs index 690e5fe1..9324987c 100644 --- a/halo2-ecc/src/bls12_381/bls_signature.rs +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -20,7 +20,7 @@ impl<'chip, F: BigPrimeField> BlsSignatureChip<'chip, F> { Self { fp_chip, pairing_chip } } - // Verifies that e(g1, signature) = e(pubkey, H(m)) by checking e(g1, signature)*e(pubkey, -H(m)) === 1 + // 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( diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs index 4a4f31d7..2a763da5 100644 --- a/halo2-ecc/src/bls12_381/hash_to_curve.rs +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -153,14 +153,16 @@ impl<'chip, F: BigPrimeField> HashToCurveInstructions, G2> // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) let t3 = self.add_unequal(ctx, t3, t2, false); - // Ψ²(2P) - Ψ(Plet ) + [x²]P - [x]Ψ(P) + [x]P + // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P let t3 = self.sub_unequal(ctx, t3, t1, false); // Ψ²(2P) - Ψ(P) + [x²]P - [x]Ψ(P) + [x]P - 1P => [x²-x-1]P + [x-1]Ψ(P) + Ψ²(2P) self.sub_unequal(ctx, t3, p, false) } - // Optimized implementation from https://eprint.iacr.org/2017/419.pdf, 4.1 + // Specific case of `scalar * P` multiplication where scalar is [C::BLS_X] variant of [C: HashCurveExt]. + // + // This is optimized implementation from https://eprint.iacr.org/2017/419.pdf, 4.1 // Reference: https://github.com/celer-network/brevis-circuits/blob/3a7adf8/gadgets/pairing_bls12381/g2.go#L227 fn mul_by_bls_x(&self, ctx: &mut Context, p: G2Point) -> G2Point { let p2 = self.double(ctx, &p); diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index cbd45484..fd9c17f6 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -133,7 +133,7 @@ pub trait HashToCurveInstructions< p: EcPoint, ) -> EcPoint; - /// Computes `scalar * P` where scalar is [C::BLS_X] variant of [C: HashCurveExt]. + /// Specific case of `scalar * P` multiplication where scalar is [C::BLS_X] variant of [C: HashCurveExt]. fn mul_by_bls_x( &self, ctx: &mut Context, From b700180751f3be8be1399452a4975e94124d2153 Mon Sep 17 00:00:00 2001 From: timofey Date: Wed, 15 May 2024 15:25:13 +0200 Subject: [PATCH 51/51] Remove unnecessary clones (#15) --- halo2-ecc/src/bls12_381/pairing.rs | 2 +- halo2-ecc/src/ecc/hash_to_curve.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/halo2-ecc/src/bls12_381/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs index 39e08ed6..b46ebf57 100644 --- a/halo2-ecc/src/bls12_381/pairing.rs +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -429,7 +429,7 @@ impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { ) -> 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()); + let fe = fp12_chip.final_exp(ctx, mml); fe } diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs index fd9c17f6..3020b988 100644 --- a/halo2-ecc/src/ecc/hash_to_curve.rs +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -162,8 +162,8 @@ pub trait HashToCurveInstructions< let x_frob = self.field_chip().conjugate(ctx, p.x); let y_frob = self.field_chip().conjugate(ctx, p.y); - let x = self.field_chip().mul(ctx, x_frob, psi_x.clone()); - let y = self.field_chip().mul(ctx, y_frob, psi_y.clone()); + let x = self.field_chip().mul(ctx, x_frob, psi_x); + let y = self.field_chip().mul(ctx, y_frob, psi_y); EcPoint::new(x, y) } @@ -177,7 +177,7 @@ pub trait HashToCurveInstructions< // 1 / 2 ^ ((q-1)/3) let psi2_x = self.field_chip().load_constant(ctx, C::PSI2_X); - let x = self.field_chip().mul(ctx, p.x, psi2_x.clone()); + let x = self.field_chip().mul(ctx, p.x, psi2_x); let y = self.field_chip().negate(ctx, p.y); EcPoint::new(x, y)