diff --git a/Cargo.toml b/Cargo.toml index 2b1e260a..18292835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,4 @@ halo2-ecc = { path = "../halo2-lib/halo2-ecc" } [patch.crates-io] halo2-base = { path = "../halo2-lib/halo2-base" } halo2-ecc = { path = "../halo2-lib/halo2-ecc" } +halo2curves-axiom = { git = "https://github.com/timoftime/halo2curves", branch = "support_bls12-381" } diff --git a/halo2-base/src/gates/flex_gate/mod.rs b/halo2-base/src/gates/flex_gate/mod.rs index 03f952b6..cdd6e25d 100644 --- a/halo2-base/src/gates/flex_gate/mod.rs +++ b/halo2-base/src/gates/flex_gate/mod.rs @@ -590,6 +590,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. @@ -863,11 +879,13 @@ pub trait GateInstructions { range_bits: usize, ) -> Vec>; - /// Constrains and returns field representation of little-endian bit vector `bits`. - /// - /// Assumes values of `bits` are boolean. - /// * `bits`: slice of [QuantumCell]'s that contains bit representation in little-endian form - fn bits_to_num(&self, ctx: &mut Context, bits: &[AssignedValue]) -> AssignedValue; + /// Constrains and returns the number represented by the little-endian bit vector `bits`. + fn bits_to_num(&self, ctx: &mut Context, bits: &[AssignedValue]) -> AssignedValue { + assert!(bits.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`. /// diff --git a/halo2-base/src/gates/flex_gate/threads/mod.rs b/halo2-base/src/gates/flex_gate/threads/mod.rs index 675f57ab..cdbaae76 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::{utils::BigPrimeField, Context}; + +/// 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; + + /// 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 CommonCircuitBuilder 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-base/src/utils/mod.rs b/halo2-base/src/utils/mod.rs index 2aaa5166..116459e6 100644 --- a/halo2-base/src/utils/mod.rs +++ b/halo2-base/src/utils/mod.rs @@ -30,16 +30,71 @@ pub trait BigPrimeField: ScalarField { fn from_u64_digits(val: &[u64]) -> Self; } #[cfg(feature = "halo2-axiom")] -impl BigPrimeField for F -where - F: ScalarField + From<[u64; 4]>, // Assume [u64; 4] is little-endian. We only implement ScalarField when this is true. -{ - #[inline(always)] - fn from_u64_digits(val: &[u64]) -> Self { - debug_assert!(val.len() <= 4); - let mut raw = [0u64; 4]; - raw[..val.len()].copy_from_slice(val); - Self::from(raw) +mod bn256 { + use crate::halo2_proofs::halo2curves::bn256::{Fq, Fr}; + + impl super::BigPrimeField for Fr { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } + } + + impl super::BigPrimeField for Fq { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } + } +} + +#[cfg(feature = "halo2-axiom")] +mod secp256k1 { + use crate::halo2_proofs::halo2curves::secp256k1::{Fp, Fq}; + + impl super::BigPrimeField for Fp { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } + } + + impl super::BigPrimeField for Fq { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } + } +} + +#[cfg(feature = "halo2-axiom")] +mod bls12_381 { + use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fr}; + + impl super::BigPrimeField for Fr { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 4]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } + } + + impl super::BigPrimeField for Fq { + #[inline(always)] + fn from_u64_digits(val: &[u64]) -> Self { + let mut raw = [0u64; 6]; + raw[..val.len()].copy_from_slice(val); + Self::from(raw) + } } } @@ -95,7 +150,7 @@ pub trait ScalarField: PrimeField + FromUniformBytes<64> + From + Hash + O /// [ScalarField] that is ~256 bits long #[cfg(feature = "halo2-pse")] -pub trait BigPrimeField = PrimeField + ScalarField; +pub trait BigPrimeField = PrimeField + ScalarField; /// Converts an [Iterator] of u64 digits into `number_of_limbs` limbs of `bit_len` bits returned as a [Vec]. /// diff --git a/halo2-ecc/Cargo.toml b/halo2-ecc/Cargo.toml index fba53531..1c26e051 100644 --- a/halo2-ecc/Cargo.toml +++ b/halo2-ecc/Cargo.toml @@ -14,7 +14,9 @@ itertools = "0.11" num-bigint = { version = "0.4", features = ["rand"] } num-integer = "0.1" num-traits = "0.2" -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } +rand_core = { version = "0.6", default-features = false, features = [ + "getrandom", +] } rand = "0.8" rand_chacha = "0.3.1" serde = { version = "1.0", features = ["derive"] } @@ -23,6 +25,9 @@ rayon = "1.8" test-case = "3.1.0" halo2-base = { version = "=0.4.1", path = "../halo2-base", default-features = false } +# Use additional axiom-crypto halo2curves for BLS12-381 chips when [feature = "halo2-pse"] is on, +# because the PSE halo2curves does not support BLS12-381 chips and Halo2 depnds on lower major version so patching it is not possible +halo2curves = { package = "halo2curves-axiom", version = "0.5", optional=true } # plotting circuit layout plotters = { version = "0.3.0", optional = true } @@ -35,13 +40,15 @@ criterion-macro = "0.4" halo2-base = { version = "=0.4.1", path = "../halo2-base", default-features = false, features = ["test-utils"] } test-log = "0.2.12" env_logger = "0.10.0" +sha2 = "0.10" + [features] default = ["jemallocator", "halo2-axiom", "display"] dev-graph = ["halo2-base/dev-graph", "plotters"] display = ["halo2-base/display"] asm = ["halo2-base/asm"] -halo2-pse = ["halo2-base/halo2-pse"] +halo2-pse = ["halo2-base/halo2-pse", "halo2curves"] halo2-axiom = ["halo2-base/halo2-axiom"] jemallocator = ["halo2-base/jemallocator"] mimalloc = ["halo2-base/mimalloc"] diff --git a/halo2-ecc/configs/bls12_381/bench_bls_signature.config b/halo2-ecc/configs/bls12_381/bench_bls_signature.config new file mode 100644 index 00000000..6e68e0f6 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_bls_signature.config @@ -0,0 +1,8 @@ +{"strategy":"Simple","degree":15,"num_advice":105,"num_lookup_advice":14,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":16,"num_advice":50,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":18,"num_advice":13,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":20,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":19,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":21,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":20,"limb_bits":120,"num_limbs":4,"num_aggregation":2} +{"strategy":"Simple","degree":22,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":21,"limb_bits":120,"num_limbs":4,"num_aggregation":2} diff --git a/halo2-ecc/configs/bls12_381/bench_ec_add.config b/halo2-ecc/configs/bls12_381/bench_ec_add.config new file mode 100644 index 00000000..eccbbd46 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_ec_add.config @@ -0,0 +1,5 @@ +{"strategy":"Simple","degree":15,"num_advice":10,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4,"batch_size":100} +{"strategy":"Simple","degree":16,"num_advice":5,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4,"batch_size":100} +{"strategy":"Simple","degree":17,"num_advice":4,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4,"batch_size":100} +{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4,"batch_size":100} +{"strategy":"Simple","degree":19,"num_advice":1,"num_lookup_advice":0,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4,"batch_size":100} diff --git a/halo2-ecc/configs/bls12_381/bench_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/configs/bls12_381/bench_pairing.config b/halo2-ecc/configs/bls12_381/bench_pairing.config new file mode 100644 index 00000000..510a0c28 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bench_pairing.config @@ -0,0 +1,9 @@ +{"strategy":"Simple","degree":14,"num_advice":211,"num_lookup_advice":27,"num_fixed":1,"lookup_bits":13,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":15,"num_advice":105,"num_lookup_advice":14,"num_fixed":1,"lookup_bits":14,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":16,"num_advice":50,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":15,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":17,"num_advice":25,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":16,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":18,"num_advice":13,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":17,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":20,"num_advice":3,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":19,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":21,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":20,"limb_bits":120,"num_limbs":4} +{"strategy":"Simple","degree":22,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":21,"limb_bits":120,"num_limbs":4} diff --git a/halo2-ecc/configs/bls12_381/bls_signature_circuit.config b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config new file mode 100644 index 00000000..fc6686bb --- /dev/null +++ b/halo2-ecc/configs/bls12_381/bls_signature_circuit.config @@ -0,0 +1 @@ +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5,"num_aggregation":30} diff --git a/halo2-ecc/configs/bls12_381/ec_add_circuit.config b/halo2-ecc/configs/bls12_381/ec_add_circuit.config new file mode 100644 index 00000000..f5d38d53 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/ec_add_circuit.config @@ -0,0 +1 @@ +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5,"batch_size":100} diff --git a/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config b/halo2-ecc/configs/bls12_381/hash_to_curve_circuit.config new file mode 100644 index 00000000..fc6686bb --- /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":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 new file mode 100644 index 00000000..1baf52d2 --- /dev/null +++ b/halo2-ecc/configs/bls12_381/pairing_circuit.config @@ -0,0 +1 @@ +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":104,"num_limbs":5} diff --git a/halo2-ecc/src/bigint/carry_mod.rs b/halo2-ecc/src/bigint/carry_mod.rs index a9667d79..098659c2 100644 --- a/halo2-ecc/src/bigint/carry_mod.rs +++ b/halo2-ecc/src/bigint/carry_mod.rs @@ -56,11 +56,11 @@ pub fn crt( // Let n' <= quot_max_bits - n(k-1) - 1 // If quot[i] <= 2^n for i < k - 1 and quot[k-1] <= 2^{n'} then // quot < 2^{n(k-1)+1} + 2^{n' + n(k-1)} = (2+2^{n'}) 2^{n(k-1)} < 2^{n'+1} * 2^{n(k-1)} <= 2^{quot_max_bits - n(k-1)} * 2^{n(k-1)} - let quot_last_limb_bits = quot_max_bits - n * (k - 1); - + let bits_wo_last_limb: usize = n * (k - 1); + // `has_redunant_limb` will be true when native element can be represented in k-1 limbs, but some cases require an extra limb to carry. + // This is only the case for BLS12-381, which requires k=5 and n > 102 because of the check above. + let has_redunant_limb = quot_max_bits < bits_wo_last_limb; let out_max_bits = modulus.bits() as usize; - // we assume `modulus` requires *exactly* `k` limbs to represent (if `< k` limbs ok, you should just be using that) - let out_last_limb_bits = out_max_bits - n * (k - 1); // these are witness vectors: // we need to find `out_vec` as a proper BigInt with k limbs @@ -138,13 +138,24 @@ pub fn crt( // range check limbs of `out` are in [0, 2^n) except last limb should be in [0, 2^out_last_limb_bits) for (out_index, out_cell) in out_assigned.iter().enumerate() { - let limb_bits = if out_index == k - 1 { out_last_limb_bits } else { n }; + if has_redunant_limb && out_index == k - 1 { + let zero = ctx.load_zero(); + ctx.constrain_equal(out_cell, &zero); + continue; + } + // we assume `modulus` requires *exactly* `k` limbs to represent (if `< k` limbs ok, you should just be using that) + let limb_bits = if out_index == k - 1 { out_max_bits - bits_wo_last_limb } else { n }; range.range_check(ctx, *out_cell, limb_bits); } // range check that quot_cell in quot_assigned is in [-2^n, 2^n) except for last cell check it's in [-2^quot_last_limb_bits, 2^quot_last_limb_bits) for (q_index, quot_cell) in quot_assigned.iter().enumerate() { - let limb_bits = if q_index == k - 1 { quot_last_limb_bits } else { n }; + if has_redunant_limb && q_index == k - 1 { + let zero = ctx.load_zero(); + ctx.constrain_equal(quot_cell, &zero); + continue; + } + let limb_bits = if q_index == k - 1 { quot_max_bits - bits_wo_last_limb } else { n }; let limb_base = if q_index == k - 1 { range.gate().pow_of_two()[limb_bits] } else { limb_bases[1] }; diff --git a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs index 13523ba5..26af73af 100644 --- a/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs +++ b/halo2-ecc/src/bigint/check_carry_mod_to_zero.rs @@ -34,7 +34,10 @@ pub fn crt( // see carry_mod.rs for explanation let quot_max_bits = trunc_len - 1 + (F::NUM_BITS as usize) - 1 - (modulus.bits() as usize); assert!(quot_max_bits < trunc_len); - let quot_last_limb_bits = quot_max_bits - n * (k - 1); + let bits_wo_last_limb: usize = n * (k - 1); + // `has_redunant_limb` will be true when native element can be represented in k-1 limbs, but some cases require an extra limb to carry. + // This is only the case for BLS12-381, which requires k=5 and n > 102 because of the check above. + let has_redunant_limb = quot_max_bits < bits_wo_last_limb; // these are witness vectors: // we need to find `quot_vec` as a proper BigInt with k limbs @@ -90,7 +93,12 @@ pub fn crt( // range check that quot_cell in quot_assigned is in [-2^n, 2^n) except for last cell check it's in [-2^quot_last_limb_bits, 2^quot_last_limb_bits) for (q_index, quot_cell) in quot_assigned.iter().enumerate() { - let limb_bits = if q_index == k - 1 { quot_last_limb_bits } else { n }; + if has_redunant_limb && q_index == k - 1 { + let zero = ctx.load_zero(); + ctx.constrain_equal(quot_cell, &zero); + continue; + } + let limb_bits = if q_index == k - 1 { quot_max_bits - n * (k - 1) } else { n }; let limb_base = if q_index == k - 1 { range.gate().pow_of_two()[limb_bits] } else { limb_bases[1] }; diff --git a/halo2-ecc/src/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..e921bc90 --- /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 || bits == 8 || limb_bits == 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/bls_signature.rs b/halo2-ecc/src/bls12_381/bls_signature.rs new file mode 100644 index 00000000..9324987c --- /dev/null +++ b/halo2-ecc/src/bls12_381/bls_signature.rs @@ -0,0 +1,87 @@ +use std::ops::Neg; + +use super::pairing::PairingChip; +use super::{Fp12Chip, FpChip}; +use super::{Fq12, G1Affine, G2Affine}; +use crate::bigint::ProperCrtUint; +use crate::ecc::{EcPoint, EccChip}; +use crate::fields::vector::FieldVector; +use crate::fields::FieldChip; +use halo2_base::utils::BigPrimeField; +use halo2_base::{AssignedValue, Context}; + +pub struct BlsSignatureChip<'chip, F: BigPrimeField> { + pub fp_chip: &'chip FpChip<'chip, F>, + pub pairing_chip: &'chip PairingChip<'chip, F>, +} + +impl<'chip, F: BigPrimeField> BlsSignatureChip<'chip, F> { + pub fn new(fp_chip: &'chip FpChip, pairing_chip: &'chip PairingChip) -> Self { + Self { fp_chip, pairing_chip } + } + + // 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); + } + + /// Verifies BLS signature and returns assigned selector. + pub fn is_valid_signature( + &self, + ctx: &mut Context, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + ) -> AssignedValue { + let g1_chip = EccChip::new(self.fp_chip); + + let g1_neg = g1_chip.assign_constant_point(ctx, G1Affine::generator().neg()); + + let gt = self.compute_pairing(ctx, signature, msghash, pubkey, g1_neg); + + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + + fp12_chip.is_equal(ctx, gt, fp12_one) + } + + /// Verifies BLS signature with equality check. + pub fn assert_valid_signature( + &self, + ctx: &mut Context, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + ) { + let g1_chip = EccChip::new(self.fp_chip); + let g1_neg = g1_chip.assign_constant_point(ctx, G1Affine::generator().neg()); + + let gt = self.compute_pairing(ctx, signature, msghash, pubkey, g1_neg); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + fp12_chip.assert_equal(ctx, gt, fp12_one); + } + + fn compute_pairing( + &self, + ctx: &mut Context, + signature: EcPoint>>, + msghash: EcPoint>>, + pubkey: EcPoint>, + g1_neg: EcPoint>, + ) -> FieldVector> { + self.pairing_chip.batched_pairing(ctx, &[(&g1_neg, &signature), (&pubkey, &msghash)]) + } +} diff --git a/halo2-ecc/src/bls12_381/final_exp.rs b/halo2-ecc/src/bls12_381/final_exp.rs new file mode 100644 index 00000000..5020ea73 --- /dev/null +++ b/halo2-ecc/src/bls12_381/final_exp.rs @@ -0,0 +1,333 @@ +use super::XI_0; +use super::{Fp12Chip, Fp2Chip, FpChip, FqPoint}; +use super::{Fq, Fq12, Fq2, FROBENIUS_COEFF_FQ12_C1}; +use crate::fields::FieldChipExt; +use crate::fields::{fp12::mul_no_carry_w6, vector::FieldVector, FieldChip}; +use halo2_base::utils::BigPrimeField; +use halo2_base::{gates::GateInstructions, utils::modulus, Context, QuantumCell::Constant}; +use num_bigint::BigUint; + +impl<'chip, F: BigPrimeField> Fp12Chip<'chip, F> { + // computes a ** (p ** power) + // only works for p = 3 (mod 4) and p = 1 (mod 6) + pub fn frobenius_map( + &self, + ctx: &mut Context, + a: &>::FieldPoint, + power: usize, + ) -> >::FieldPoint { + assert_eq!(modulus::() % 4u64, BigUint::from(3u64)); + assert_eq!(modulus::() % 6u64, BigUint::from(1u64)); + assert_eq!(a.0.len(), 12); + let pow = power % 12; + let mut out_fp2 = Vec::with_capacity(6); + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + for i in 0..6 { + let frob_coeff = FROBENIUS_COEFF_FQ12_C1[pow].pow_vartime(&[i as u64]); + // possible optimization (not implemented): load `frob_coeff` as we multiply instead of loading first + // frobenius map is used infrequently so this is a small optimization + + let mut a_fp2 = FieldVector(vec![a[i].clone(), a[i + 6].clone()]); + if pow % 2 != 0 { + a_fp2 = fp2_chip.conjugate(ctx, a_fp2); + } + // if `frob_coeff` is in `Fp` and not just `Fp2`, then we can be more efficient in multiplication + if frob_coeff == Fq2::one() { + out_fp2.push(a_fp2); + } else if frob_coeff.c1 == Fq::zero() { + let frob_fixed = fp_chip.load_constant(ctx, frob_coeff.c0); + { + let out_nocarry = fp2_chip.0.fp_mul_no_carry(ctx, a_fp2, frob_fixed); + out_fp2.push(fp2_chip.carry_mod(ctx, out_nocarry)); + } + } else { + let frob_fixed = fp2_chip.load_constant(ctx, frob_coeff); + out_fp2.push(fp2_chip.mul(ctx, a_fp2, frob_fixed)); + } + } + + let out_coeffs = out_fp2 + .iter() + .map(|x| x[0].clone()) + .chain(out_fp2.iter().map(|x| x[1].clone())) + .collect(); + + FieldVector(out_coeffs) + } + + // assume input is an element of Fp12 in the cyclotomic subgroup GΦ₁₂ + // A cyclotomic group is a subgroup of Fp^n defined by + // GΦₙ(p) = {α ∈ Fpⁿ : α^{Φₙ(p)} = 1} + + // below we implement compression and decompression for an element GΦ₁₂ following Theorem 3.1 of https://eprint.iacr.org/2010/542.pdf + // Fp4 = Fp2(w^3) where (w^3)^2 = XI_0 +u + // Fp12 = Fp4(w) where w^3 = w^3 + + /// in = g0 + g2 w + g4 w^2 + g1 w^3 + g3 w^4 + g5 w^5 where g_i = g_i0 + g_i1 * u are elements of Fp2 + /// out = Compress(in) = [ g2, g3, g4, g5 ] + pub fn cyclotomic_compress(&self, a: &FqPoint) -> Vec> { + let a = &a.0; + let g2 = FieldVector(vec![a[1].clone(), a[1 + 6].clone()]); + let g3 = FieldVector(vec![a[4].clone(), a[4 + 6].clone()]); + let g4 = FieldVector(vec![a[2].clone(), a[2 + 6].clone()]); + let g5 = FieldVector(vec![a[5].clone(), a[5 + 6].clone()]); + vec![g2, g3, g4, g5] + } + + /// Input: + /// * `compression = [g2, g3, g4, g5]` where g_i are proper elements of Fp2 + /// Output: + /// * `Decompress(compression) = g0 + g2 w + g4 w^2 + g1 w^3 + g3 w^4 + g5 w^5` where + /// * All elements of output are proper elements of Fp2 and: + /// c = XI0 + u + /// if g2 != 0: + /// g1 = (g5^2 * c + 3 g4^2 - 2 g3)/(4g2) + /// g0 = (2 g1^2 + g2 * g5 - 3 g3*g4) * c + 1 + /// if g2 = 0: + /// g1 = (2 g4 * g5)/g3 + /// g0 = (2 g1^2 - 3 g3 * g4) * c + 1 + pub fn cyclotomic_decompress( + &self, + ctx: &mut Context, + compression: Vec>, + ) -> FqPoint { + let [g2, g3, g4, g5]: [_; 4] = compression.try_into().unwrap(); + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + let g5_sq = fp2_chip.mul_no_carry(ctx, &g5, &g5); + let g5_sq_c = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, g5_sq); + + let g4_sq = fp2_chip.mul_no_carry(ctx, &g4, &g4); + let g4_sq_3 = fp2_chip.scalar_mul_no_carry(ctx, &g4_sq, 3); + let g3_2 = fp2_chip.scalar_mul_no_carry(ctx, &g3, 2); + + let mut g1_num = fp2_chip.add_no_carry(ctx, &g5_sq_c, &g4_sq_3); + g1_num = fp2_chip.sub_no_carry(ctx, &g1_num, &g3_2); + // can divide without carrying g1_num or g1_denom (I think) + let g2_4 = fp2_chip.scalar_mul_no_carry(ctx, &g2, 4); + let g1_1 = fp2_chip.divide_unsafe(ctx, &g1_num, &g2_4); + + let g4_g5 = fp2_chip.mul_no_carry(ctx, &g4, &g5); + let g1_num = fp2_chip.scalar_mul_no_carry(ctx, &g4_g5, 2); + let g1_0 = fp2_chip.divide_unsafe(ctx, &g1_num, &g3); + + let g2_is_zero = fp2_chip.is_zero(ctx, &g2); + // resulting `g1` is already in "carried" format (witness is in `[0, p)`) + let g1 = fp2_chip.0.select(ctx, g1_0, g1_1, g2_is_zero); + + // share the computation of 2 g1^2 between the two cases + let g1_sq = fp2_chip.mul_no_carry(ctx, &g1, &g1); + let g1_sq_2 = fp2_chip.scalar_mul_no_carry(ctx, &g1_sq, 2); + + let g2_g5 = fp2_chip.mul_no_carry(ctx, &g2, &g5); + let g3_g4 = fp2_chip.mul_no_carry(ctx, &g3, &g4); + let g3_g4_3 = fp2_chip.scalar_mul_no_carry(ctx, &g3_g4, 3); + let temp = fp2_chip.add_no_carry(ctx, &g1_sq_2, &g2_g5); + let temp = fp2_chip.0.select(ctx, g1_sq_2, temp, g2_is_zero); + let temp = fp2_chip.sub_no_carry(ctx, &temp, &g3_g4_3); + let mut g0 = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, temp); + + // compute `g0 + 1` + g0[0].truncation.limbs[0] = + fp2_chip.gate().add(ctx, g0[0].truncation.limbs[0], Constant(F::ONE)); + g0[0].native = fp2_chip.gate().add(ctx, g0[0].native, Constant(F::ONE)); + g0[0].truncation.max_limb_bits += 1; + g0[0].value += 1usize; + + // finally, carry g0 + let g0 = fp2_chip.carry_mod(ctx, g0); + + let mut g0 = g0.into_iter(); + let mut g1 = g1.into_iter(); + let mut g2 = g2.into_iter(); + let mut g3 = g3.into_iter(); + let mut g4 = g4.into_iter(); + let mut g5 = g5.into_iter(); + + let mut out_coeffs = Vec::with_capacity(12); + for _ in 0..2 { + out_coeffs.append(&mut vec![ + g0.next().unwrap(), + g2.next().unwrap(), + g4.next().unwrap(), + g1.next().unwrap(), + g3.next().unwrap(), + g5.next().unwrap(), + ]); + } + FieldVector(out_coeffs) + } + + // input is [g2, g3, g4, g5] = C(g) in compressed format of `cyclotomic_compress` + // assume all inputs are proper Fp2 elements + // output is C(g^2) = [h2, h3, h4, h5] computed using Theorem 3.2 of https://eprint.iacr.org/2010/542.pdf + // all output elements are proper Fp2 elements (with carry) + // c = XI_0 + u + // h2 = 2(g2 + 3*c*B_45) + // h3 = 3(A_45 - (c+1)B_45) - 2g3 + // h4 = 3(A_23 - (c+1)B_23) - 2g4 + // h5 = 2(g5 + 3B_23) + // A_ij = (g_i + g_j)(g_i + c g_j) + // B_ij = g_i g_j + pub fn cyclotomic_square( + &self, + ctx: &mut Context, + compression: &[FqPoint], + ) -> Vec> { + assert_eq!(compression.len(), 4); + let g2 = &compression[0]; + let g3 = &compression[1]; + let g4 = &compression[2]; + let g5 = &compression[3]; + + let fp_chip = self.fp_chip(); + let fp2_chip = Fp2Chip::::new(fp_chip); + + let g2_plus_g3 = fp2_chip.add_no_carry(ctx, g2, g3); + let cg3 = mul_no_carry_w6::, XI_0>(fp_chip, ctx, g3.into()); + let g2_plus_cg3 = fp2_chip.add_no_carry(ctx, g2, &cg3); + let a23 = fp2_chip.mul_no_carry(ctx, &g2_plus_g3, &g2_plus_cg3); + + let g4_plus_g5 = fp2_chip.add_no_carry(ctx, g4, g5); + let cg5 = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, g5.into()); + let g4_plus_cg5 = fp2_chip.add_no_carry(ctx, g4, &cg5); + let a45 = fp2_chip.mul_no_carry(ctx, &g4_plus_g5, &g4_plus_cg5); + + let b23 = fp2_chip.mul_no_carry(ctx, g2, g3); + let b45 = fp2_chip.mul_no_carry(ctx, g4, g5); + let b45_c = mul_no_carry_w6::<_, _, XI_0>(fp_chip, ctx, b45.clone()); + + let mut temp = fp2_chip.scalar_mul_and_add_no_carry(ctx, &b45_c, g2, 3); + let h2 = fp2_chip.scalar_mul_no_carry(ctx, &temp, 2); + + temp = fp2_chip.add_no_carry(ctx, b45_c, b45); + temp = fp2_chip.sub_no_carry(ctx, &a45, temp); + temp = fp2_chip.scalar_mul_no_carry(ctx, temp, 3); + let h3 = fp2_chip.scalar_mul_and_add_no_carry(ctx, g3, temp, -2); + + const XI0_PLUS_1: i64 = XI_0 + 1; + // (c + 1) = (XI_0 + 1) + u + temp = mul_no_carry_w6::, XI0_PLUS_1>(fp_chip, ctx, b23.clone()); + temp = fp2_chip.sub_no_carry(ctx, &a23, temp); + temp = fp2_chip.scalar_mul_no_carry(ctx, temp, 3); + let h4 = fp2_chip.scalar_mul_and_add_no_carry(ctx, g4, temp, -2); + + temp = fp2_chip.scalar_mul_and_add_no_carry(ctx, b23, g5, 3); + let h5 = fp2_chip.scalar_mul_no_carry(ctx, temp, 2); + + [h2, h3, h4, h5].into_iter().map(|h| fp2_chip.carry_mod(ctx, h)).collect() + } + + fn cyclotomic_square_for(&self, ctx: &mut Context, a: &FqPoint, n: usize) -> FqPoint { + let mut tv = self.cyclotomic_compress(a); + for _ in 0..n { + tv = self.cyclotomic_square(ctx, &tv); + } + self.cyclotomic_decompress(ctx, tv) + } + + /// # Assumptions + /// * `a` is a nonzero element in the cyclotomic subgroup + pub fn cyclotomic_pow(&self, ctx: &mut Context, a: FqPoint, exp: u64) -> FqPoint { + let mut res = self.load_private(ctx, Fq12::one()); + let mut found_one = false; + + for bit in (0..64).rev().map(|i| ((exp >> i) & 1) == 1) { + if found_one { + let compressed = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&res)); + res = self.cyclotomic_decompress(ctx, compressed); + } else { + found_one = bit; + } + + if bit { + res = self.mul(ctx, &res, &a); + } + } + + self.conjugate(ctx, res) + } + + // Optimized implementation of cyclotomic_pow on BLS_X for BLS12-381 + // Reference: https://github.com/celer-network/brevis-circuits/blob/fe7936f7f/gadgets/pairing_bls12381/tower.go#L801 + fn cyclotomic_pow_bls_x(&self, ctx: &mut Context, a: &FqPoint) -> FqPoint { + let mut tv = self.cyclotomic_compress(a); + for _ in 0..15 { + tv = self.cyclotomic_square(ctx, &tv); + } + let t0 = self.cyclotomic_decompress(ctx, tv.clone()); + + for _ in 0..32 { + tv = self.cyclotomic_square(ctx, &tv); + } + let t1 = self.cyclotomic_decompress(ctx, tv); + + let mut res = self.mul(ctx, &t0, &t1); + let mut t1 = self.cyclotomic_square_for(ctx, &t1, 9); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 3); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 2); + + res = self.mul(ctx, &res, &t1); + t1 = self.cyclotomic_square_for(ctx, &t1, 1); + + res = self.mul(ctx, &res, &t1); + res = self.conjugate(ctx, res); + + self.cyclotomic_square_for(ctx, &res, 1) + } + + // out = in^{(q^12 - 1)/r} + pub fn final_exp( + &self, + ctx: &mut Context, + a: >::FieldPoint, + ) -> >::FieldPoint { + // a^{q^6} = conjugate of a + let f1 = self.conjugate(ctx, a.clone()); + let f2 = self.divide_unsafe(ctx, &f1, a); + let f3 = self.frobenius_map(ctx, &f2, 2); + + let t2 = self.mul(ctx, &f3, &f2); + let t1 = { + let tv = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&t2)); + let tv = self.cyclotomic_decompress(ctx, tv); + self.conjugate(ctx, tv) + }; + let t3 = self.cyclotomic_pow_bls_x(ctx, &t2); + let t4 = { + let tv = self.cyclotomic_square(ctx, &self.cyclotomic_compress(&t3)); + self.cyclotomic_decompress(ctx, tv) + }; + + let t5 = self.mul(ctx, &t1, &t3); + let t1 = self.cyclotomic_pow_bls_x(ctx, &t5); + + let t0 = self.cyclotomic_pow_bls_x(ctx, &t1.clone()); + + let t6 = self.cyclotomic_pow_bls_x(ctx, &t0.clone()); + let t6 = self.mul(ctx, &t6, &t4); + let t4 = self.cyclotomic_pow_bls_x(ctx, &t6.clone()); + let t5 = self.conjugate(ctx, t5); + let t4 = self.mul(ctx, &t4, &t5); + let t4 = self.mul(ctx, &t4, &t2); + let t5 = self.conjugate(ctx, t2.clone()); + let t1 = self.mul(ctx, &t1, &t2); + + let t1 = self.frobenius_map(ctx, &t1, 3); + let t6 = self.mul(ctx, &t6, &t5); + let t6 = self.frobenius_map(ctx, &t6, 1); + let t3 = self.mul(ctx, &t3, &t0); + let t3 = self.frobenius_map(ctx, &t3, 2); + let t3 = self.mul(ctx, &t3, &t1); + let t3 = self.mul(ctx, &t3, &t6); + + self.mul(ctx, &t3, &t4) + } +} diff --git a/halo2-ecc/src/bls12_381/hash_to_curve.rs b/halo2-ecc/src/bls12_381/hash_to_curve.rs new file mode 100644 index 00000000..2a763da5 --- /dev/null +++ b/halo2-ecc/src/bls12_381/hash_to_curve.rs @@ -0,0 +1,503 @@ +//! 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::{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, +}; +use crate::ff::Field; +use crate::halo2_base::{Context, QuantumCell}; +use crate::halo2_proofs::plonk::Error; +use crate::{ + ecc::EccChip, + fields::{vector::FieldVector, FieldChip}, +}; +use halo2_base::gates::flex_gate::threads::CommonCircuitBuilder; +use halo2_base::gates::RangeInstructions; +use halo2_base::utils::BigPrimeField; +use itertools::Itertools; + +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> HashToCurveInstructions, G2> + for EccChip<'chip, F, Fp2Chip<'chip, F>> +{ + 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 + /// + /// 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, XC: ExpandMessageChip>( + &self, + thread_pool: &mut HC::CircuitBuilder, + hash_chip: &HC, + msg: impl Iterator>, + dst: &[u8], + ) -> Result<[Fp2Point; 2], Error> { + let fp_chip = self.field_chip().fp_chip(); + let range = fp_chip.range(); + let gate = range.gate(); + + let extended_msg = + XC::expand_message(thread_pool, hash_chip, range, msg, dst, 2 * G2_EXT_DEGREE * L)?; + + let ctx = thread_pool.main(); + + // 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) + .chunks(G2_EXT_DEGREE) + .into_iter() + .map(|elm_chunk| { + FieldVector( + elm_chunk + .map(|tv| { + let y = decode_into_bn::( + ctx, + gate, + tv.iter().copied().rev().take(64).collect_vec(), + &limb_bases, + fp_chip.limb_bits(), + ); + + let y: CRTInteger = y.into(); + + fp_chip.carry_mod(ctx, y) + }) + .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(ctx, x_num, x_den) }; + + let y = { + let tv = fp2_chip.divide(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 = { + 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) - Ψ(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) + } + + // 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); + let mut 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 { + #[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; + + impl HashCurveExt for G2 { + 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(), + }; + } +} diff --git a/halo2-ecc/src/bls12_381/mod.rs b/halo2-ecc/src/bls12_381/mod.rs new file mode 100644 index 00000000..3749b61d --- /dev/null +++ b/halo2-ecc/src/bls12_381/mod.rs @@ -0,0 +1,33 @@ +use crate::bigint::ProperCrtUint; +use crate::ecc::EcPoint; +use crate::fields::vector::FieldVector; +use crate::fields::{fp, fp12, fp2}; + +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>; +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/pairing.rs b/halo2-ecc/src/bls12_381/pairing.rs new file mode 100644 index 00000000..b46ebf57 --- /dev/null +++ b/halo2-ecc/src/bls12_381/pairing.rs @@ -0,0 +1,458 @@ +#![allow(non_snake_case)] +use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint, XI_0}; +use super::{Fq12, G1Affine, G2Affine, BLS_X, BLS_X_IS_NEGATIVE}; +use crate::fields::vector::FieldVector; +use crate::{ + ecc::{EcPoint, EccChip}, + fields::fp12::mul_no_carry_w6, + fields::FieldChip, +}; + +use halo2_base::utils::BigPrimeField; +use halo2_base::Context; + +// Inputs: +// Q0 = (x_1, y_1) and Q1 = (x_2, y_2) are points in E(Fp2) +// P is point (X, Y) in E(Fp) +// Assuming Q0 != Q1 +// Output: +// line_{Psi(Q0), Psi(Q1)}(P) where Psi(x,y) = (w^2 x, w^3 y) +// - equals w^3 (y_1 - y_2) X + w^2 (x_2 - x_1) Y + w^5 (x_1 y_2 - x_2 y_1) =: out3 * w^3 + out2 * w^2 + out5 * w^5 where out2, out3, out5 are Fp2 points +// Output is [None, None, out2, out3, None, out5] as vector of `Option`s +pub fn sparse_line_function_unequal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + Q: (&EcPoint>, &EcPoint>), + P: &EcPoint>, +) -> Vec>> { + let (x_1, y_1) = (&Q.0.x, &Q.0.y); + let (x_2, y_2) = (&Q.1.x, &Q.1.y); + let (X, Y) = (&P.x, &P.y); + assert_eq!(x_1.0.len(), 2); + assert_eq!(y_1.0.len(), 2); + assert_eq!(x_2.0.len(), 2); + assert_eq!(y_2.0.len(), 2); + + let y1_minus_y2 = fp2_chip.sub_no_carry(ctx, y_1, y_2); + let x2_minus_x1 = fp2_chip.sub_no_carry(ctx, x_2, x_1); + let x1y2 = fp2_chip.mul_no_carry(ctx, x_1, y_2); + let x2y1 = fp2_chip.mul_no_carry(ctx, x_2, y_1); + + let out3 = fp2_chip.0.fp_mul_no_carry(ctx, y1_minus_y2, X); + let out4 = fp2_chip.0.fp_mul_no_carry(ctx, x2_minus_x1, Y); + let out2 = fp2_chip.sub_no_carry(ctx, &x1y2, &x2y1); + + // so far we have not "carried mod p" for any of the outputs + // we do this below + [None, Some(out2), None, Some(out3), Some(out4), None] + .into_iter() + .map(|option_nc| option_nc.map(|nocarry| fp2_chip.carry_mod(ctx, nocarry))) + .collect() +} + +// Assuming curve is of form Y^2 = X^3 + b (a = 0) to save operations +// Inputs: +// Q = (x, y) is a point in E(Fp) +// P = (P.x, P.y) in E(Fp2) +// Output: +// line_{Psi(Q), Psi(Q)}(P) where Psi(x,y) = (w^2 x, w^3 y) +// - equals (3x^3 - 2y^2)(XI_0 + u) + w^4 (-3 x^2 * Q.x) + w^3 (2 y * Q.y) =: out0 + out4 * w^4 + out3 * w^3 where out0, out3, out4 are Fp2 points +// Output is [out0, None, out2, out3, None, None] as vector of `Option`s +pub fn sparse_line_function_equal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, +) -> Vec>> { + let (x, y) = (&Q.x, &Q.y); + assert_eq!(x.0.len(), 2); + assert_eq!(y.0.len(), 2); + + let x_sq = fp2_chip.mul(ctx, x, x); + + let x_cube = fp2_chip.mul_no_carry(ctx, &x_sq, x); + let three_x_cu = fp2_chip.scalar_mul_no_carry(ctx, &x_cube, 3); + let y_sq = fp2_chip.mul_no_carry(ctx, y, y); + let two_y_sq = fp2_chip.scalar_mul_no_carry(ctx, &y_sq, 2); + let out0 = fp2_chip.sub_no_carry(ctx, &three_x_cu, &two_y_sq); + + let x_sq_Px = fp2_chip.0.fp_mul_no_carry(ctx, x_sq, &P.x); + let out2 = fp2_chip.scalar_mul_no_carry(ctx, x_sq_Px, -3); + + let y_Py = fp2_chip.0.fp_mul_no_carry(ctx, y.clone(), &P.y); + let out3 = fp2_chip.scalar_mul_no_carry(ctx, &y_Py, 2); + + // so far we have not "carried mod p" for any of the outputs + // we do this below + [Some(out0), None, Some(out2), Some(out3), None, None] + .into_iter() + .map(|option_nc| option_nc.map(|nocarry| fp2_chip.carry_mod(ctx, nocarry))) + .collect() +} + +// multiply Fp12 point `a` with Fp12 point `b` where `b` is len 6 vector of Fp2 points, where some are `None` to represent zero. +// Assumes `b` is not vector of all `None`s +pub fn sparse_fp12_multiply( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + a: &FqPoint, + b_fp2_coeffs: &[Option>], +) -> FqPoint { + assert_eq!(a.0.len(), 12); + assert_eq!(b_fp2_coeffs.len(), 6); + let mut a_fp2_coeffs = Vec::with_capacity(6); + for i in 0..6 { + a_fp2_coeffs.push(FieldVector(vec![a[i].clone(), a[i + 6].clone()])); + } + // a * b as element of Fp2[w] without evaluating w^6 = (XI_0 + u) + let mut prod_2d = vec![None; 11]; + for i in 0..6 { + for j in 0..6 { + prod_2d[i + j] = + match (prod_2d[i + j].clone(), &a_fp2_coeffs[i], b_fp2_coeffs[j].as_ref()) { + (a, _, None) => a, + (None, a, Some(b)) => { + let ab = fp2_chip.mul_no_carry(ctx, a, b); + Some(ab) + } + (Some(a), b, Some(c)) => { + let bc = fp2_chip.mul_no_carry(ctx, b, c); + let out = fp2_chip.add_no_carry(ctx, &a, &bc); + Some(out) + } + }; + } + } + + let mut out_fp2 = Vec::with_capacity(6); + for i in 0..6 { + // prod_2d[i] + prod_2d[i+6] * w^6 + let prod_nocarry = if i != 5 { + let eval_w6 = prod_2d[i + 6] + .as_ref() + .map(|a| mul_no_carry_w6::<_, _, XI_0>(fp2_chip.fp_chip(), ctx, a.clone())); + match (prod_2d[i].as_ref(), eval_w6) { + (None, b) => b.unwrap(), // Our current use cases of 235 and 034 sparse multiplication always result in non-None value + (Some(a), None) => a.clone(), + (Some(a), Some(b)) => fp2_chip.add_no_carry(ctx, a, &b), + } + } else { + prod_2d[i].clone().unwrap() + }; + let prod = fp2_chip.carry_mod(ctx, prod_nocarry); + out_fp2.push(prod); + } + + let mut out_coeffs = Vec::with_capacity(12); + for fp2_coeff in &out_fp2 { + out_coeffs.push(fp2_coeff[0].clone()); + } + for fp2_coeff in &out_fp2 { + out_coeffs.push(fp2_coeff[1].clone()); + } + FieldVector(out_coeffs) +} + +// Input: +// - g is Fp12 point +// - P = (P0, P1) with Q0, Q1 points in E(Fp2) +// - Q is point in E(Fp) +// Output: +// - out = g * l_{Psi(Q0), Psi(Q1)}(P) as Fp12 point +pub fn fp12_multiply_with_line_unequal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + g: &FqPoint, + P: &EcPoint>, + Q: (&EcPoint>, &EcPoint>), +) -> FqPoint { + let line = sparse_line_function_unequal::(fp2_chip, ctx, Q, P); + sparse_fp12_multiply::(fp2_chip, ctx, g, &line) +} + +// Input: +// - g is Fp12 point +// - P is point in E(Fp2) +// - Q is point in E(Fp) +// Output: +// - out = g * l_{Psi(Q), Psi(Q)}(P) as Fp12 point +pub fn fp12_multiply_with_line_equal( + fp2_chip: &Fp2Chip, + ctx: &mut Context, + g: &FqPoint, + P: &EcPoint>, + Q: &EcPoint>, +) -> FqPoint { + let line = sparse_line_function_equal::(fp2_chip, ctx, P, Q); + sparse_fp12_multiply::(fp2_chip, ctx, g, &line) +} + +// Assuming curve is of form `y^2 = x^3 + b` for now (a = 0) for less operations +// Value of `b` is never used +// Inputs: +// - Q = (x, y) is a point in E(Fp2) +// - P is a point in E(Fp) +// Output: +// - f_{loop_count}(Q,P) * l_{[loop_count] Q', Frob_p(Q')}(P) * l_{[loop_count] Q' + Frob_p(Q'), -Frob_p^2(Q')}(P) +// - where we start with `f_1(Q,P) = 1` and use Miller's algorithm f_{i+j} = f_i * f_j * l_{i,j}(Q,P) +// - Q' = Psi(Q) in E(Fp12) +// - Frob_p(x,y) = (x^p, y^p) +pub fn miller_loop( + ecc_chip: &EccChip>, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, +) -> FqPoint { + let sparse_f = sparse_line_function_equal::(ecc_chip.field_chip(), ctx, P, Q); + assert_eq!(sparse_f.len(), 6); + + let fp_chip = ecc_chip.field_chip.fp_chip(); + let zero_fp = fp_chip.load_constant(ctx, Fq::zero()); + let mut f_coeffs = Vec::with_capacity(12); + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[0].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[1].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + + let mut f = FieldVector(f_coeffs); + let fp12_chip = Fp12Chip::::new(fp_chip); + + let mut r = Q.clone(); + + let mut found_one = true; + let mut prev_bit = true; + + // double Q as in the first part of Miller loop + r = ecc_chip.double(ctx, r.clone()); + + // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) + // resequence the loop to perfrom additiona step for the previous iteration first and then doubling step + for bit in (0..62).rev().map(|i| ((BLS_X >> i) & 1) == 1) { + if prev_bit { + f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, P, (&r, Q)); + r = ecc_chip.add_unequal(ctx, r.clone(), Q.clone(), false); + } + + prev_bit = bit; + + if !found_one { + found_one = bit; + continue; + } + + f = fp12_chip.mul(ctx, &f, &f); + + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, P, &r); + r = ecc_chip.double(ctx, r.clone()); + } + + if BLS_X_IS_NEGATIVE { + f = fp12_chip.conjugate(ctx, f) + } + + f +} + +// let pairs = [(a_i, b_i)], a_i in G_1, b_i in G_2 +// output is Prod_i e'(a_i, b_i), where e'(a_i, b_i) is the output of `miller_loop_BN(b_i, a_i)` +pub fn multi_miller_loop( + ecc_chip: &EccChip>, + ctx: &mut Context, + pairs: Vec<(&EcPoint>, &EcPoint>)>, +) -> FqPoint { + let fp_chip = ecc_chip.field_chip.fp_chip(); + + // initialize the first line function into Fq12 point with first (Q,P) pair + // this is to skip first iteration by leveraging the fact that f = 1 + let mut f = { + let sparse_f = + sparse_line_function_equal::(ecc_chip.field_chip(), ctx, pairs[0].0, pairs[0].1); + assert_eq!(sparse_f.len(), 6); + + let zero_fp = fp_chip.load_constant(ctx, Fq::zero()); + let mut f_coeffs = Vec::with_capacity(12); + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[0].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + for coeff in &sparse_f { + if let Some(fp2_point) = coeff { + f_coeffs.push(fp2_point[1].clone()); + } else { + f_coeffs.push(zero_fp.clone()); + } + } + FieldVector(f_coeffs) + }; + + // process second (Q,P) pair + for &(p, q) in pairs.iter().skip(1) { + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, p, q); + } + + let fp12_chip = Fp12Chip::::new(fp_chip); + + let mut r = pairs.iter().map(|pair| pair.1.clone()).collect::>(); + let mut found_one = true; + let mut prev_bit = true; + + // double Q as in the first part of Miller loop + for r in r.iter_mut() { + *r = ecc_chip.double(ctx, r.clone()); + } + + // skip two bits after init (first beacuse f = 1, second because 1-ft found_one = false) + // resequence the loop to perfrom additiona step for the previous iteration first and then doubling step + for bit in (0..62).rev().map(|i| ((BLS_X >> i) & 1) == 1) { + if prev_bit { + for (r, &(p, q)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_unequal::(ecc_chip.field_chip(), ctx, &f, p, (r, q)); + *r = ecc_chip.add_unequal(ctx, r.clone(), q.clone(), false); + } + } + + prev_bit = bit; + + if !found_one { + found_one = bit; + continue; + } + + f = fp12_chip.mul(ctx, &f, &f); + + for (r, &(p, _q)) in r.iter_mut().zip(pairs.iter()) { + f = fp12_multiply_with_line_equal::(ecc_chip.field_chip(), ctx, &f, p, r); + *r = ecc_chip.double(ctx, r.clone()); + } + } + + // Apperently Gt conjugation can be skipped for multi miller loop. However, cannot find evidence for this. + // if BLS_X_IS_NEGATIVE { + // f = fp12_chip.conjugate(ctx, f) + // } + + f +} + +/// Pairing chip for BLS12-381. +/// To avoid issues with mutably borrowing twice (not allowed in Rust), we only store `fp_chip` and construct `g2_chip` in scope when needed for temporary mutable borrows +pub struct PairingChip<'chip, F: BigPrimeField> { + pub fp_chip: &'chip FpChip<'chip, F>, +} + +impl<'chip, F: BigPrimeField> PairingChip<'chip, F> { + pub fn new(fp_chip: &'chip FpChip) -> Self { + Self { fp_chip } + } + + /// Assigns a constant G1 point without checking if it's on the curve. + pub fn load_private_g1_unchecked( + &self, + ctx: &mut Context, + point: G1Affine, + ) -> EcPoint> { + let g1_chip = EccChip::new(self.fp_chip); + g1_chip.load_private_unchecked(ctx, (point.x, point.y)) + } + + /// Assigns a constant G2 point without checking if it's on the curve. + pub fn load_private_g2_unchecked( + &self, + ctx: &mut Context, + point: G2Affine, + ) -> EcPoint> { + let fp2_chip = Fp2Chip::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + g2_chip.load_private_unchecked(ctx, (point.x, point.y)) + } + + /// Miller loop for a single pair of (G1, G2). + pub fn miller_loop( + &self, + ctx: &mut Context, + P: &EcPoint>, + Q: &EcPoint>, + ) -> FqPoint { + let fp2_chip = Fp2Chip::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + miller_loop::(&g2_chip, ctx, P, Q) + } + + /// Multi-pairing Miller loop. + pub fn multi_miller_loop( + &self, + ctx: &mut Context, + pairs: Vec<(&EcPoint>, &EcPoint>)>, + ) -> FqPoint { + let fp2_chip = Fp2Chip::::new(self.fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + multi_miller_loop::(&g2_chip, ctx, pairs) + } + + /// Final exponentiation to complete the pairing. + pub fn final_exp(&self, ctx: &mut Context, f: FqPoint) -> FqPoint { + let fp12_chip = Fp12Chip::::new(self.fp_chip); + fp12_chip.final_exp(ctx, f) + } + + // optimal Ate pairing + pub fn pairing( + &self, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + ) -> FqPoint { + let f0 = self.miller_loop(ctx, P, Q); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + // final_exp implemented in final_exp module + fp12_chip.final_exp(ctx, f0) + } + + /// Multi-pairing Miller loop + final exponentiation. + pub fn batched_pairing( + &self, + ctx: &mut Context, + pairs: &[(&EcPoint>, &EcPoint>)], + ) -> FqPoint { + let mml = self.multi_miller_loop(ctx, pairs.to_vec()); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fe = fp12_chip.final_exp(ctx, mml); + fe + } + + /* + * Conducts an efficient pairing check e(P, Q) = e(S, T) using only one + * final exponentiation. In particular, this constraints + * (e'(-P, Q)e'(S, T))^x = 1, where e' is the optimal ate pairing without + * the final exponentiation. Reduces number of necessary advice cells by + * ~30%. + */ + pub fn pairing_check( + &self, + ctx: &mut Context, + Q: &EcPoint>, + P: &EcPoint>, + T: &EcPoint>, + S: &EcPoint>, + ) { + let ecc_chip_fp = EccChip::new(self.fp_chip); + let negated_P = ecc_chip_fp.negate(ctx, P); + let gt = self.batched_pairing(ctx, &[(&negated_P, Q), (S, T)]); + let fp12_chip = Fp12Chip::::new(self.fp_chip); + let fp12_one = fp12_chip.load_constant(ctx, Fq12::one()); + fp12_chip.assert_equal(ctx, gt, fp12_one); + } +} diff --git a/halo2-ecc/src/bls12_381/tests/bls_signature.rs b/halo2-ecc/src/bls12_381/tests/bls_signature.rs new file mode 100644 index 00000000..8448f792 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/bls_signature.rs @@ -0,0 +1,149 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + ops::Neg, +}; + +use super::*; +use crate::{bls12_381::bls_signature::BlsSignatureChip, fields::FpStrategy}; +use halo2_base::{gates::RangeChip, utils::BigPrimeField, Context}; +use crate::halo2curves::pairing::group::ff::Field; +use rand_core::OsRng; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct BlsSignatureCircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, + num_aggregation: u32, +} + +/// Verify e(g1, signature_agg) = e(pubkey_agg, H(m)) +fn bls_signature_test( + ctx: &mut Context, + range: &RangeChip, + params: BlsSignatureCircuitParams, + signature: G2Affine, + pubkey: G1Affine, + msghash: G2Affine, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let pairing_chip = PairingChip::new(&fp_chip); + let bls_signature_chip = BlsSignatureChip::new(&fp_chip, &pairing_chip); + + let assigned_signature = pairing_chip.load_private_g2_unchecked(ctx, signature); + let assigned_pubkey = pairing_chip.load_private_g1_unchecked(ctx, pubkey); + let assigned_msghash = pairing_chip.load_private_g2_unchecked(ctx, msghash); + + let result = bls_signature_chip.is_valid_signature( + ctx, + assigned_signature, + assigned_msghash, + assigned_pubkey, + ); + + // Verify off-circuit + let g1_neg = G1Affine::generator().neg(); + let actual_result = + multi_miller_loop(&[(&g1_neg, &signature.into()), (&pubkey, &msghash.into())]) + .final_exponentiation(); + + // Compare the 2 results + assert_eq!(*result.value(), F::from(actual_result == Gt::identity())) +} + +#[test] +fn test_bls_signature() { + let run_path = "configs/bls12_381/bls_signature_circuit.config"; + let path = run_path; + let params: BlsSignatureCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + println!("num_advice: {num_advice}", num_advice = params.num_advice); + + let msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::from(msghash * sk); + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + bls_signature_test(ctx, range, params, signature, pubkey, msghash); + }) +} + +#[test] +fn test_bls_signature_fail() { + let run_path = "configs/bls12_381/bls_signature_circuit.config"; + let path = run_path; + let params: BlsSignatureCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + println!("num_advice: {num_advice}", num_advice = params.num_advice); + + let msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::random(OsRng); + + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + bls_signature_test(ctx, range, params, signature, pubkey, msghash); + }) +} + +#[test] +fn bench_bls_signature() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_bls_signature.config"; + let bench_params_file = + File::open(config_path).unwrap_or_else(|e| panic!("{config_path} does not exist: {e:?}")); + fs::create_dir_all("results/bls12_381").unwrap(); + fs::create_dir_all("data").unwrap(); + + let results_path = "results/bls12_381/bls_signature_bench.csv"; + let mut fs_results = File::create(results_path).unwrap(); + writeln!(fs_results, "degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,num_aggregation,proof_time,proof_size,verify_time")?; + + let bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: BlsSignatureCircuitParams = + serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + + let msghash = G2Affine::random(OsRng); + let sk = Scalar::random(OsRng); + let pubkey = G1Affine::from(G1Affine::generator() * sk); + let signature = G2Affine::from(msghash * sk); + + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + (signature, pubkey, msghash), + (signature, pubkey, msghash), + |pool, range, (signature, pubkey, msghash)| { + bls_signature_test(pool.main(), range, bench_params, signature, pubkey, msghash); + }, + ); + + writeln!( + fs_results, + "{},{},{},{},{},{},{},{},{:?},{},{:?}", + bench_params.degree, + bench_params.num_advice, + bench_params.num_lookup_advice, + bench_params.num_fixed, + bench_params.lookup_bits, + bench_params.limb_bits, + bench_params.num_limbs, + bench_params.num_aggregation, + stats.proof_time.time.elapsed(), + stats.proof_size, + stats.verify_time.time.elapsed() + )?; + } + Ok(()) +} diff --git a/halo2-ecc/src/bls12_381/tests/ec_add.rs b/halo2-ecc/src/bls12_381/tests/ec_add.rs new file mode 100644 index 00000000..8a2a566d --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/ec_add.rs @@ -0,0 +1,109 @@ +use std::fs; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +use super::*; +use crate::fields::{FieldChip, FpStrategy}; +use halo2_base::gates::RangeChip; +use halo2_base::utils::testing::base_test; +use halo2_base::utils::BigPrimeField; +use halo2_base::Context; +use itertools::Itertools; +use rand_core::OsRng; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct CircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, + batch_size: usize, +} + +fn g2_add_test( + ctx: &mut Context, + range: &RangeChip, + params: CircuitParams, + _points: Vec, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let fp2_chip = Fp2Chip::::new(&fp_chip); + let g2_chip = EccChip::new(&fp2_chip); + + let points = + _points.iter().map(|pt| g2_chip.assign_point_unchecked(ctx, *pt)).collect::>(); + + let acc = g2_chip.sum::(ctx, points); + + let answer = _points.iter().fold(G2Affine::identity(), |a, &b| (a + b).to_affine()); + let x = fp2_chip.get_assigned_value(&acc.x.into()); + let y = fp2_chip.get_assigned_value(&acc.y.into()); + assert_eq!(answer.x, x); + assert_eq!(answer.y, y); +} + +#[test] +fn test_ec_add() { + let path = "configs/bls12_381/ec_add_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let k = params.degree; + let points = (0..params.batch_size).map(|_| G2Affine::random(OsRng)).collect_vec(); + + base_test() + .k(k) + .lookup_bits(params.lookup_bits) + .run(|ctx, range| g2_add_test(ctx, range, params, points)); +} + +#[test] +fn bench_ec_add() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_ec_add.config"; + let bench_params_file = + File::open(config_path).unwrap_or_else(|e| panic!("{config_path} does not exist: {e:?}")); + fs::create_dir_all("results/bls12_381").unwrap(); + + let results_path = "results/bls12_381/ec_add_bench.csv"; + let mut fs_results = File::create(results_path).unwrap(); + writeln!(fs_results, "degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,batch_size,proof_time,proof_size,verify_time")?; + fs::create_dir_all("data").unwrap(); + + let bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: CircuitParams = serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + let mut rng = OsRng; + + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + vec![G2Affine::generator(); bench_params.batch_size], + (0..bench_params.batch_size).map(|_| G2Affine::random(&mut rng)).collect_vec(), + |pool, range, points| { + g2_add_test(pool.main(), range, bench_params, points); + }, + ); + writeln!( + fs_results, + "{},{},{},{},{},{},{},{},{:?},{},{:?}", + bench_params.degree, + bench_params.num_advice, + bench_params.num_lookup_advice, + bench_params.num_fixed, + bench_params.lookup_bits, + bench_params.limb_bits, + bench_params.num_limbs, + bench_params.batch_size, + stats.proof_time.time.elapsed(), + stats.proof_size, + stats.verify_time.time.elapsed() + )?; + } + Ok(()) +} diff --git a/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs new file mode 100644 index 00000000..6164c24b --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/hash_to_curve.rs @@ -0,0 +1,179 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, + marker::PhantomData, +}; + +use super::*; +use crate::group::Curve; +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, +}; +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 new file mode 100644 index 00000000..9ad0fc15 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/mod.rs @@ -0,0 +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; + +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 new file mode 100644 index 00000000..cc9ffe78 --- /dev/null +++ b/halo2-ecc/src/bls12_381/tests/pairing.rs @@ -0,0 +1,173 @@ +use std::{ + fs::{self, File}, + io::{BufRead, BufReader}, +}; + +use crate::fields::FieldChip; + +use super::*; +use halo2_base::{ + gates::RangeChip, halo2_proofs::arithmetic::Field, utils::BigPrimeField, Context, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +struct PairingCircuitParams { + strategy: FpStrategy, + degree: u32, + num_advice: usize, + num_lookup_advice: usize, + num_fixed: usize, + lookup_bits: usize, + limb_bits: usize, + num_limbs: usize, +} + +fn pairing_test( + ctx: &mut Context, + range: &RangeChip, + params: PairingCircuitParams, + P: G1Affine, + Q: G2Affine, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let chip = PairingChip::new(&fp_chip); + let P_assigned = chip.load_private_g1_unchecked(ctx, P); + let Q_assigned = chip.load_private_g2_unchecked(ctx, Q); + // test optimal ate pairing + let f = chip.pairing(ctx, &Q_assigned, &P_assigned); + let actual_f = pairing(&P, &Q); + let fp12_chip = Fp12Chip::new(&fp_chip); + // cannot directly compare f and actual_f because `Gt` has private field `Fq12` + assert_eq!( + format!("Gt({:?})", fp12_chip.get_assigned_value(&f.into())), + format!("{actual_f:?}") + ); +} + +#[test] +fn test_pairing() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let P = G1Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + pairing_test(ctx, range, params, P, Q); + }); +} + +fn pairing_check_test( + ctx: &mut Context, + range: &RangeChip, + params: PairingCircuitParams, + P: G1Affine, + Q: G2Affine, + S: G1Affine, +) { + let fp_chip = FpChip::::new(range, params.limb_bits, params.num_limbs); + let chip = PairingChip::new(&fp_chip); + let P_assigned = chip.load_private_g1_unchecked(ctx, P); + let Q_assigned = chip.load_private_g2_unchecked(ctx, Q); + let S_assigned = chip.load_private_g1_unchecked(ctx, S); + let T_assigned = chip.load_private_g2_unchecked(ctx, G2Affine::generator()); + chip.pairing_check(ctx, &Q_assigned, &P_assigned, &T_assigned, &S_assigned); +} + +/* + * Samples a random α,β in Fr and does the pairing check + * e(H_1^α, H_2^β) = e(H_1^(α*β), H_2), where H_1 is the generator for G1 and + * H_2 for G2. + */ +#[test] +fn test_pairing_check() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let alpha = Scalar::random(&mut rng); + let beta = Scalar::random(&mut rng); + let P = G1Affine::from(G1Affine::generator() * alpha); + let Q = G2Affine::from(G2Affine::generator() * beta); + let S = G1Affine::from(G1Affine::generator() * alpha * beta); + base_test().k(params.degree).lookup_bits(params.lookup_bits).run(|ctx, range| { + pairing_check_test(ctx, range, params, P, Q, S); + }) +} + +/* + * Samples a random α,β in Fr and does an incorrect pairing check + * e(H_1^α, H_2^β) = e(H_1^α, H_2), where H_1 is the generator for G1 and + * H_2 for G2. + */ +#[test] +fn test_pairing_check_fail() { + let path = "configs/bls12_381/pairing_circuit.config"; + let params: PairingCircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + let mut rng = StdRng::seed_from_u64(0); + let alpha = Scalar::random(&mut rng); + let beta = Scalar::random(&mut rng); + let P = G1Affine::from(G1Affine::generator() * alpha); + let Q = G2Affine::from(G2Affine::generator() * beta); + base_test().k(params.degree).lookup_bits(params.lookup_bits).expect_satisfied(false).run( + |ctx, range| { + pairing_check_test(ctx, range, params, P, Q, P); + }, + ) +} + +#[test] +fn bench_pairing() -> Result<(), Box> { + let config_path = "configs/bls12_381/bench_pairing.config"; + let bench_params_file = + File::open(config_path).unwrap_or_else(|e| panic!("{config_path} does not exist: {e:?}")); + fs::create_dir_all("results/bls12_381").unwrap(); + fs::create_dir_all("data").unwrap(); + + let results_path = "results/bls12_381/pairing_bench.csv"; + let mut fs_results = File::create(results_path).unwrap(); + writeln!(fs_results, "degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time")?; + + let mut rng = StdRng::seed_from_u64(0); + let bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: PairingCircuitParams = + serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + println!("---------------------- degree = {k} ------------------------------",); + + let P = G1Affine::random(&mut rng); + let Q = G2Affine::random(&mut rng); + let stats = base_test().k(k).lookup_bits(bench_params.lookup_bits).bench_builder( + (P, Q), + (P, Q), + |pool, range, (P, Q)| { + pairing_test(pool.main(), range, bench_params, P, Q); + }, + ); + + writeln!( + fs_results, + "{},{},{},{},{},{},{},{:?},{},{:?}", + bench_params.degree, + bench_params.num_advice, + bench_params.num_lookup_advice, + bench_params.num_fixed, + bench_params.lookup_bits, + bench_params.limb_bits, + bench_params.num_limbs, + stats.proof_time.time.elapsed(), + stats.proof_size, + stats.verify_time.time.elapsed() + )?; + } + Ok(()) +} diff --git a/halo2-ecc/src/bn254/final_exp.rs b/halo2-ecc/src/bn254/final_exp.rs index ae2ecac9..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}, -}; 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 dbd7382f..28229218 100644 --- a/halo2-ecc/src/bn254/pairing.rs +++ b/halo2-ecc/src/bn254/pairing.rs @@ -1,6 +1,7 @@ #![allow(non_snake_case)] use super::{Fp12Chip, Fp2Chip, FpChip, FpPoint, Fq, FqPoint}; 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/bn254/tests/bls_signature.rs b/halo2-ecc/src/bn254/tests/bls_signature.rs index adacce89..bca2635f 100644 --- a/halo2-ecc/src/bn254/tests/bls_signature.rs +++ b/halo2-ecc/src/bn254/tests/bls_signature.rs @@ -4,7 +4,7 @@ use std::{ }; use super::*; -use crate::halo2curves::pairing::{group::ff::Field, MillerLoopResult}; +use crate::group::ff::Field; use crate::{ bn254::bls_signature::BlsSignatureChip, fields::FpStrategy, halo2_proofs::halo2curves::bn256::G2Affine, @@ -17,6 +17,11 @@ use halo2_base::{ }; use rand_core::OsRng; +#[cfg(feature = "halo2-axiom")] +use crate::halo2curves::pairing::MillerLoopResult; +#[cfg(feature = "halo2-pse")] +use halo2_base::halo2_proofs::halo2curves::pairing::MillerLoopResult; + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct BlsSignatureCircuitParams { strategy: FpStrategy, diff --git a/halo2-ecc/src/ecc/hash_to_curve.rs b/halo2-ecc/src/ecc/hash_to_curve.rs new file mode 100644 index 00000000..3020b988 --- /dev/null +++ b/halo2-ecc/src/ecc/hash_to_curve.rs @@ -0,0 +1,485 @@ +//! 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::{flex_gate::threads::CommonCircuitBuilder, GateInstructions, RangeInstructions}, + halo2_proofs::{halo2curves::CurveExt, plonk::Error}, + utils::BigPrimeField, + AssignedValue, Context, QuantumCell, +}; +use itertools::Itertools; + +use crate::ff::Field; +use crate::fields::{FieldChip, FieldChipExt, Selectable}; + +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 CircuitBuilder: CommonCircuitBuilder; + // Type of output produced by the hash function. + type Output: IntoIterator>; + + /// Hashes the input of fixed size and returns finilized output. + fn digest( + &self, + ctx: &mut Self::CircuitBuilder, + input: impl IntoIterator>, + ) -> 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; +} + +/// Trait that extneds [`CurveExt`] with constants specific to hash to curve operations. +pub trait HashCurveExt: CurveExt +where + Self::Base: crate::ff::PrimeField, +{ + 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; + + const BLS_X: u64; +} + +/// A trait for message expansion methods supported by [`HashToCurveChip`]. +pub trait ExpandMessageChip { + fn expand_message>( + thread_pool: &mut HC::CircuitBuilder, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + len_in_bytes: usize, + ) -> Result>, Error>; +} + +/// Trait that defines methods used by the [`HashToCurveChip`] to the curve point for [`EccChip`]. +pub trait HashToCurveInstructions< + F: BigPrimeField, + FC: FieldChipExt, + C: HashCurveExt, +> where + FC::FieldType: crate::ff::PrimeField, + FC: Selectable, +{ + fn field_chip(&self) -> &FC; + + /// Computes `scalar * P` where scalar is represented as vector of [bits]. + fn scalar_mult_bits( + &self, + ctx: &mut Context, + p: EcPoint, + bits: Vec>, + window_bits: usize, + ) -> EcPoint { + let max_bits = bits.len(); + 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::CircuitBuilder, + hash_chip: &HC, + msg: impl Iterator>, + 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; + + /// 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, + 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) + } + + /// Computes the endomorphism psi for point [p]. + 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); + let y = self.field_chip().mul(ctx, y_frob, psi_y); + + EcPoint::new(x, y) + } + + /// Efficiently omputes psi(psi(P)) for point [p]. + 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); + let y = self.field_chip().negate(ctx, p.y); + + EcPoint::new(x, y) + } +} + +/// 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::CircuitBuilder, + 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, true); + + 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] + // + // 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( + &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 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)); + 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 { + /// 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>( + thread_pool: &mut HC::CircuitBuilder, + hash_chip: &HC, + range: &impl RangeInstructions, + msg: impl Iterator>, + dst: &[u8], + 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); + + // assign DST bytes & cache them + 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| 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| thread_pool.main().load_constant(b)); + + let assigned_msg = msg + .map(|cell| match cell { + QuantumCell::Existing(v) => v, + QuantumCell::Constant(v) => thread_pool.main().load_constant(v), + _ => panic!("passing unassigned witness to this function is insecure"), + }) + .collect_vec(); + + // compute blocks + 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(thread_pool, msg_prime)?.into_iter().collect_vec(); + + b_vals.insert( + 0, + hash_chip + .digest( + thread_pool, + b_0.iter() + .copied() + .chain(iter::once(one)) + .chain(dst_prime.clone()) + .map(QuantumCell::Existing), + )? + .into_iter() + .collect_vec(), + ); + + 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); + + b_vals.insert( + i, + hash_chip.digest(thread_pool, preimg)?.into_iter().collect_vec(), + ); + } + + 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 b410b1e0..ee75e854 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -19,6 +19,7 @@ pub mod ecdsa; pub mod fixed_base; pub mod schnorr_signature; // pub mod fixed_base_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) @@ -586,6 +587,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 diff --git a/halo2-ecc/src/fields/fp.rs b/halo2-ecc/src/fields/fp.rs index c083c709..92450f73 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,23 @@ 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/fp12.rs b/halo2-ecc/src/fields/fp12.rs index bdb9f790..8ce8670c 100644 --- a/halo2-ecc/src/fields/fp12.rs +++ b/halo2-ecc/src/fields/fp12.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +#[cfg(feature = "halo2-axiom")] use crate::ff::PrimeField as _; use crate::impl_field_ext_chip_common; @@ -249,3 +250,37 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + #[cfg(feature = "halo2-axiom")] + use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq12, Fq2, Fq6}; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::{Fq, Fq12, Fq2, Fq6}; + // This means we store an Fp12 point as `\sum_{i = 0}^6 (a_{i0} + a_{i1} * u) * w^i` + // This is encoded in an FqPoint of degree 12 as `(a_{00}, ..., a_{50}, a_{01}, ..., a_{51})` + impl FieldExtConstructor for Fq12 { + fn new(c: [Fq; 12]) -> Self { + Fq12 { + c0: Fq6 { + c0: Fq2 { c0: c[0], c1: c[6] }, + c1: Fq2 { c0: c[2], c1: c[8] }, + c2: Fq2 { c0: c[4], c1: c[10] }, + }, + c1: Fq6 { + c0: Fq2 { c0: c[1], c1: c[7] }, + c1: Fq2 { c0: c[3], c1: c[9] }, + c2: Fq2 { c0: c[5], c1: c[11] }, + }, + } + } + + fn coeffs(&self) -> Vec { + let x = self; + vec![ + x.c0.c0.c0, x.c1.c0.c0, x.c0.c1.c0, x.c1.c1.c0, x.c0.c2.c0, x.c1.c2.c0, x.c0.c0.c1, + x.c1.c0.c1, x.c0.c1.c1, x.c1.c1.c1, x.c0.c2.c1, x.c1.c2.c1, + ] + } + } +} diff --git a/halo2-ecc/src/fields/fp2.rs b/halo2-ecc/src/fields/fp2.rs index 71c5d446..a1d7c276 100644 --- a/halo2-ecc/src/fields/fp2.rs +++ b/halo2-ecc/src/fields/fp2.rs @@ -1,13 +1,17 @@ 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 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 +44,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 +109,67 @@ 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}; @@ -130,3 +183,21 @@ mod bn254 { } } } + +mod bls12_381 { + use crate::fields::FieldExtConstructor; + #[cfg(feature = "halo2-axiom")] + use crate::halo2_proofs::halo2curves::bls12_381::{Fq, Fq2}; + #[cfg(feature = "halo2-pse")] + use halo2curves::bls12_381::{Fq, Fq2}; + + impl FieldExtConstructor for Fq2 { + fn new(c: [Fq; 2]) -> Self { + Fq2 { c0: c[0], c1: c[1] } + } + + fn coeffs(&self) -> Vec { + vec![self.c0, self.c1] + } + } +} diff --git a/halo2-ecc/src/fields/mod.rs b/halo2-ecc/src/fields/mod.rs index 469397df..29cc5edd 100644 --- a/halo2-ecc/src/fields/mod.rs +++ b/halo2-ecc/src/fields/mod.rs @@ -278,6 +278,34 @@ 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 f007c3bf..b4d38d28 100644 --- a/halo2-ecc/src/fields/vector.rs +++ b/halo2-ecc/src/fields/vector.rs @@ -351,6 +351,26 @@ 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] diff --git a/halo2-ecc/src/lib.rs b/halo2-ecc/src/lib.rs index 5b3f191a..4be26eee 100644 --- a/halo2-ecc/src/lib.rs +++ b/halo2-ecc/src/lib.rs @@ -7,12 +7,14 @@ pub mod bigint; pub mod ecc; pub mod fields; +pub mod bls12_381; pub mod bn254; pub mod grumpkin; 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; 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() diff --git a/rust-toolchain b/rust-toolchain index ee2d639b..1d77724c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2023-08-12 \ No newline at end of file +nightly-2023-11-16