diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a52fd70..bdce99a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,11 +37,8 @@ concurrency: jobs: test: - runs-on: ${{ matrix.os }} + runs-on: gh-32vcpu-128gbram-ubuntu-2204 strategy: - matrix: - os: - - ubuntu-latest fail-fast: false env: RUSTFLAGS: -D warnings diff --git a/crates/bls12381/Cargo.toml b/crates/bls12381/Cargo.toml index b86e6cf..e3c2f1d 100644 --- a/crates/bls12381/Cargo.toml +++ b/crates/bls12381/Cargo.toml @@ -2,7 +2,7 @@ name = "bellpepper-bls12381" version = "0.2.0" edition = "2021" -authors = ["Gustavo "] +authors = ["Lurk Lab Engineering "] license.workspace=true description = "Bellpepper circuit implementation of BLS12-381 pairing and curve operations" documentation = "https://docs.rs/bellpepper-bls12381" @@ -19,8 +19,8 @@ num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true} rand = { workspace = true} -bls12_381 = { git = "https://github.com/wrinqle/bls12_381" } +bls12_381 = { git = "https://github.com/wrinqle/bls12_381", features = ["experimental"] } [dev-dependencies] expect-test = "1.4.1" -pasta_curves = "0.5.1" +halo2curves = "0.6.1" diff --git a/crates/bls12381/README.md b/crates/bls12381/README.md index 35ae3c3..a57ca77 100644 --- a/crates/bls12381/README.md +++ b/crates/bls12381/README.md @@ -1,6 +1,6 @@ # bellpepper-bls12381 -Emulated pairing and elliptic curve library using [bellpepper](https://github.com/lurk-lab/bellpepper) inspired by the [emulated](https://github.com/Consensys/gnark/tree/master/std/math/emulated) package in [Gnark](https://github.com/Consensys/gnark) +Emulated pairing and elliptic curve library using [bellpepper](https://github.com/lurk-lab/bellpepper) inspired by the [emulated](https://github.com/Consensys/gnark/tree/master/std/algebra/emulated) package in [Gnark](https://github.com/Consensys/gnark) ## License diff --git a/crates/bls12381/src/curves/g1.rs b/crates/bls12381/src/curves/g1.rs index 9769e91..d20c88a 100644 --- a/crates/bls12381/src/curves/g1.rs +++ b/crates/bls12381/src/curves/g1.rs @@ -5,9 +5,11 @@ use bls12_381::{fp::Fp as BlsFp, G1Projective}; use ff::PrimeFieldBits; use num_bigint::BigInt; -use crate::curves::params::Bls12381G1Params; use crate::fields::fp::FpElement; +use super::params::{Bls12381G1Params, EmulatedCurveParams}; + +/// Represents an affine point on BLS12-381's G1 curve. Point at infinity is represented with (0, 0) #[derive(Clone)] pub struct G1Point { pub x: FpElement, @@ -111,14 +113,15 @@ impl G1Point { Ok(()) } + /// Returns `phi(P)` where the coefficient is a cube root of unity in Fp pub fn phi(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, { - let x = self.x.mul( - &mut cs.namespace(|| "x <- x * g1.w"), - &Bls12381G1Params::w(), - )?; + let phi_coeff = FpElement::from_dec("793479390729215512621379701633421447060886740281060493010456487427281649075476305620758731620350").unwrap(); + let x = self + .x + .mul(&mut cs.namespace(|| "x <- x * phi_coeff"), &phi_coeff)?; Ok(Self { x, y: self.y.clone(), @@ -219,6 +222,7 @@ impl G1Point { Ok(()) } + /// Returns the EC addition between `self` and `value`. Requires that `self != value` and that neither point is the identity pub fn add(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -308,6 +312,7 @@ impl G1Point { Ok(res) } + /// Returns `-P` pub fn neg(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, @@ -318,6 +323,7 @@ impl G1Point { }) } + /// Returns `self - value`. Requires that `self != -value` and neither point is the identity since it calls `add` pub fn sub(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -327,12 +333,13 @@ impl G1Point { Ok(res) } + /// Returns `self + self` pub fn double(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, { - let p = self; let cs = &mut cs.namespace(|| "G1::double(p)"); + let p = self.reduce(&mut cs.namespace(|| "p <- p.reduce()"))?; // compute λ = (3p.x²)/2*p.y let xx3a = p.x.square(&mut cs.namespace(|| "xx3a <- p.x.square()"))?; let xx3a = xx3a.mul_const(&mut cs.namespace(|| "xx3a <- xx3a * 3"), &BigInt::from(3))?; @@ -352,6 +359,30 @@ impl G1Point { Ok(Self { x: xr, y: yr }) } + /// Calls `self.double()` repeated `n` times + pub fn double_n(&self, cs: &mut CS, n: usize) -> Result + where + CS: ConstraintSystem, + { + let mut p: Option<&Self> = Some(self); + let mut tmp: Option = None; + let mut cs = cs.namespace(|| format!("G1::double_n(p, {n})")); + for i in 0..n { + if let Some(cur_p) = p { + let mut val = + cur_p.double(&mut cs.namespace(|| format!("p <- p.double() ({i})")))?; + if i % 2 == 1 { + val = val.reduce(&mut cs.namespace(|| format!("p <- p.reduce() ({i})")))?; + } + tmp = Some(val); + p = tmp.as_ref(); + } + } + + Ok(tmp.unwrap()) + } + + /// Returns `self + self + self` pub fn triple(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, @@ -389,6 +420,7 @@ impl G1Point { Ok(Self { x: xr, y: yr }) } + /// Returns `2*self + value` pub fn double_and_add(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -449,14 +481,81 @@ impl G1Point { )?; Ok(Self { x, y }) } + + /// Returns `[x^2]P` where `x` is the BLS parameter for BLS12-381, `-15132376222941642752` + pub fn scalar_mul_by_seed_square(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let cs = &mut cs.namespace(|| "G1::scalar_mul_by_seed_square(q)"); + let z = self.double(&mut cs.namespace(|| "z <- q.double()"))?; + let z = self.add(&mut cs.namespace(|| "z <- q + z"), &z)?; + let z = z.double(&mut cs.namespace(|| "z <- z.double()"))?; + let z = z.double_and_add(&mut cs.namespace(|| "z <- z.double_and_add(q) 1"), self)?; + let z = z.double_n(&mut cs.namespace(|| "z <- z.double_n(2)"), 2)?; + let z = z.double_and_add(&mut cs.namespace(|| "z <- z.double_and_add(q) 2"), self)?; + let z = z.double_n(&mut cs.namespace(|| "z <- z.double_n(8)"), 8)?; + let z = z.double_and_add(&mut cs.namespace(|| "z <- z.double_and_add(q) 3"), self)?; + let t0 = z.double(&mut cs.namespace(|| "t0 <- z.double()"))?; + let t0 = z.add(&mut cs.namespace(|| "t0 <- z + t0"), &t0)?; + let t0 = t0.double(&mut cs.namespace(|| "t0 <- t0.double()"))?; + let t0 = t0.double_and_add(&mut cs.namespace(|| "t0 <- t0.double_and_add(z) 1"), &z)?; + let t0 = t0.double_n(&mut cs.namespace(|| "t0 <- t0.double_n(2)"), 2)?; + let t0 = t0.double_and_add(&mut cs.namespace(|| "t0 <- t0.double_and_add(z) 2"), &z)?; + let t0 = t0.double_n(&mut cs.namespace(|| "t0 <- t0.double_n(8)"), 8)?; + let t0 = t0.double_and_add(&mut cs.namespace(|| "t0 <- t0.double_and_add(z) 3"), &z)?; + let t0 = t0.double_n(&mut cs.namespace(|| "t0 <- t0.double_n(31)"), 31)?; + let z = t0.add(&mut cs.namespace(|| "z <- t0 + z"), &z)?; + let z = z.double_n(&mut cs.namespace(|| "z <- z.double_n(32) 1"), 32)?; + let z = z.double_and_add(&mut cs.namespace(|| "z <- z.double_and_add(q) 4"), self)?; + let z = z.double_n(&mut cs.namespace(|| "z <- z.double_n(32) 2"), 32)?; + + Ok(z) + } + + /// Asserts that `phi(P) == [-x^2]P` + pub fn assert_subgroup_check(&self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let a = self.phi(&mut cs.namespace(|| "a <- p.phi()"))?; + let b = self.scalar_mul_by_seed_square( + &mut cs.namespace(|| "b <- p.scalar_mul_by_seed_square()"), + )?; + let b = b.neg(&mut cs.namespace(|| "b <- -b"))?; + Self::assert_is_equal(&mut cs.namespace(|| "a == b"), &a, &b)?; + Ok(()) + } + + /// Asserts that y^2 = x^3 + ax + b + pub fn assert_is_on_curve(&self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let y_2 = self.y.square(&mut cs.namespace(|| "y_2 <- p.y.square()"))?; + let x_2 = self.x.square(&mut cs.namespace(|| "x_2 <- p.x.square()"))?; + let x_3 = self.x.mul(&mut cs.namespace(|| "x_3 <- x * x_2"), &x_2)?; + let ax = self + .x + .mul(&mut cs.namespace(|| "ax <- x * a"), &Bls12381G1Params::a())?; + let rhs = x_3.add(&mut cs.namespace(|| "rhs <- x_3 + ax"), &ax)?; + let rhs = rhs.add( + &mut cs.namespace(|| "rhs <- rhs + b"), + &Bls12381G1Params::b(), + )?; + FpElement::assert_is_equal(&mut cs.namespace(|| "y_2 == rhs"), &y_2, &rhs)?; + Ok(()) + } } #[cfg(test)] mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::group::Group; - use pasta_curves::Fp; + use bls12_381::Scalar; + use ff::Field; + use halo2curves::bn256::Fq as Fp; + use halo2curves::group::Group; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -484,8 +583,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5063"]); - expect_eq(cs.num_constraints(), &expect!["5051"]); + expect_eq(cs.scalar_aux().len(), &expect!["3009"]); + expect_eq(cs.num_constraints(), &expect!["2977"]); } #[test] @@ -528,8 +627,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["28644"]); - expect_eq(cs.num_constraints(), &expect!["28836"]); + expect_eq(cs.scalar_aux().len(), &expect!["19974"]); + expect_eq(cs.num_constraints(), &expect!["20120"]); } #[test] @@ -585,8 +684,8 @@ mod tests { assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["15256"]); - expect_eq(cs.num_constraints(), &expect!["15488"]); + expect_eq(cs.scalar_aux().len(), &expect!["14672"]); + expect_eq(cs.num_constraints(), &expect!["14932"]); } #[test] @@ -608,8 +707,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -631,8 +730,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8894"]); - expect_eq(cs.num_constraints(), &expect!["8912"]); + expect_eq(cs.scalar_aux().len(), &expect!["4764"]); + expect_eq(cs.num_constraints(), &expect!["4750"]); } #[test] @@ -654,8 +753,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5068"]); - expect_eq(cs.num_constraints(), &expect!["5068"]); + expect_eq(cs.scalar_aux().len(), &expect!["3014"]); + expect_eq(cs.num_constraints(), &expect!["2996"]); } #[test] @@ -679,8 +778,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5063"]); - expect_eq(cs.num_constraints(), &expect!["5051"]); + expect_eq(cs.scalar_aux().len(), &expect!["3009"]); + expect_eq(cs.num_constraints(), &expect!["2977"]); } #[test] @@ -711,8 +810,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8913"]); - expect_eq(cs.num_constraints(), &expect!["8919"]); + expect_eq(cs.scalar_aux().len(), &expect!["4769"]); + expect_eq(cs.num_constraints(), &expect!["4741"]); } #[test] @@ -755,7 +854,112 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["9015"]); - expect_eq(cs.num_constraints(), &expect!["9078"]); + expect_eq(cs.scalar_aux().len(), &expect!["6713"]); + expect_eq(cs.num_constraints(), &expect!["6765"]); + } + + #[test] + fn test_random_mul_by_seed_square() { + let mut rng = rand::thread_rng(); + let a = G1Projective::random(&mut rng); + let x0 = bls12_381::Scalar::from(15132376222941642752); + let c = a * (x0 * x0); + let a = G1Affine::from(a); + let c = G1Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc + .scalar_mul_by_seed_square(&mut cs.namespace(|| "a.mul_by_seed_square()")) + .unwrap(); + G1Point::assert_is_equal( + &mut cs.namespace(|| "a.mul_by_seed_square() = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["489860"]); + expect_eq(cs.num_constraints(), &expect!["491128"]); + } + + #[test] + fn test_random_subgroup_check() { + let mut rng = rand::thread_rng(); + let n = Scalar::random(&mut rng); + let a = G1Affine::from(G1Projective::generator() * n); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + a_alloc + .assert_subgroup_check(&mut cs.namespace(|| "a.subgroup_check()")) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["490240"]); + expect_eq(cs.num_constraints(), &expect!["491522"]); + } + + #[test] + fn test_random_subgroup_check_negative() { + use crate::curves::params::EmulatedCurveParams; + let b = BlsFp::from(&Bls12381G1Params::::b()); + use rand::RngCore; + let mut rng = rand::thread_rng(); + let mut random_point = || loop { + let x = BlsFp::random(&mut rng); + let y = ((x.square() * x) + b).sqrt(); + if y.is_some().into() { + let flip_sign = rng.next_u32() % 2 != 0; + return G1Affine { + x, + y: if flip_sign { -y.unwrap() } else { y.unwrap() }, + infinity: 0.into(), + }; + } + }; + let mut a = random_point(); + while a.is_torsion_free().into() { + a = random_point(); + } + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + a_alloc + .assert_subgroup_check(&mut cs.namespace(|| "a.subgroup_check()")) + .unwrap(); + assert!(!cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["490240"]); + expect_eq(cs.num_constraints(), &expect!["491522"]); + } + + #[test] + fn test_random_phi() { + let mut rng = rand::thread_rng(); + let a = G1Affine::from(G1Projective::random(&mut rng)); + let c = bls12_381::g1::endomorphism(&a); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc.phi(&mut cs.namespace(|| "a.phi()")).unwrap(); + G1Point::assert_is_equal(&mut cs.namespace(|| "a.phi() = c"), &res_alloc, &c_alloc) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["868"]); + expect_eq(cs.num_constraints(), &expect!["846"]); } } diff --git a/crates/bls12381/src/curves/g2.rs b/crates/bls12381/src/curves/g2.rs index 66a7cbb..c32dea0 100644 --- a/crates/bls12381/src/curves/g2.rs +++ b/crates/bls12381/src/curves/g2.rs @@ -1,18 +1,29 @@ +use bellpepper_core::boolean::{AllocatedBit, Boolean}; use bellpepper_core::{ConstraintSystem, SynthesisError}; +use bellpepper_emulated::field_element::EmulatedFieldParams; use bls12_381::fp2::Fp2 as BlsFp2; +use bls12_381::hash_to_curve::Sgn0; use bls12_381::{G2Affine, G2Projective}; use ff::PrimeFieldBits; use num_bigint::BigInt; +use num_traits::Zero; use crate::curves::params::Bls12381G2Params; -use crate::fields::fp2::Fp2Element; +use crate::fields::fp::{big_from_dec, bigint_to_fpelem, fp_from_dec, Bls12381FpParams, FpElement}; +use crate::fields::fp2::{fp2_from_dec, fp2_pow_vartime, Fp2Element}; +use super::params::EmulatedCurveParams; + +/// Represents an affine point on BLS12-381's G2 curve. Point at infinity is represented with (0, 0) #[derive(Clone)] pub struct G2Point { pub x: Fp2Element, pub y: Fp2Element, } +/// Represents a point on the curve E' isogenous to E specified in section 8.8.2 of [RFC 9380](https://datatracker.ietf.org/doc/rfc9380/) +pub struct G2IsoPoint(pub G2Point); + impl From<&G2Affine> for G2Point where F: PrimeFieldBits, @@ -89,24 +100,39 @@ impl G2Point { Ok(()) } + /// Returns psi(P) pub fn psi(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, { - let x = self.x.mul_element( - &mut cs.namespace(|| "x <- x * g2.u1"), - &Bls12381G2Params::u1(), - )?; + let x = self + .x + .conjugate(&mut cs.namespace(|| "x <- p.x.conjugate()"))?; + let x = x.mul(&mut cs.namespace(|| "x <- x * c0"), &Bls12381G2Params::c0())?; + // TODO: might be cheaper to use a different mul here since first coord is 0 let y = self .y - .conjugate(&mut cs.namespace(|| "y <- y.conjugate()"))?; - let y = y.mul( - &mut cs.namespace(|| "y <- y * g2.v"), - &Bls12381G2Params::v(), - )?; + .conjugate(&mut cs.namespace(|| "y <- p.y.conjugate()"))?; + let y = y.mul(&mut cs.namespace(|| "y <- y * c1"), &Bls12381G2Params::c1())?; + Ok(Self { x, y }) + } + + /// Returns psi(psi(P)) + pub fn psi2(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let w = Fp2Element { + a0: Bls12381FpParams::w(), + a1: FpElement::zero(), + }; + // TODO: might be cheaper to use a different mul here since second coord is 0 + let x = self.x.mul(&mut cs.namespace(|| "x <- p.x * w"), &w)?; + let y = self.y.neg(&mut cs.namespace(|| "y <- -p.y"))?; Ok(Self { x, y }) } + /// Returns `[x]P` where `x` is the BLS parameter for BLS12-381, `-15132376222941642752` pub fn scalar_mul_by_seed(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, @@ -127,6 +153,7 @@ impl G2Point { Ok(z) } + /// Returns the EC addition between `self` and `value`. Requires that `self != value` and that neither point is the identity pub fn add(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -151,6 +178,71 @@ impl G2Point { Ok(Self { x: xr, y: yr }) } + /// This function is equivalent to `add`, but it will conditionally check if + /// the points are equal and call `double` instead if necessary. + pub fn add_or_double(&self, cs: &mut CS, value: &Self) -> Result + where + CS: ConstraintSystem, + { + // TODO: replace this with gnark's [AddUnified](https://github.com/Consensys/gnark/blob/9b8efdac514400a4100888b3cd5279e207f4a193/std/algebra/emulated/sw_emulated/point.go#L205) + let diffx = self + .x + .sub(&mut cs.namespace(|| "diffx <- t1.x - t2.x"), &value.x)?; + let diffy = self + .y + .sub(&mut cs.namespace(|| "diffy <- t1.y - t2.y"), &value.y)?; + let is_eq_x = diffx.alloc_is_zero(&mut cs.namespace(|| "itfx <- diff.x ?= 0"))?; + let is_eq_y = diffy.alloc_is_zero(&mut cs.namespace(|| "itfy <- diff.y ?= 0"))?; + let is_eq = Boolean::and( + &mut cs.namespace(|| "itf <- itfx & itfy"), + &Boolean::Is(is_eq_x), + &Boolean::Is(is_eq_y), + )?; + + let double = self.double(&mut cs.namespace(|| "self.double()"))?; + let double = double.reduce(&mut cs.namespace(|| "double.reduce()"))?; + + let value = value.reduce(&mut cs.namespace(|| "value.reduce()"))?; + let dummy = Self { + x: Fp2Element::zero(), + y: Fp2Element::zero(), + }; + let inputx = Fp2Element::conditionally_select( + &mut cs.namespace(|| "tmp x"), + &value.x, + &dummy.x, + &is_eq, + )?; + let inputy = Fp2Element::conditionally_select( + &mut cs.namespace(|| "tmp y"), + &value.y, + &dummy.y, + &is_eq, + )?; + let input = Self { + x: inputx, + y: inputy, + }; + let add = self.add(&mut cs.namespace(|| "self.add()"), &input)?; + let add = add.reduce(&mut cs.namespace(|| "add.reduce()"))?; + + let resx = Fp2Element::conditionally_select( + &mut cs.namespace(|| "res x"), + &add.x, + &double.x, + &is_eq, + )?; + let resy = Fp2Element::conditionally_select( + &mut cs.namespace(|| "res y"), + &add.y, + &double.y, + &is_eq, + )?; + + Ok(Self { x: resx, y: resy }) + } + + /// Returns `-P` pub fn neg(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, @@ -161,6 +253,7 @@ impl G2Point { }) } + /// Returns `self - value`. Requires that `self != -value` and neither point is the identity since it calls `add` pub fn sub(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -170,12 +263,13 @@ impl G2Point { Ok(res) } + /// Returns `self + self` pub fn double(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, { - let p = self; let cs = &mut cs.namespace(|| "G2::double(p)"); + let p = self.reduce(&mut cs.namespace(|| "p <- p.reduce()"))?; // compute λ = (3p.x²)/2*p.y let xx3a = p.x.square(&mut cs.namespace(|| "xx3a <- p.x.square()"))?; let xx3a = xx3a.mul_const(&mut cs.namespace(|| "xx3a <- xx3a * 3"), &BigInt::from(3))?; @@ -195,6 +289,7 @@ impl G2Point { Ok(Self { x: xr, y: yr }) } + /// Calls `self.double()` repeated `n` times pub fn double_n(&self, cs: &mut CS, n: usize) -> Result where CS: ConstraintSystem, @@ -204,11 +299,11 @@ impl G2Point { let mut cs = cs.namespace(|| format!("G2::double_n(p, {n})")); for i in 0..n { if let Some(cur_p) = p { - let val = cur_p.double(&mut cs.namespace(|| format!("p <- p.double() ({i})")))?; - // TODO: This fails with an overflow without an explicit reduce call every few iterations, even though theoretically this should be happening automatically. needs further investigation - // even weirder, the constraint count for `scalar_mul_by_seed` goes up if this reduce is called less often - // currently this function is unused except for `scalar_mul_by_seed` which will be used for an `assert_is_on_g2` implementation - let val = val.reduce(&mut cs.namespace(|| format!("p <- p.reduce() ({i})")))?; + let mut val = + cur_p.double(&mut cs.namespace(|| format!("p <- p.double() ({i})")))?; + if i % 2 == 1 { + val = val.reduce(&mut cs.namespace(|| format!("p <- p.reduce() ({i})")))?; + } tmp = Some(val); p = tmp.as_ref(); } @@ -217,6 +312,7 @@ impl G2Point { Ok(tmp.unwrap()) } + /// Returns `self + self + self` pub fn triple(&self, cs: &mut CS) -> Result where CS: ConstraintSystem, @@ -254,6 +350,7 @@ impl G2Point { Ok(Self { x: xr, y: yr }) } + /// Returns `2*self + value` pub fn double_and_add(&self, cs: &mut CS, value: &Self) -> Result where CS: ConstraintSystem, @@ -290,14 +387,439 @@ impl G2Point { Ok(Self { x: x3, y: y3 }) } + + /// Implementation of the optimized simple SWU map to BLS12-381 G2. + /// Following [circom-pairing's implementation](https://github.com/yi-sun/circom-pairing/blob/107c316223a08ac577522c54edd81f0fc4c03130/circuits/bls12_381_hash_to_G2.circom#L11-L29). + /// + /// Takes an input `t` in Fp2 and returns a point on the 3-isogenous curve E2'. + /// + /// Additional references: + /// * [Section 8.8.2 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380.html#name-bls12-381-g2) + /// * [Section 4.2 of Wahby-Boneh](https://eprint.iacr.org/2019/403.pdf) + /// * [Reference python code from bls_sigs_ref](https://github.com/algorand/bls_sigs_ref/blob/master/python-impl/opt_swu_g2.py) + /// * [Code from bls12_381's hash_to_curve module](https://github.com/zkcrypto/bls12_381/blob/4df45188913e9d66ef36ae12825865347eed4e1b/src/hash_to_curve/map_g2.rs#L387-L388) + pub fn opt_simple_swu2( + cs: &mut CS, + t: &Fp2Element, + ) -> Result, SynthesisError> + where + CS: ConstraintSystem, + { + let cs = &mut cs.namespace(|| "G2::opt_simple_swu2(t)"); + + // xi <- (-2, -1) + let xi = Fp2Element::from_dec("2", "1").unwrap(); + let xi = xi.neg(&mut cs.namespace(|| "xi <- (-2, -1)"))?; + + // curve equation parameters for E2' + // a <- (0, 240) + // b <- (1012, 1012) + let a = Fp2Element::from_dec("0", "240").unwrap(); + let a_neg = a.neg(&mut cs.namespace(|| "a_neg <- -a"))?; + let b = Fp2Element::from_dec("1012", "1012").unwrap(); + + let t2 = t.square(&mut cs.namespace(|| "t2 <- t.square()"))?; + let xi_t2 = xi.mul(&mut cs.namespace(|| "xi_t2 <- xi * t2"), &t2)?; + let xi2_t4 = xi_t2.square(&mut cs.namespace(|| "xi2_t4 <- xi_t2.square()"))?; + + let num_den_common = xi2_t4.add(&mut cs.namespace(|| "ndc <- xi2_t4 + xi_t2"), &xi_t2)?; + + // Calculate num_den_common * a piece-wise for fewer constraints + let x0_den_a0 = num_den_common + .a1 + .mul(&mut cs.namespace(|| "x0_den_a0 <- ndc.a1 * a_a1"), &a.a1)?; + let x0_den_a1 = num_den_common.a0.mul( + &mut cs.namespace(|| "x0_den_a1 <- ndc.a0 * -a_a1"), + &a_neg.a1, + )?; + let x0_den = Fp2Element { + a0: x0_den_a0, + a1: x0_den_a1, + }; + + let x0_den = x0_den.reduce(&mut cs.namespace(|| "x0_den <- x0_den.reduce()"))?; + // if X0_den = 0, replace with X1_den = a * xi; this way X1(t) = X0_num / X1_den = b / (xi * a) + let is_den_0 = x0_den.alloc_is_zero(&mut cs.namespace(|| "is_den_0"))?; + + // X1_den = a * xi = 240 - 480 i + let x1_den = Fp2Element::from_dec("240", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559307").unwrap(); + + let ndc_a0 = num_den_common + .a0 + .add(&mut cs.namespace(|| "ndc <- ndc + 1"), &FpElement::one())?; + let num_den_common = Fp2Element { + a0: ndc_a0, + a1: num_den_common.a1, + }; + + // Calculate num_den_common * b piece-wise for fewer constraints + let tmp_ndc0 = b.a0.mul( + &mut cs.namespace(|| "tmp_ndc0 <- b * ndc.a0"), + &num_den_common.a0, + )?; + let tmp_ndc1 = b.a0.mul( + &mut cs.namespace(|| "tmp_ndc1 <- b * ndc.a1"), + &num_den_common.a1, + )?; + let x0_num_a0 = tmp_ndc0.sub( + &mut cs.namespace(|| "x0_num_a0 <- tmp_ndc0 - tmp_ndc1"), + &tmp_ndc1, + )?; + let x0_num_a1 = tmp_ndc0.add( + &mut cs.namespace(|| "x0_num_a1 <- tmp_ndc0 + tmp_ndc1"), + &tmp_ndc1, + )?; + let x0_num = Fp2Element { + a0: x0_num_a0, + a1: x0_num_a1, + }; + + let x0_den = Fp2Element::conditionally_select( + &mut cs.namespace(|| "x0_den <- select(x0_den, x1_den, is_den_0)"), + &x0_den, + &x1_den, + &Boolean::Is(is_den_0), + )?; + + let x0 = x0_num.div_unchecked(&mut cs.namespace(|| "x0 <- x0_num div x0_den"), &x0_den)?; + + // g(x) = x^3 + a x + b + // Compute g(X0(t)) + let x0_2 = x0.square(&mut cs.namespace(|| "x0_2 <- x0.square()"))?; + let x0_3 = x0_2.mul(&mut cs.namespace(|| "x0_3 <- x0_2 * x0"), &x0)?; + let ax0 = x0.mul(&mut cs.namespace(|| "ax0 <- x0 * a"), &a)?; + let gx0 = x0_3.add(&mut cs.namespace(|| "g <- x0_3 + b"), &b)?; + let gx0 = gx0.add(&mut cs.namespace(|| "g <- g + ax0"), &ax0)?; + + // X1(t) = xi * t^2 * X0(t) + let x1 = x0.mul(&mut cs.namespace(|| "x1 <- x0 * xi_t2"), &xi_t2)?; + + let xi3_t6 = xi2_t4.mul(&mut cs.namespace(|| "xi3_t6 <- xi2_t4 * xi_t2"), &xi_t2)?; + // g(X1(t)) = xi^3 * t^6 * g(X0(t)) + let gx1 = xi3_t6.mul(&mut cs.namespace(|| "gx1 <- xi3_t6 * gx0"), &gx0)?; + + // xi^3 is not a square, so one of gX0, gX1 must be a square + // isSquare = 1 if gX0 is a square, = 0 if gX1 is a square + // sqrt = sqrt(gX0) if isSquare = 1, sqrt = sqrt(gX1) if isSquare = 0 + + // Implementation is special to p^2 = 9 mod 16 + // References: + // p. 9 of https://eprint.iacr.org/2019/403.pdf + // F.2.1.1 for general version for any field: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-14#appendix-F.2.1.1 + + // we do not use the trick for combining division and sqrt from Section 5 of + // Bernstein, Duif, Lange, Schwabe, and Yang, "High-speed high-security signatures", + // since division is cheap in R1CS + + // Precompute sqrt_candidate = gX0^{(p^2 + 7) / 16} + // p^2 + 7 + let c1: BigInt = (Bls12381FpParams::modulus() * Bls12381FpParams::modulus()) + 7; + // (p^2 + 7) // 16 + let c2: BigInt = &c1 / BigInt::from(16); + + assert_eq!(&c1 % 16, BigInt::zero(), "p^2 + 7 divisible by 16"); + + let gx0_n = BlsFp2::from(&gx0); + let gx1_n = BlsFp2::from(&gx1); + let sqrt_candidate0 = fp2_pow_vartime(&gx0_n, &c2); + + // -1 is a square in Fp2 (because p^2 - 1 is even) so we only need to check half of the 8th roots of unity + let roots_of_unity = vec![ + fp2_from_dec("1", "0").unwrap(), + fp2_from_dec("0", "1").unwrap(), + fp2_from_dec("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257", "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257").unwrap(), + fp2_from_dec("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257", "2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530").unwrap(), + ]; + let mut is_square0_val: bool = false; + let mut sqrt_witness0: BlsFp2 = BlsFp2::zero(); + // if gX0 is a square, square root must be sqrt_candidate * (8th-root of unity) + for root in roots_of_unity.iter() { + let sqrt_tmp = sqrt_candidate0 * root; + if sqrt_tmp * sqrt_tmp == gx0_n { + is_square0_val = true; + sqrt_witness0 = sqrt_tmp; + break; + } + } + let is_square0 = Boolean::from(AllocatedBit::alloc( + &mut cs.namespace(|| "is_square0"), + Some(is_square0_val), + )?); + + // find square root of gX1 + // square root of gX1 must be = sqrt_candidate * t^3 * eta + // for one of four precomputed values of eta + // eta determined by eta^2 = xi^3 * (-1)^{-1/4} + let t_native = BlsFp2::from(t); + let t3 = t_native * t_native * t_native; + let sqrt_candidate1 = sqrt_candidate0 * t3; + + let etas = vec![ + fp2_from_dec("1015919005498129635886032702454337503112659152043614931979881174103627376789972962005013361970813319613593700736144", "1244231661155348484223428017511856347821538750986231559855759541903146219579071812422210818684355842447591283616181").unwrap(), + BlsFp2 { c0: bigint_to_fpelem(&(&Bls12381FpParams::modulus() - big_from_dec("1244231661155348484223428017511856347821538750986231559855759541903146219579071812422210818684355842447591283616181").unwrap())).unwrap(), c1: fp_from_dec("1015919005498129635886032702454337503112659152043614931979881174103627376789972962005013361970813319613593700736144").unwrap() }, + fp2_from_dec("1646015993121829755895883253076789309308090876275172350194834453434199515639474951814226234213676147507404483718679", "1637752706019426886789797193293828301565549384974986623510918743054325021588194075665960171838131772227885159387073").unwrap(), + BlsFp2 { c0: bigint_to_fpelem(&(&Bls12381FpParams::modulus() - big_from_dec("1637752706019426886789797193293828301565549384974986623510918743054325021588194075665960171838131772227885159387073").unwrap())).unwrap(), c1: fp_from_dec("1646015993121829755895883253076789309308090876275172350194834453434199515639474951814226234213676147507404483718679").unwrap() }, + ]; + let mut is_square1_val: bool = false; + let mut sqrt_witness1: BlsFp2 = BlsFp2::zero(); + for eta in etas.iter() { + let sqrt_tmp = sqrt_candidate1 * eta; + if sqrt_tmp * sqrt_tmp == gx1_n { + is_square1_val = true; + sqrt_witness1 = sqrt_tmp; + break; + } + } + + assert!( + is_square0_val || is_square1_val, + "one of gX0 or gX1 must be a square" + ); + let x0 = x0.reduce(&mut cs.namespace(|| "x0 <- x0.reduce()"))?; + let x1 = x1.reduce(&mut cs.namespace(|| "x1 <- x1.reduce()"))?; + + // X = out[0] = X0 if isSquare == 1, else X = X1 + let outx = Fp2Element::conditionally_select( + &mut cs.namespace(|| "outx <- select(x1, x0, is_square0)"), + &x1, + &x0, + &is_square0, + )?; + + let sgn_t = t.sgn0(&mut cs.namespace(|| "sgn_t <- t.sgn0()"))?; + + let mut outy_val = if is_square0_val { + sqrt_witness0 + } else { + sqrt_witness1 + }; + let y_sgn0: bool = outy_val.sgn0().into(); + let t_sgn0: bool = t_native.sgn0().into(); + if y_sgn0 != t_sgn0 { + outy_val = outy_val.neg(); + } + let outy = + Fp2Element::alloc_element(&mut cs.namespace(|| "alloc outy <- outy_val"), &outy_val)?; + + // enforce that Y^2 = g(X) + let y_sq = outy.square(&mut cs.namespace(|| "y_sq <- outy.square()"))?; + let gx0 = gx0.reduce(&mut cs.namespace(|| "gx0 <- gx0.reduce()"))?; + let gx1 = gx1.reduce(&mut cs.namespace(|| "gx1 <- gx1.reduce()"))?; + let gx = Fp2Element::conditionally_select( + &mut cs.namespace(|| "gx <- select(gx1, gx0, is_square0)"), + &gx1, + &gx0, + &is_square0, + )?; + Fp2Element::assert_is_equal(&mut cs.namespace(|| "y_sq == gx"), &y_sq, &gx)?; + + // sgn0(Y) == sgn0(t) + let sgn_y = outy.sgn0(&mut cs.namespace(|| "sgn_y <- outy.sgn0()"))?; + Boolean::enforce_equal(&mut cs.namespace(|| "sgn_y == sgn_t"), &sgn_y, &sgn_t)?; + + Ok(G2IsoPoint(Self { x: outx, y: outy })) + } + + /// Maps points from the 3-isogenous curve E' to the main curve E. + /// + /// Assumes that the point is not the point at infinity. Returns the point + /// at infinity if the input is infinity or if the input is one of the + /// isogeny poles. + /// + /// References: + /// * [Appendix E.3 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380.html#name-3-isogeny-map-for-bls12-381) + /// * [Section 4.3 of Wahby-Boneh](https://eprint.iacr.org/2019/403.pdf) + /// * [Reference python code from bls_sigs_ref](https://github.com/algorand/bls_sigs_ref/blob/master/python-impl/opt_swu_g2.py) + pub fn iso3_map(cs: &mut CS, p: &G2IsoPoint) -> Result + where + CS: ConstraintSystem, + { + // list of coefficients from appendix E.3 of the RFC + let iso3_coeffs = vec![ + vec![ + Fp2Element::from_dec("889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542", "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235542").unwrap(), + Fp2Element::from_dec("0", "2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706522").unwrap(), + Fp2Element::from_dec("2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706526", "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853261").unwrap(), + Fp2Element::from_dec("3557697382419259905260257622876359250272784728834673675850718343221361467102966990615722337003569479144794908942033", "0").unwrap(), + ], + vec![ + Fp2Element::from_dec("0", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559715").unwrap(), + Fp2Element::from_dec("12", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559775").unwrap(), + ], + vec![ + Fp2Element::from_dec("3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558", "3261222600550988246488569487636662646083386001431784202863158481286248011511053074731078808919938689216061999863558").unwrap(), + Fp2Element::from_dec("0", "889424345604814976315064405719089812568196182208668418962679585805340366775741747653930584250892369786198727235518").unwrap(), + Fp2Element::from_dec("2668273036814444928945193217157269437704588546626005256888038757416021100327225242961791752752677109358596181706524", "1334136518407222464472596608578634718852294273313002628444019378708010550163612621480895876376338554679298090853263").unwrap(), + Fp2Element::from_dec("2816510427748580758331037284777117739799287910327449993381818688383577828123182200904113516794492504322962636245776", "0").unwrap(), + ], + vec![ + Fp2Element::from_dec("4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559355").unwrap(), + Fp2Element::from_dec("0", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559571").unwrap(), + Fp2Element::from_dec("18", "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559769").unwrap(), + ], + ]; + + // x = x_num / x_den + // y = y' * y_num / y_den + // x_num = sum_{i=0}^3 coeffs[0][i] * x'^i + // x_den = x'^2 + coeffs[1][1] * x' + coeffs[1][0] + // y_num = sum_{i=0}^3 coeffs[2][i] * x'^i + // y_den = x'^3 + sum_{i=0}^2 coeffs[3][i] * x'^i + + let xp1 = p.0.x.clone(); + let xp2 = p.0.x.square(&mut cs.namespace(|| "xp2 <- P.x.square()"))?; + let xp3 = xp2.mul(&mut cs.namespace(|| "xp3 <- xp2 * P.x"), &p.0.x)?; + let xp_pow = vec![xp1.clone(), xp2.clone(), xp3.clone()]; + let deg = [3, 1, 3, 2]; + let mut coeffs_xp = vec![]; + for i in 0..4 { + let mut coeffs_j = vec![]; + for (j, xp) in xp_pow.iter().enumerate().take(deg[i]) { + let coeff = iso3_coeffs[i][j + 1].mul( + &mut cs.namespace(|| { + format!("coeff_xp_{i}_{j} <- coeffs[{i}][{}] * xp_pow[{j}]", j + 1) + }), + xp, + )?; + coeffs_j.push(coeff); + } + coeffs_xp.push(coeffs_j); + } + let mut x_frac = vec![]; + for i in 0..4 { + let mut x_f = iso3_coeffs[i][0].clone(); + for j in 0..deg[i] { + x_f = x_f.add( + &mut cs.namespace(|| format!("x_f_{i} <- x_f_{i} + coeff_xp_{i}_{j}")), + &coeffs_xp[i][j], + )?; + } + x_frac.push(x_f); + } + x_frac[1] = x_frac[1].add(&mut cs.namespace(|| "x_f_1 <- x_f_1 + xp2"), &xp2)?; + x_frac[3] = x_frac[3].add(&mut cs.namespace(|| "x_f_3 <- x_f_3 + xp3"), &xp3)?; + + let den_0 = x_frac[1].clone(); + let den_1 = x_frac[3].clone(); + let den_0_is_zero = den_0.alloc_is_zero(&mut cs.namespace(|| "den_0_is_zero"))?; + let den_1_is_zero = den_1.alloc_is_zero(&mut cs.namespace(|| "den_1_is_zero"))?; + let is_infinity = Boolean::or( + &mut cs.namespace(|| "is_infinity <- or(den_0_is_zero, den_1_is_zero)"), + &Boolean::from(den_0_is_zero), + &Boolean::from(den_1_is_zero), + )?; + let den_0 = den_0.reduce(&mut cs.namespace(|| "den_0 <- den_0.reduce()"))?; + let den_1 = den_1.reduce(&mut cs.namespace(|| "den_1 <- den_1.reduce()"))?; + let den_0 = Fp2Element::conditionally_select( + &mut cs.namespace(|| "den_0 <- select(den_0, 1, is_infinity)"), + &den_0, + &Fp2Element::one(), + &is_infinity, + )?; + let den_1 = Fp2Element::conditionally_select( + &mut cs.namespace(|| "den_1 <- select(den_1, 1, is_infinity)"), + &den_1, + &Fp2Element::one(), + &is_infinity, + )?; + + let num_0 = x_frac[0].clone(); + let num_1 = x_frac[2].clone(); + + // num / den if den != 0, else num / 1 + let x_0 = num_0.div_unchecked(&mut cs.namespace(|| "x_0 <- num_0 div den_0"), &den_0)?; + let x_1 = num_1.div_unchecked(&mut cs.namespace(|| "x_1 <- num_1 div den_1"), &den_1)?; + + let y = p.0.y.mul(&mut cs.namespace(|| "y <- P.y * x_1"), &x_1)?; + + Ok(Self { x: x_0, y }) + } + + /// Clears the cofactor of a point to ensure it lies in the proper E2 + /// subgroup. Returns `[x^2 - x - 1]P + [x-1]*psi(P) + psi2(2*P)` where `x` + /// is the BLS parameter for BLS12-381, `-15132376222941642752` + pub fn clear_cofactor(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + let t1 = self.scalar_mul_by_seed(&mut cs.namespace(|| "t1 <- p.scalar_mul_by_seed()"))?; + let t2 = self.psi(&mut cs.namespace(|| "t2 <- p.psi()"))?; + + let z = self.double(&mut cs.namespace(|| "z <- p.double()"))?; + let z = z.psi2(&mut cs.namespace(|| "z <- z.psi2()"))?; + let t3 = t1.add_or_double(&mut cs.namespace(|| "t3 <- t1 + t2"), &t2)?; + let t3 = t3.scalar_mul_by_seed(&mut cs.namespace(|| "t3 <- t3.scalar_mul_by_seed()"))?; + let z = z.add_or_double(&mut cs.namespace(|| "z <- z + t3"), &t3)?; + let z = z.sub(&mut cs.namespace(|| "z <- z - t1"), &t1)?; + let z = z.sub(&mut cs.namespace(|| "z <- z - t2"), &t2)?; + let z = z.sub(&mut cs.namespace(|| "z <- z - p"), self)?; + + Ok(z) + } + + /// Given two Fp2 field elements `p1` and `p2`, calculate a point in G2 of BLS12-381. + /// Corresponds to function `map_to_curve` from [section 3 of RFC 9380](https://www.rfc-editor.org/rfc/rfc9380.html#name-encoding-byte-strings-to-el). + pub fn map_to_g2( + cs: &mut CS, + p1: &Fp2Element, + p2: &Fp2Element, + ) -> Result + where + CS: ConstraintSystem, + { + let u0 = Self::opt_simple_swu2(&mut cs.namespace(|| "u0 <- p1.opt_simple_swu2()"), p1)?; + let u1 = Self::opt_simple_swu2(&mut cs.namespace(|| "u1 <- p2.opt_simple_swu2()"), p2)?; + // we can use the regular addition function before calling iso3_map + let z = G2IsoPoint(u0.0.add(&mut cs.namespace(|| "z <- u0 + u1"), &u1.0)?); + + let z = Self::iso3_map(&mut cs.namespace(|| "z <- z.iso3_map()"), &z)?; + + let z = z.clear_cofactor(&mut cs.namespace(|| "z <- z.clear_cofactor()"))?; + // TODO: ensure z is infinity if either of the previous 2 ops is infinity? needs tests around isInfinity and exceptional cases (currently returns DivisionByZero error) + + Ok(z) + } + + /// Asserts that y^2 = x^3 + ax + b + pub fn assert_is_on_curve(&self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let y_2 = self.y.square(&mut cs.namespace(|| "y_2 <- p.y.square()"))?; + let x_2 = self.x.square(&mut cs.namespace(|| "x_2 <- p.x.square()"))?; + let x_3 = self.x.mul(&mut cs.namespace(|| "x_3 <- x * x_2"), &x_2)?; + let ax = self + .x + .mul(&mut cs.namespace(|| "ax <- x * a"), &Bls12381G2Params::a())?; + let rhs = x_3.add(&mut cs.namespace(|| "rhs <- x_3 + ax"), &ax)?; + let rhs = rhs.add( + &mut cs.namespace(|| "rhs <- rhs + b"), + &Bls12381G2Params::b(), + )?; + Fp2Element::assert_is_equal(&mut cs.namespace(|| "y_2 == rhs"), &y_2, &rhs)?; + Ok(()) + } + + /// Asserts that `psi(P) == [x]P` + pub fn assert_subgroup_check(&self, cs: &mut CS) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + { + let a = self.psi(&mut cs.namespace(|| "a <- p.psi()"))?; + let b = self.scalar_mul_by_seed(&mut cs.namespace(|| "b <- p.scalar_mul_by_seed()"))?; + Self::assert_is_equal(&mut cs.namespace(|| "a == b"), &a, &b)?; + Ok(()) + } } #[cfg(test)] mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::group::Group; - use pasta_curves::Fp; + use bls12_381::{hash_to_curve::MapToCurve, Scalar}; + use ff::Field; + use halo2curves::bn256::Fq as Fp; + use halo2curves::group::Group; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -325,8 +847,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["9592"]); - expect_eq(cs.num_constraints(), &expect!["9556"]); + expect_eq(cs.scalar_aux().len(), &expect!["5365"]); + expect_eq(cs.num_constraints(), &expect!["5287"]); } #[test] @@ -348,8 +870,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1084"]); - expect_eq(cs.num_constraints(), &expect!["1048"]); + expect_eq(cs.scalar_aux().len(), &expect!["948"]); + expect_eq(cs.num_constraints(), &expect!["904"]); } #[test] @@ -371,8 +893,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["16685"]); - expect_eq(cs.num_constraints(), &expect!["16697"]); + expect_eq(cs.scalar_aux().len(), &expect!["8185"]); + expect_eq(cs.num_constraints(), &expect!["8129"]); } #[test] @@ -394,8 +916,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["9605"]); - expect_eq(cs.num_constraints(), &expect!["9593"]); + expect_eq(cs.scalar_aux().len(), &expect!["5378"]); + expect_eq(cs.num_constraints(), &expect!["5328"]); } #[test] @@ -419,8 +941,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["9592"]); - expect_eq(cs.num_constraints(), &expect!["9556"]); + expect_eq(cs.scalar_aux().len(), &expect!["5365"]); + expect_eq(cs.num_constraints(), &expect!["5287"]); } #[test] @@ -451,39 +973,301 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["16720"]); - expect_eq(cs.num_constraints(), &expect!["16708"]); - } - - // NOTE: this test currently takes a few minutes to run, so it's commented out - // #[test] - // fn test_random_mul_by_seed() { - // let mut rng = rand::thread_rng(); - // let a = G2Projective::random(&mut rng); - // let x0 = bls12_381::Scalar::from(15132376222941642752); - // let c = a * x0; - // let c = -c; - // let a = G2Affine::from(a); - // let c = G2Affine::from(c); - - // let mut cs = TestConstraintSystem::::new(); - // let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); - // let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); - // let res_alloc = a_alloc - // .scalar_mul_by_seed(&mut cs.namespace(|| "a.mul_by_seed()")) - // .unwrap(); - // G2Point::assert_is_equal( - // &mut cs.namespace(|| "a.mul_by_seed() = c"), - // &res_alloc, - // &c_alloc, - // ) - // .unwrap(); - // if !cs.is_satisfied() { - // eprintln!("{:?}", cs.which_is_unsatisfied()) - // } - // assert!(cs.is_satisfied()); - // expect_eq(cs.num_inputs(), expect!["1"]); - // expect_eq(cs.scalar_aux().len(), expect!["788217"]); - // expect_eq(cs.num_constraints(), expect!["790797"]); - // } + expect_eq(cs.scalar_aux().len(), &expect!["8192"]); + expect_eq(cs.num_constraints(), &expect!["8108"]); + } + + #[test] + fn test_random_mul_by_seed() { + let mut rng = rand::thread_rng(); + let a = G2Projective::random(&mut rng); + let x0 = bls12_381::Scalar::from(15132376222941642752); + let c = a * x0; + let c = -c; + let a = G2Affine::from(a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc + .scalar_mul_by_seed(&mut cs.namespace(|| "a.mul_by_seed()")) + .unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "a.mul_by_seed() = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["440023"]); + expect_eq(cs.num_constraints(), &expect!["440303"]); + } + + #[test] + fn test_random_subgroup_check_positive() { + let mut rng = rand::thread_rng(); + let n = Scalar::random(&mut rng); + let a = G2Affine::from(G2Projective::generator() * n); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + a_alloc + .assert_subgroup_check(&mut cs.namespace(|| "a.subgroup_check()")) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["441619"]); + expect_eq(cs.num_constraints(), &expect!["441927"]); + } + + #[test] + fn test_random_psi2() { + let mut rng = rand::thread_rng(); + let a = G2Projective::random(&mut rng); + let c = a.psi2(); + let a = G2Affine::from(a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc.psi2(&mut cs.namespace(|| "a.psi2()")).unwrap(); + G2Point::assert_is_equal(&mut cs.namespace(|| "a.psi2() = c"), &res_alloc, &c_alloc) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["1748"]); + expect_eq(cs.num_constraints(), &expect!["1704"]); + } + + #[test] + fn test_random_psi() { + let mut rng = rand::thread_rng(); + let a = G2Projective::random(&mut rng); + let c = a.psi(); + let a = G2Affine::from(a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc.psi(&mut cs.namespace(|| "a.psi()")).unwrap(); + G2Point::assert_is_equal(&mut cs.namespace(|| "a.psi() = c"), &res_alloc, &c_alloc) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["2572"]); + expect_eq(cs.num_constraints(), &expect!["2528"]); + } + + #[test] + fn test_random_clear_cofactor_torsion_free_point() { + let mut rng = rand::thread_rng(); + let a = G2Projective::random(&mut rng); + let c = a.clear_cofactor(); + let a = G2Affine::from(a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc + .clear_cofactor(&mut cs.namespace(|| "a.clear_cofactor()")) + .unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "a.clear_cofactor() = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["965906"]); + expect_eq(cs.num_constraints(), &expect!["966732"]); + } + + #[test] + fn test_random_clear_cofactor_torsion_point() { + use crate::curves::params::EmulatedCurveParams; + let b = BlsFp2::from(&Bls12381G2Params::::b()); + use rand::RngCore; + let mut rng = rand::thread_rng(); + let mut random_point = || loop { + let x = BlsFp2::random(&mut rng); + let y = ((x.square() * x) + b).sqrt(); + if y.is_some().into() { + let flip_sign = rng.next_u32() % 2 != 0; + return G2Affine { + x, + y: if flip_sign { -y.unwrap() } else { y.unwrap() }, + infinity: 0.into(), + }; + } + }; + let mut a = random_point(); + while a.is_torsion_free().into() { + a = random_point(); + } + let c = G2Projective::from(a).clear_cofactor(); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = a_alloc + .clear_cofactor(&mut cs.namespace(|| "a.clear_cofactor()")) + .unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "a.clear_cofactor() = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["965906"]); + expect_eq(cs.num_constraints(), &expect!["966732"]); + } + + #[test] + fn test_random_subgroup_check_negative() { + use crate::curves::params::EmulatedCurveParams; + let b = BlsFp2::from(&Bls12381G2Params::::b()); + use rand::RngCore; + let mut rng = rand::thread_rng(); + let mut random_point = || loop { + let x = BlsFp2::random(&mut rng); + let y = ((x.square() * x) + b).sqrt(); + if y.is_some().into() { + let flip_sign = rng.next_u32() % 2 != 0; + return G2Affine { + x, + y: if flip_sign { -y.unwrap() } else { y.unwrap() }, + infinity: 0.into(), + }; + } + }; + let mut a = random_point(); + while a.is_torsion_free().into() { + a = random_point(); + } + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + a_alloc + .assert_subgroup_check(&mut cs.namespace(|| "a.subgroup_check()")) + .unwrap(); + assert!(!cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["441619"]); + expect_eq(cs.num_constraints(), &expect!["441927"]); + } + + #[test] + fn test_random_opt_simple_swu2() { + let mut rng = rand::thread_rng(); + let a = BlsFp2::random(&mut rng); + let c = bls12_381::hash_to_curve::map_g2::map_to_curve_simple_swu(&a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = + G2Point::opt_simple_swu2(&mut cs.namespace(|| "opt_simple_swu2(a)"), &a_alloc).unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "opt_simple_swu2(a) = c"), + &res_alloc.0, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["107401"]); + expect_eq(cs.num_constraints(), &expect!["107798"]); + } + + #[test] + fn test_random_iso3_map() { + let mut rng = rand::thread_rng(); + let a = BlsFp2::random(&mut rng); + let a = bls12_381::hash_to_curve::map_g2::map_to_curve_simple_swu(&a); // this ensures a is in E2' + let c = bls12_381::hash_to_curve::map_g2::iso_map(&a); + let a = G2Affine::from(a); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = + G2IsoPoint(G2Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap()); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = G2Point::iso3_map(&mut cs.namespace(|| "iso3_map(a)"), &a_alloc).unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "iso3_map(a) = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["38543"]); + expect_eq(cs.num_constraints(), &expect!["38639"]); + } + + #[test] + fn test_random_map_to_g2() { + let mut rng = rand::thread_rng(); + let x = BlsFp2::random(&mut rng); + let y = BlsFp2::random(&mut rng); + let p1 = G2Projective::map_to_curve(&x); + let p2 = G2Projective::map_to_curve(&y); + let c = (p1 + p2).clear_h(); + let c = G2Affine::from(c); + + let mut cs = TestConstraintSystem::::new(); + let x_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc x"), &x).unwrap(); + let y_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc y"), &y).unwrap(); + let c_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = + G2Point::map_to_g2(&mut cs.namespace(|| "map_to_g2(x, y)"), &x_alloc, &y_alloc) + .unwrap(); + G2Point::assert_is_equal( + &mut cs.namespace(|| "map_to_g2(x, y) = c"), + &res_alloc, + &c_alloc, + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["1450871"]); + expect_eq(cs.num_constraints(), &expect!["1453683"]); + } } diff --git a/crates/bls12381/src/curves/pairing.rs b/crates/bls12381/src/curves/pairing.rs index 8dd00d8..c88eda3 100644 --- a/crates/bls12381/src/curves/pairing.rs +++ b/crates/bls12381/src/curves/pairing.rs @@ -4,9 +4,6 @@ use bellpepper_core::{ boolean::{AllocatedBit, Boolean}, ConstraintSystem, SynthesisError, }; -use bls12_381::fp12::Fp12 as BlsFp12; -use bls12_381::fp2::Fp2 as BlsFp2; -use bls12_381::fp6::Fp6 as BlsFp6; use ff::PrimeFieldBits; use num_bigint::BigInt; @@ -104,14 +101,14 @@ impl EmulatedBls12381Pairing { res.v0[n - 2] = tmp0; res.v1[n - 2] = tmp1; let mut i = n - 3; - let mut j = 1; while i >= 1 { if LOOP_COUNTER[i] == 0 { let (tmpq, tmp0) = Self::double_step( &mut cs.namespace(|| format!("q_acc,tmp0 <- double_step(q_acc) ({i})")), &q_acc, )?; - q_acc = tmpq; + q_acc = + tmpq.reduce(&mut cs.namespace(|| format!("q_acc <- tmpq.reduce() ({i})")))?; res.v0[i] = tmp0; } else { let (tmpq, tmp0, tmp1) = Self::double_and_add_step( @@ -121,19 +118,13 @@ impl EmulatedBls12381Pairing { &q_acc, q, )?; - q_acc = tmpq; + q_acc = + tmpq.reduce(&mut cs.namespace(|| format!("q_acc <- tmpq.reduce() ({i})")))?; res.v0[i] = tmp0; res.v1[i] = tmp1; } - // TODO: This fails with an overflow without an explicit reduce call every few iterations, even though theoretically this should be happening automatically. - if j % 16 == 0 { - q_acc = - q_acc.reduce(&mut cs.namespace(|| format!("q_acc <- q_acc.reduce() ({i})")))?; - } - i -= 1; - j += 1; } let tmp0 = Self::tangent_compute(cs, &q_acc)?; res.v0[0] = tmp0; @@ -196,6 +187,7 @@ impl EmulatedBls12381Pairing { p: &G2Point, ) -> Result<(G2Point, LineEval), SynthesisError> { let cs = &mut cs.namespace(|| "double_step(p)"); + let p = p.reduce(&mut cs.namespace(|| "p <- p.reduce()"))?; // λ = 3x²/2y let n = p.x.square(&mut cs.namespace(|| "n <- p.x.square()"))?; let n = n.mul_const(&mut cs.namespace(|| "n <- n * 3"), &BigInt::from(3))?; @@ -326,9 +318,7 @@ impl EmulatedBls12381Pairing { x_neg_over_y.push(x); } - // TODO: there should be some unnecessary allocs due to how we're overwriting some res values below, double check - let mut res = - Fp12Element::alloc_element(&mut cs.namespace(|| "res <- 1"), &BlsFp12::one())?; + let mut res = Fp12Element::one(); // Compute ∏ᵢ { fᵢ_{x₀,Q}(P) } @@ -349,8 +339,7 @@ impl EmulatedBls12381Pairing { &x_neg_over_y[0], )? .clone(); - res.c1.b1 = - Fp2Element::alloc_element(&mut cs.namespace(|| "res.c1.b1 <- 1"), &BlsFp2::one())?; + res.c1.b1 = Fp2Element::one(); let tmp0 = lines[0].v1[62].r1.mul_element( &mut cs.namespace(|| "tmp0 <- l[0][1][62].r1 * y_inv[0]"), @@ -481,10 +470,8 @@ where is_single_pairing: bool, ) -> Result, SynthesisError> { let cs = &mut cs.namespace(|| format!("final_exponentiation(e, {is_single_pairing})")); - let mut e = gt.clone(); + let mut e = gt.reduce(&mut cs.namespace(|| "e <- e.reduce()"))?; let mut sel1: Option = None; - let mut dummy: Option> = None; - // 1. Easy part // (p⁶-1)(p²+1) @@ -497,19 +484,15 @@ where // However, for a product of Miller loops (n>=2) this might happen. If this is // the case, the result is 1 in the torus. We assign a dummy value (1) to e.C1 // and proceed further. - dummy = Some(Fp6Element::::alloc_element( - &mut cs.namespace(|| "alloc dummy"), - &BlsFp6::one(), - )?); // TODO: do we need to explicit alloc here or could this be a constant? sel1 = Some(e.c1.alloc_is_zero(&mut cs.namespace(|| "sel1 <- e.c1.is_zero()"))?); - // reduce e.c1 explicitly so both e.c1 and dummy have the same number of limbs (for conditionally_select) + // reduce e.c1 explicitly so both e.c1 and the const dummy have the same number of limbs (for conditionally_select) let ec1 = e.c1.reduce(&mut cs.namespace(|| "e.c1 <- e.c1.reduce()"))?; e = Fp12Element { c0: e.c0, c1: Fp6Element::conditionally_select( - &mut cs.namespace(|| "e.c1 <- select(e.c1, dummy, sel1)"), + &mut cs.namespace(|| "e.c1 <- select(e.c1, 1, sel1)"), &ec1, - dummy.as_ref().unwrap(), + &Fp6Element::one(), &Boolean::Is(sel1.clone().unwrap()), )?, }; @@ -570,9 +553,9 @@ where let sel2: AllocatedBit = sum.alloc_is_zero(&mut cs.namespace(|| "sel2 <- sum.is_zero()"))?; let t1 = Fp6Element::conditionally_select( - &mut cs.namespace(|| "t1 <- select(t1, dummy, sel2)"), + &mut cs.namespace(|| "t1 <- select(t1, 1, sel2)"), &t1, - &dummy.unwrap(), + &Fp6Element::one(), &Boolean::Is(sel2.clone()), )?; let selector = AllocatedBit::nor( @@ -584,13 +567,9 @@ where let t1 = Torus(t1); let res = ct.mul(&mut cs.namespace(|| "res <- ct * t1"), &t1)?; let res = res.decompress(&mut cs.namespace(|| "res <- res.decompress()"))?; - let dummy2 = Fp12Element::::alloc_element( - &mut cs.namespace(|| "alloc dummy2"), - &BlsFp12::one(), - )?; // TODO: does this need an explicit alloc or could this be a constant? let res = Fp12Element::conditionally_select( &mut cs.namespace(|| "res <- select(1, res, selector)"), - &dummy2, + &Fp12Element::one(), &res, &Boolean::Is(selector), )?; @@ -623,100 +602,97 @@ where #[cfg(test)] mod tests { - // use super::*; - // use bellpepper_core::test_cs::TestConstraintSystem; - // use pasta_curves::group::Group; - // use pasta_curves::Fp; - - // use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective}; - - // use expect_test::{expect, Expect}; - // fn expect_eq(computed: usize, expected: Expect) { - // expected.assert_eq(&computed.to_string()); - // } - - // NOTE: this test currently takes ~100GB of ram and a few minutes to run, so it's commented out - // #[test] - // fn test_random_pairing() { - // let mut rng = rand::thread_rng(); - // let a = G1Projective::random(&mut rng); - // let b = G2Projective::random(&mut rng); - // let a = G1Affine::from(a); - // let b = G2Affine::from(b); - // let c = bls12_381::pairing(&a, &b); - // let c = c.0; - - // let mut cs = TestConstraintSystem::::new(); - // let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); - // let b_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); - // let c_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); - // let res_alloc = EmulatedBls12381Pairing::pair( - // &mut cs.namespace(|| "pair(a, b)"), - // &[a_alloc], - // &[b_alloc], - // ) - // .unwrap(); - // Fp12Element::assert_is_equal(&mut cs.namespace(|| "pair(a, b) = c"), &res_alloc, &c_alloc) - // .unwrap(); - // if !cs.is_satisfied() { - // eprintln!("{:?}", cs.which_is_unsatisfied()) - // } - // assert!(cs.is_satisfied()); - // expect_eq(cs.num_inputs(), expect!["1"]); - // expect_eq(cs.scalar_aux().len(), expect!["27479875"]); - // expect_eq(cs.num_constraints(), expect!["27589382"]); - // } - - // NOTE: this test currently takes ~140GB of ram and a lot of minutes to run, so it's commented out (but it works!) - // #[test] - // fn test_random_multi_pairing() { - // let mut rng = rand::thread_rng(); - // let a = vec![ - // G1Projective::random(&mut rng), - // G1Projective::random(&mut rng), - // ]; - // let b = vec![ - // G2Projective::random(&mut rng), - // G2Projective::random(&mut rng), - // ]; - // let a: Vec = a.into_iter().map(G1Affine::from).collect(); - // let b: Vec = b.iter().map(G2Affine::from).collect(); - // let b_prep: Vec = b.iter().cloned().map(G2Prepared::from).collect(); - // let terms: Vec<(&G1Affine, &G2Prepared)> = a - // .iter() - // .zip(b_prep.iter()) - // .map(|(g1, g2)| (g1, g2)) - // .collect(); - // let c = bls12_381::multi_miller_loop(&terms).final_exponentiation(); - // let c = c.0; - - // let mut cs = TestConstraintSystem::::new(); - // let a_allocs: Vec> = a - // .iter() - // .enumerate() - // .map(|(idx, a)| { - // G1Point::alloc_element(&mut cs.namespace(|| format!("alloc a {idx}")), &a).unwrap() - // }) - // .collect(); - // let b_allocs: Vec> = b - // .iter() - // .enumerate() - // .map(|(idx, b)| { - // G2Point::alloc_element(&mut cs.namespace(|| format!("alloc b {idx}")), &b).unwrap() - // }) - // .collect(); - // let c_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); - // let res_alloc = - // EmulatedBls12381Pairing::pair(&mut cs.namespace(|| "pair(a, b)"), &a_allocs, &b_allocs) - // .unwrap(); - // Fp12Element::assert_is_equal(&mut cs.namespace(|| "pair(a, b) = c"), &res_alloc, &c_alloc) - // .unwrap(); - // if !cs.is_satisfied() { - // eprintln!("{:?}", cs.which_is_unsatisfied()) - // } - // assert!(cs.is_satisfied()); - // expect_eq(cs.num_inputs(), expect!["1"]); - // expect_eq(cs.scalar_aux().len(), expect!["42514420"]); - // expect_eq(cs.num_constraints(), expect!["42690960"]); - // } + use super::*; + use bellpepper_core::test_cs::TestConstraintSystem; + use halo2curves::bn256::Fq as Fp; + use halo2curves::group::Group; + + use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective}; + + use expect_test::{expect, Expect}; + fn expect_eq(computed: usize, expected: &Expect) { + expected.assert_eq(&computed.to_string()); + } + + // NOTE: this test currently takes ~22GB of ram and ~50s to run + #[test] + fn test_random_pairing() { + let mut rng = rand::thread_rng(); + let a = G1Projective::random(&mut rng); + let b = G2Projective::random(&mut rng); + let a = G1Affine::from(a); + let b = G2Affine::from(b); + let c = bls12_381::pairing(&a, &b); + let c = c.0; + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = G1Point::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let b_alloc = G2Point::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); + let c_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = EmulatedBls12381Pairing::pair( + &mut cs.namespace(|| "pair(a, b)"), + &[a_alloc], + &[b_alloc], + ) + .unwrap(); + Fp12Element::assert_is_equal(&mut cs.namespace(|| "pair(a, b) = c"), &res_alloc, &c_alloc) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["7142222"]); + expect_eq(cs.num_constraints(), &expect!["7147240"]); + } + + // NOTE: this test currently takes ~50GB of ram and ~110s to run + #[test] + fn test_random_multi_pairing() { + use bls12_381::G2Prepared; + let mut rng = rand::thread_rng(); + let a = vec![ + G1Projective::random(&mut rng), + G1Projective::random(&mut rng), + ]; + let b = vec![ + G2Projective::random(&mut rng), + G2Projective::random(&mut rng), + ]; + let a: Vec = a.into_iter().map(G1Affine::from).collect(); + let b: Vec = b.iter().map(G2Affine::from).collect(); + let b_prep: Vec = b.iter().cloned().map(G2Prepared::from).collect(); + let terms: Vec<(&G1Affine, &G2Prepared)> = a.iter().zip(b_prep.iter()).collect(); + let c = bls12_381::multi_miller_loop(&terms).final_exponentiation(); + let c = c.0; + + let mut cs = TestConstraintSystem::::new(); + let a_allocs: Vec> = a + .iter() + .enumerate() + .map(|(idx, a)| { + G1Point::alloc_element(&mut cs.namespace(|| format!("alloc a {idx}")), a).unwrap() + }) + .collect(); + let b_allocs: Vec> = b + .iter() + .enumerate() + .map(|(idx, b)| { + G2Point::alloc_element(&mut cs.namespace(|| format!("alloc b {idx}")), b).unwrap() + }) + .collect(); + let c_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); + let res_alloc = + EmulatedBls12381Pairing::pair(&mut cs.namespace(|| "pair(a, b)"), &a_allocs, &b_allocs) + .unwrap(); + Fp12Element::assert_is_equal(&mut cs.namespace(|| "pair(a, b) = c"), &res_alloc, &c_alloc) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["17043247"]); + expect_eq(cs.num_constraints(), &expect!["17085695"]); + } } diff --git a/crates/bls12381/src/curves/params.rs b/crates/bls12381/src/curves/params.rs index 20cf89d..6e0dceb 100644 --- a/crates/bls12381/src/curves/params.rs +++ b/crates/bls12381/src/curves/params.rs @@ -43,29 +43,23 @@ impl EmulatedCurveParams> for Bls12381G2Params< fn b() -> Fp2Element { // 4*(u + 1) becomes (4, 4) - Fp2Element::::from_dec(("4", "4")).unwrap() + Fp2Element::::from_dec("4", "4").unwrap() } fn generator() -> (Fp2Element, Fp2Element) { // https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-10.html#name-bls-curves-for-the-128-bit- - let x = Fp2Element::::from_dec(("352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160", "3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758")).unwrap(); - let y = Fp2Element::::from_dec(("1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905", "927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582")).unwrap(); + let x = Fp2Element::::from_dec("352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160", "3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758").unwrap(); + let y = Fp2Element::::from_dec("1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905", "927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582").unwrap(); (x, y) } } impl Bls12381G2Params { - pub fn u1() -> FpElement { - FpElement::::from_dec("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437").unwrap() + pub fn c0() -> Fp2Element { + Fp2Element::::from_dec("0", "4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939437").unwrap() } - pub fn v() -> Fp2Element { - Fp2Element::::from_dec(("2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530", "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257")).unwrap() - } -} - -impl Bls12381G1Params { - pub fn w() -> FpElement { - FpElement::::from_dec("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436").unwrap() + pub fn c1() -> Fp2Element { + Fp2Element::::from_dec("2973677408986561043442465346520108879172042883009249989176415018091420807192182638567116318576472649347015917690530", "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257").unwrap() } } diff --git a/crates/bls12381/src/fields/fp.rs b/crates/bls12381/src/fields/fp.rs index be72405..df94483 100644 --- a/crates/bls12381/src/fields/fp.rs +++ b/crates/bls12381/src/fields/fp.rs @@ -13,13 +13,13 @@ pub struct Bls12381FpParams; impl EmulatedFieldParams for Bls12381FpParams { // TODO: Depending on the native field, different limb/bit pairs are more optimal and have less waste. This should be customizable and not hardcoded - // for example, in the pasta field, 4/96 could be used instead + // for example, in the pasta field, 4/96 could be used instead for savings, but in bn256 7/55 is better fn num_limbs() -> usize { - 6 + 7 } fn bits_per_limb() -> usize { - 64 + 55 } fn modulus() -> BigInt { @@ -39,6 +39,13 @@ impl EmulatedFieldParams for Bls12381FpParams { } } +impl Bls12381FpParams { + /// Third root of unity + pub fn w() -> FpElement { + FpElement::::from_dec("4002409555221667392624310435006688643935503118305586438271171395842971157480381377015405980053539358417135540939436").unwrap() + } +} + pub type Bls12381Fp = EmulatedFieldElement; #[derive(Clone)] @@ -113,6 +120,14 @@ where bigint_to_fpelem(&val).unwrap() } +pub(crate) fn big_from_dec(v: &str) -> Option { + BigInt::parse_bytes(v.as_bytes(), 10) +} + +pub(crate) fn fp_from_dec(v: &str) -> Option { + big_from_dec(v).as_ref().and_then(bigint_to_fpelem) +} + impl From<&FpElement> for BlsFp where F: PrimeFieldBits, @@ -122,13 +137,17 @@ where } } +impl From<&FpElement> for BigInt { + fn from(value: &FpElement) -> Self { + use std::ops::Rem; + let p = &Bls12381FpParams::modulus(); + Self::from(&value.0).rem(p) + } +} + impl FpElement { pub fn from_dec(val: &str) -> Option { - BigInt::parse_bytes(val.as_bytes(), 10) - .as_ref() - .and_then(bigint_to_fpelem) - .as_ref() - .map(Self::from) + fp_from_dec(val).as_ref().map(Self::from) } pub fn zero() -> Self { @@ -276,13 +295,21 @@ impl FpElement { )?; Ok(Self(res)) } + + pub fn sgn0(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + self.0.sgn0(cs) + } } #[cfg(test)] mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::Fp; + use bls12_381::hash_to_curve::Sgn0; + use halo2curves::bn256::Fq as Fp; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -307,8 +334,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["277"]); - expect_eq(cs.num_constraints(), &expect!["262"]); + expect_eq(cs.scalar_aux().len(), &expect!["244"]); + expect_eq(cs.num_constraints(), &expect!["226"]); } #[test] @@ -329,8 +356,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["277"]); - expect_eq(cs.num_constraints(), &expect!["262"]); + expect_eq(cs.scalar_aux().len(), &expect!["244"]); + expect_eq(cs.num_constraints(), &expect!["226"]); } #[test] @@ -346,13 +373,13 @@ mod tests { let c_alloc = FpElement::alloc_element(&mut cs.namespace(|| "alloc c"), &c).unwrap(); let res_alloc = a_alloc.mul(&mut cs.namespace(|| "a*b"), &b_alloc).unwrap(); FpElement::assert_is_equal(&mut cs.namespace(|| "a*b = c"), &res_alloc, &c_alloc).unwrap(); + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["651"]); + expect_eq(cs.num_constraints(), &expect!["633"]); if !cs.is_satisfied() { eprintln!("{:?}", cs.which_is_unsatisfied()) } - assert!(cs.is_satisfied()); - expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["681"]); - expect_eq(cs.num_constraints(), &expect!["666"]); } #[test] @@ -381,8 +408,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["271"]); - expect_eq(cs.num_constraints(), &expect!["262"]); + expect_eq(cs.scalar_aux().len(), &expect!["237"]); + expect_eq(cs.num_constraints(), &expect!["226"]); } #[test] @@ -401,8 +428,33 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["271"]); - expect_eq(cs.num_constraints(), &expect!["262"]); + expect_eq(cs.scalar_aux().len(), &expect!["237"]); + expect_eq(cs.num_constraints(), &expect!["226"]); + } + + #[test] + fn test_random_sgn0() { + let mut rng = rand::thread_rng(); + let a = BlsFp::random(&mut rng); + let c: bool = a.sgn0().into(); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = FpElement::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = AllocatedBit::alloc(&mut cs.namespace(|| "alloc c"), Some(c)).unwrap(); + let res_alloc = a_alloc.sgn0(&mut cs.namespace(|| "a.sgn0()")).unwrap(); + Boolean::enforce_equal( + &mut cs.namespace(|| "a.sgn0() = c"), + &res_alloc, + &Boolean::from(c_alloc), + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["10"]); + expect_eq(cs.num_constraints(), &expect!["4"]); } #[test] @@ -415,9 +467,8 @@ mod tests { let a_alloc = FpElement::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); let b_alloc = FpElement::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); let res_alloc = a_alloc.sub(&mut cs.namespace(|| "a-a"), &a_alloc).unwrap(); - let z_alloc = - FpElement::alloc_element(&mut cs.namespace(|| "alloc zero"), &BlsFp::zero()).unwrap(); - FpElement::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &z_alloc).unwrap(); + let zero = FpElement::zero(); + FpElement::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &zero).unwrap(); let zbit_alloc = res_alloc .alloc_is_zero(&mut cs.namespace(|| "z <- a-a ?= 0")) .unwrap(); @@ -439,7 +490,7 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1199"]); - expect_eq(cs.num_constraints(), &expect!["1196"]); + expect_eq(cs.scalar_aux().len(), &expect!["1091"]); + expect_eq(cs.num_constraints(), &expect!["1093"]); } } diff --git a/crates/bls12381/src/fields/fp12.rs b/crates/bls12381/src/fields/fp12.rs index 242afea..ee409b5 100644 --- a/crates/bls12381/src/fields/fp12.rs +++ b/crates/bls12381/src/fields/fp12.rs @@ -1,7 +1,6 @@ use bellpepper_core::boolean::{AllocatedBit, Boolean}; use bellpepper_core::{ConstraintSystem, SynthesisError}; use bls12_381::fp12::Fp12 as BlsFp12; -use bls12_381::fp2::Fp2 as BlsFp2; use bls12_381::fp6::Fp6 as BlsFp6; use ff::PrimeFieldBits; @@ -185,8 +184,7 @@ impl Fp12Element { b2: z.c1.b1.clone(), }; - let one = Fp2Element::::one(); - let d = c1.add(&mut cs.namespace(|| "d <- c1 + 1"), &one)?; + let d = c1.add(&mut cs.namespace(|| "d <- c1 + 1"), &Fp2Element::one())?; let rc1 = z.c1.add(&mut cs.namespace(|| "rc1 <- z.c1 + z.c0"), &z.c0)?; @@ -245,9 +243,6 @@ impl Fp12Element { let zc0b0 = Fp2Element::::non_residue(); let zc0b0 = zc0b0.add(&mut cs.namespace(|| "zc0b0 <- non_residue() + x0"), &x0)?; - // TODO: double check if we need to actually alloc here or if this could be a constant - let c1b0 = - Fp2Element::::alloc_element(&mut cs.namespace(|| "c1b0 <- 0"), &BlsFp2::zero())?; Ok(Self { c0: Fp6Element { b0: zc0b0, @@ -255,7 +250,7 @@ impl Fp12Element { b2: x1, }, c1: Fp6Element { - b0: c1b0, + b0: Fp2Element::zero(), b1: x04, b2: x14, }, @@ -313,8 +308,14 @@ impl Fp12Element { where CS: ConstraintSystem, { - let x = self; let mut cs = cs.namespace(|| "Fp12::square(x)"); + + // This explicit reduction is single-handedly responsible for saving + // millions of constraints during a pairing operation. This function is + // repeatedly called inside `miller_loop_lines`, and is responsible for + // a considerable chunk of the constraints + let x = self.reduce(&mut cs.namespace(|| "x <- x.reduce()"))?; + let c0 = x.c0.sub(&mut cs.namespace(|| "c0 <- x.c0 - x.c1"), &x.c1)?; let c3 = x.c1.mul_by_nonresidue(&mut cs.namespace(|| "c3 <- x.c1.mul_by_nonresidue()"))?; @@ -346,11 +347,6 @@ impl Fp12Element { // x*inv = 1 let prod = inv_alloc.mul(&mut cs.namespace(|| "x*inv"), self)?; - // TODO: An alternative implementation would be calling - // `assert_equality_to_constant(1)`, however that seems to only work if - // we `reduce` the value first, and then the constraint count of just - // calling `assert_is_equal` ends up being lower instead. - Self::assert_is_equal(&mut cs.namespace(|| "x*inv = 1 mod P"), &prod, &Self::one())?; Ok(inv_alloc) @@ -410,7 +406,7 @@ impl Fp12Element { mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::Fp; + use halo2curves::bn256::Fq as Fp; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -436,8 +432,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["3324"]); - expect_eq(cs.num_constraints(), &expect!["3144"]); + expect_eq(cs.scalar_aux().len(), &expect!["2928"]); + expect_eq(cs.num_constraints(), &expect!["2712"]); } #[test] @@ -459,8 +455,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["3324"]); - expect_eq(cs.num_constraints(), &expect!["3144"]); + expect_eq(cs.scalar_aux().len(), &expect!["2928"]); + expect_eq(cs.num_constraints(), &expect!["2712"]); } #[test] @@ -484,8 +480,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8895"]); - expect_eq(cs.num_constraints(), &expect!["8715"]); + expect_eq(cs.scalar_aux().len(), &expect!["8619"]); + expect_eq(cs.num_constraints(), &expect!["8403"]); } #[test] @@ -504,8 +500,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8742"]); - expect_eq(cs.num_constraints(), &expect!["8634"]); + expect_eq(cs.scalar_aux().len(), &expect!["8418"]); + expect_eq(cs.num_constraints(), &expect!["8286"]); } #[test] @@ -532,8 +528,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["12075"]); - expect_eq(cs.num_constraints(), &expect!["11859"]); + expect_eq(cs.scalar_aux().len(), &expect!["11379"]); + expect_eq(cs.num_constraints(), &expect!["11115"]); } #[test] @@ -577,8 +573,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8562"]); - expect_eq(cs.num_constraints(), &expect!["8430"]); + expect_eq(cs.scalar_aux().len(), &expect!["8230"]); + expect_eq(cs.num_constraints(), &expect!["8070"]); } #[test] @@ -637,8 +633,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["7437"]); - expect_eq(cs.num_constraints(), &expect!["7341"]); + expect_eq(cs.scalar_aux().len(), &expect!["7053"]); + expect_eq(cs.num_constraints(), &expect!["6949"]); } #[test] @@ -691,8 +687,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["8844"]); - expect_eq(cs.num_constraints(), &expect!["8676"]); + expect_eq(cs.scalar_aux().len(), &expect!["8560"]); + expect_eq(cs.num_constraints(), &expect!["8358"]); } #[test] @@ -718,8 +714,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["3252"]); - expect_eq(cs.num_constraints(), &expect!["3144"]); + expect_eq(cs.scalar_aux().len(), &expect!["2844"]); + expect_eq(cs.num_constraints(), &expect!["2712"]); } #[test] @@ -739,8 +735,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["12003"]); - expect_eq(cs.num_constraints(), &expect!["11859"]); + expect_eq(cs.scalar_aux().len(), &expect!["11295"]); + expect_eq(cs.num_constraints(), &expect!["11115"]); } #[test] @@ -753,11 +749,8 @@ mod tests { let a_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); let b_alloc = Fp12Element::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); let res_alloc = a_alloc.sub(&mut cs.namespace(|| "a-a"), &a_alloc).unwrap(); - let z_alloc = - Fp12Element::alloc_element(&mut cs.namespace(|| "alloc zero"), &BlsFp12::zero()) - .unwrap(); - Fp12Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &z_alloc) - .unwrap(); + let zero = Fp12Element::zero(); + Fp12Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &zero).unwrap(); let zbit_alloc = res_alloc .alloc_is_zero(&mut cs.namespace(|| "z <- a-a ?= 0")) .unwrap(); @@ -779,7 +772,7 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["14399"]); - expect_eq(cs.num_constraints(), &expect!["14363"]); + expect_eq(cs.scalar_aux().len(), &expect!["13103"]); + expect_eq(cs.num_constraints(), &expect!["13127"]); } } diff --git a/crates/bls12381/src/fields/fp2.rs b/crates/bls12381/src/fields/fp2.rs index 2f2ed58..9f7bf51 100644 --- a/crates/bls12381/src/fields/fp2.rs +++ b/crates/bls12381/src/fields/fp2.rs @@ -4,8 +4,9 @@ use bls12_381::fp::Fp as BlsFp; use bls12_381::fp2::Fp2 as BlsFp2; use ff::PrimeFieldBits; use num_bigint::BigInt; +use num_traits::ToBytes; -use super::fp::FpElement; +use super::fp::{fp_from_dec, FpElement}; #[derive(Clone)] pub struct Fp2Element { @@ -35,10 +36,36 @@ where } } +/// Returns a^e in Fp2. Internal helper function for opt_simple_swu2 +pub(crate) fn fp2_pow_vartime(a: &BlsFp2, e: &BigInt) -> BlsFp2 { + let e_bytes = e.to_le_bytes(); + let mut res = BlsFp2::one(); + for e in e_bytes.iter().rev() { + for i in (0..8).rev() { + res = res.square(); + + if ((*e >> i) & 1) == 1 { + res *= a; + } + } + } + res +} + +pub(crate) fn fp2_from_dec(c0: &str, c1: &str) -> Option { + let c0 = fp_from_dec(c0); + let c1 = fp_from_dec(c1); + if let (Some(c0), Some(c1)) = (c0, c1) { + Some(BlsFp2 { c0, c1 }) + } else { + None + } +} + impl Fp2Element { - pub fn from_dec(val: (&str, &str)) -> Option { - let c0 = FpElement::from_dec(val.0); - let c1 = FpElement::from_dec(val.1); + pub fn from_dec(c0: &str, c1: &str) -> Option { + let c0 = FpElement::from_dec(c0); + let c1 = FpElement::from_dec(c1); if let (Some(c0), Some(c1)) = (c0, c1) { Some(Self { a0: c0, a1: c1 }) } else { @@ -189,7 +216,7 @@ impl Fp2Element { where CS: ConstraintSystem, { - let elm = Self::from_dec(("3850754370037169011952147076051364057158807420970682438676050522613628423219637725072182697113062777891589506424760", "151655185184498381465642749684540099398075398968325446656007613510403227271200139370504932015952886146304766135027")).unwrap(); + let elm = Self::from_dec("3850754370037169011952147076051364057158807420970682438676050522613628423219637725072182697113062777891589506424760", "151655185184498381465642749684540099398075398968325446656007613510403227271200139370504932015952886146304766135027").unwrap(); self.mul( &mut cs.namespace(|| "Fp2::mul_by_nonresidue_1pow5(x)"), &elm, @@ -213,7 +240,7 @@ impl Fp2Element { where CS: ConstraintSystem, { - let elm = Self::from_dec(("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257", "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257")).unwrap(); + let elm = Self::from_dec("1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257", "1028732146235106349975324479215795277384839936929757896155643118032610843298655225875571310552543014690878354869257").unwrap(); self.mul( &mut cs.namespace(|| "Fp2::mul_by_nonresidue_1pow3(x)"), &elm, @@ -237,7 +264,7 @@ impl Fp2Element { where CS: ConstraintSystem, { - let elm = Self::from_dec(("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230", "3125332594171059424908108096204648978570118281977575435832422631601824034463382777937621250592425535493320683825557")).unwrap(); + let elm = Self::from_dec("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230", "3125332594171059424908108096204648978570118281977575435832422631601824034463382777937621250592425535493320683825557").unwrap(); self.mul( &mut cs.namespace(|| "Fp2::mul_by_nonresidue_1pow5(x)"), &elm, @@ -388,11 +415,6 @@ impl Fp2Element { // x*inv = 1 let prod = inv_alloc.mul(&mut cs.namespace(|| "x*inv"), self)?; - // TODO: An alternative implementation would be calling - // `assert_equality_to_constant(1)`, however that seems to only work if - // we `reduce` the value first, and then the constraint count of just - // calling `assert_is_equal` ends up being lower instead. - Self::assert_is_equal(&mut cs.namespace(|| "x*inv = 1 mod P"), &prod, &Self::one())?; Ok(inv_alloc) @@ -446,15 +468,36 @@ impl Fp2Element { )?; Ok(Self { a0, a1 }) } + + pub fn sgn0(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + // sgn[0] || ( (in0 == 0 && sgn[1]) ) + let sgn_0 = self.a0.sgn0(&mut cs.namespace(|| "self.a0.sgn0()"))?; + let sgn_1 = self.a1.sgn0(&mut cs.namespace(|| "self.a1.sgn0()"))?; + let is_a0_zero = self + .a0 + .alloc_is_zero(&mut cs.namespace(|| "self.a0.alloc_is_zero()"))?; + + let tmp = Boolean::and( + &mut cs.namespace(|| "tmp <- and(is_a0_zero, sgn_1)"), + &Boolean::from(is_a0_zero), + &sgn_1, + )?; + let out = Boolean::or(&mut cs.namespace(|| "out <- or(sgn_0, tmp)"), &sgn_0, &tmp)?; + + Ok(out) + } } #[cfg(test)] mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::Fp; + use halo2curves::bn256::Fq as Fp; - use bls12_381::fp::Fp as BlsFp; + use bls12_381::{fp::Fp as BlsFp, hash_to_curve::Sgn0}; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -479,8 +522,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["554"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["488"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -501,8 +544,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["554"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["488"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -521,8 +564,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -543,8 +586,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1385"]); - expect_eq(cs.num_constraints(), &expect!["1355"]); + expect_eq(cs.scalar_aux().len(), &expect!["1327"]); + expect_eq(cs.num_constraints(), &expect!["1291"]); } #[test] @@ -566,8 +609,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -586,8 +629,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1365"]); - expect_eq(cs.num_constraints(), &expect!["1347"]); + expect_eq(cs.scalar_aux().len(), &expect!["1303"]); + expect_eq(cs.num_constraints(), &expect!["1281"]); } #[test] @@ -614,8 +657,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1915"]); - expect_eq(cs.num_constraints(), &expect!["1879"]); + expect_eq(cs.scalar_aux().len(), &expect!["1787"]); + expect_eq(cs.num_constraints(), &expect!["1743"]); } #[test] @@ -638,8 +681,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1328"]); - expect_eq(cs.num_constraints(), &expect!["1310"]); + expect_eq(cs.scalar_aux().len(), &expect!["1262"]); + expect_eq(cs.num_constraints(), &expect!["1240"]); } #[test] @@ -668,8 +711,62 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); + } + + #[test] + fn test_random_sgn0() { + let mut rng = rand::thread_rng(); + let a = BlsFp2::random(&mut rng); + let c: bool = a.sgn0().into(); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = AllocatedBit::alloc(&mut cs.namespace(|| "alloc c"), Some(c)).unwrap(); + let res_alloc = a_alloc.sgn0(&mut cs.namespace(|| "a.sgn0()")).unwrap(); + Boolean::enforce_equal( + &mut cs.namespace(|| "a.sgn0() = c"), + &res_alloc, + &Boolean::from(c_alloc), + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["41"]); + expect_eq(cs.num_constraints(), &expect!["35"]); + } + + #[test] + fn test_random_sgn0_zero_a0() { + let mut rng = rand::thread_rng(); + let a = BlsFp::random(&mut rng); + let a = BlsFp2 { + c0: BlsFp::zero(), + c1: a, + }; + let c: bool = a.sgn0().into(); + + let mut cs = TestConstraintSystem::::new(); + let a_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); + let c_alloc = AllocatedBit::alloc(&mut cs.namespace(|| "alloc c"), Some(c)).unwrap(); + let res_alloc = a_alloc.sgn0(&mut cs.namespace(|| "a.sgn0()")).unwrap(); + Boolean::enforce_equal( + &mut cs.namespace(|| "a.sgn0() = c"), + &res_alloc, + &Boolean::from(c_alloc), + ) + .unwrap(); + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + expect_eq(cs.num_inputs(), &expect!["1"]); + expect_eq(cs.scalar_aux().len(), &expect!["41"]); + expect_eq(cs.num_constraints(), &expect!["35"]); } #[test] @@ -688,8 +785,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -709,8 +806,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["542"]); - expect_eq(cs.num_constraints(), &expect!["524"]); + expect_eq(cs.scalar_aux().len(), &expect!["474"]); + expect_eq(cs.num_constraints(), &expect!["452"]); } #[test] @@ -730,8 +827,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1903"]); - expect_eq(cs.num_constraints(), &expect!["1879"]); + expect_eq(cs.scalar_aux().len(), &expect!["1773"]); + expect_eq(cs.num_constraints(), &expect!["1743"]); } #[test] @@ -744,9 +841,8 @@ mod tests { let a_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); let b_alloc = Fp2Element::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); let res_alloc = a_alloc.sub(&mut cs.namespace(|| "a-a"), &a_alloc).unwrap(); - let z_alloc = - Fp2Element::alloc_element(&mut cs.namespace(|| "alloc zero"), &BlsFp2::zero()).unwrap(); - Fp2Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &z_alloc).unwrap(); + let zero = Fp2Element::zero(); + Fp2Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &zero).unwrap(); let zbit_alloc = res_alloc .alloc_is_zero(&mut cs.namespace(|| "z <- a-a ?= 0")) .unwrap(); @@ -768,7 +864,7 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["2399"]); - expect_eq(cs.num_constraints(), &expect!["2393"]); + expect_eq(cs.scalar_aux().len(), &expect!["2183"]); + expect_eq(cs.num_constraints(), &expect!["2187"]); } } diff --git a/crates/bls12381/src/fields/fp6.rs b/crates/bls12381/src/fields/fp6.rs index e902a3c..c6143ff 100644 --- a/crates/bls12381/src/fields/fp6.rs +++ b/crates/bls12381/src/fields/fp6.rs @@ -378,11 +378,6 @@ impl Fp6Element { // x*inv = 1 let prod = inv_alloc.mul(&mut cs.namespace(|| "x*inv"), self)?; - // TODO: An alternative implementation would be calling - // `assert_equality_to_constant(1)`, however that seems to only work if - // we `reduce` the value first, and then the constraint count of just - // calling `assert_is_equal` ends up being lower instead. - Self::assert_is_equal(&mut cs.namespace(|| "x*inv = 1 mod P"), &prod, &Self::one())?; Ok(inv_alloc) @@ -448,7 +443,7 @@ impl Fp6Element { mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::Fp; + use halo2curves::bn256::Fq as Fp; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -473,8 +468,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1662"]); - expect_eq(cs.num_constraints(), &expect!["1572"]); + expect_eq(cs.scalar_aux().len(), &expect!["1464"]); + expect_eq(cs.num_constraints(), &expect!["1356"]); } #[test] @@ -495,8 +490,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1662"]); - expect_eq(cs.num_constraints(), &expect!["1572"]); + expect_eq(cs.scalar_aux().len(), &expect!["1464"]); + expect_eq(cs.num_constraints(), &expect!["1356"]); } #[test] @@ -515,8 +510,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1626"]); - expect_eq(cs.num_constraints(), &expect!["1572"]); + expect_eq(cs.scalar_aux().len(), &expect!["1422"]); + expect_eq(cs.num_constraints(), &expect!["1356"]); } #[test] @@ -539,8 +534,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4317"]); - expect_eq(cs.num_constraints(), &expect!["4227"]); + expect_eq(cs.scalar_aux().len(), &expect!["4161"]); + expect_eq(cs.num_constraints(), &expect!["4053"]); } #[test] @@ -562,8 +557,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1626"]); - expect_eq(cs.num_constraints(), &expect!["1572"]); + expect_eq(cs.scalar_aux().len(), &expect!["1422"]); + expect_eq(cs.num_constraints(), &expect!["1356"]); } #[test] @@ -582,8 +577,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4248"]); - expect_eq(cs.num_constraints(), &expect!["4194"]); + expect_eq(cs.scalar_aux().len(), &expect!["4074"]); + expect_eq(cs.num_constraints(), &expect!["4008"]); } #[test] @@ -610,8 +605,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5907"]); - expect_eq(cs.num_constraints(), &expect!["5799"]); + expect_eq(cs.scalar_aux().len(), &expect!["5541"]); + expect_eq(cs.num_constraints(), &expect!["5409"]); } #[test] @@ -634,8 +629,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4131"]); - expect_eq(cs.num_constraints(), &expect!["4065"]); + expect_eq(cs.scalar_aux().len(), &expect!["3953"]); + expect_eq(cs.num_constraints(), &expect!["3873"]); } #[test] @@ -663,8 +658,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4155"]); - expect_eq(cs.num_constraints(), &expect!["4089"]); + expect_eq(cs.scalar_aux().len(), &expect!["3977"]); + expect_eq(cs.num_constraints(), &expect!["3897"]); } #[test] @@ -694,8 +689,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4266"]); - expect_eq(cs.num_constraints(), &expect!["4188"]); + expect_eq(cs.scalar_aux().len(), &expect!["4102"]); + expect_eq(cs.num_constraints(), &expect!["4008"]); } #[test] @@ -720,8 +715,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["4266"]); - expect_eq(cs.num_constraints(), &expect!["4188"]); + expect_eq(cs.scalar_aux().len(), &expect!["4102"]); + expect_eq(cs.num_constraints(), &expect!["4008"]); } #[test] @@ -740,8 +735,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["1626"]); - expect_eq(cs.num_constraints(), &expect!["1572"]); + expect_eq(cs.scalar_aux().len(), &expect!["1422"]); + expect_eq(cs.num_constraints(), &expect!["1356"]); } #[test] @@ -761,8 +756,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5871"]); - expect_eq(cs.num_constraints(), &expect!["5799"]); + expect_eq(cs.scalar_aux().len(), &expect!["5499"]); + expect_eq(cs.num_constraints(), &expect!["5409"]); } #[test] @@ -775,9 +770,8 @@ mod tests { let a_alloc = Fp6Element::alloc_element(&mut cs.namespace(|| "alloc a"), &a).unwrap(); let b_alloc = Fp6Element::alloc_element(&mut cs.namespace(|| "alloc b"), &b).unwrap(); let res_alloc = a_alloc.sub(&mut cs.namespace(|| "a-a"), &a_alloc).unwrap(); - let z_alloc = - Fp6Element::alloc_element(&mut cs.namespace(|| "alloc zero"), &BlsFp6::zero()).unwrap(); - Fp6Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &z_alloc).unwrap(); + let zero = Fp6Element::zero(); + Fp6Element::assert_is_equal(&mut cs.namespace(|| "a-a = 0"), &res_alloc, &zero).unwrap(); let zbit_alloc = res_alloc .alloc_is_zero(&mut cs.namespace(|| "z <- a-a ?= 0")) .unwrap(); @@ -799,7 +793,7 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["7199"]); - expect_eq(cs.num_constraints(), &expect!["7181"]); + expect_eq(cs.scalar_aux().len(), &expect!["6551"]); + expect_eq(cs.num_constraints(), &expect!["6563"]); } } diff --git a/crates/bls12381/src/fields/torus.rs b/crates/bls12381/src/fields/torus.rs index e0076a3..48cc4e9 100644 --- a/crates/bls12381/src/fields/torus.rs +++ b/crates/bls12381/src/fields/torus.rs @@ -58,13 +58,11 @@ impl Torus { where CS: ConstraintSystem, { - // NOTE: if we don't alloc_element and try to just use Fp6Element::one() instead, this fails - // (presumably because one() returns a non-allocated constant) - let alloc_one = Fp6Element::alloc_element(&mut cs.namespace(|| "alloc 1"), &BlsFp6::one())?; - let neg_one = alloc_one.neg(&mut cs.namespace(|| "-1"))?; + let one = Fp6Element::one(); + let neg_one = one.neg(&mut cs.namespace(|| "-1"))?; let n = Fp12Element { c0: self.0.clone(), - c1: alloc_one, + c1: one, }; let d = Fp12Element { c0: self.0.clone(), @@ -174,7 +172,7 @@ impl Torus { let t2 = t2.mul_by_nonresidue_1pow4(&mut cs.namespace(|| "t2 <- t2.mul_by_nonresidue_1pow4()"))?; - let v0 = Fp2Element::::from_dec(("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230", "877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230")).unwrap(); + let v0 = Fp2Element::::from_dec("877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230", "877076961050607968509681729531255177986764537961432449499635504522207616027455086505066378536590128544573588734230").unwrap(); let res = Fp6Element { b0: t0, b1: t1, @@ -302,7 +300,7 @@ impl Torus { mod tests { use super::*; use bellpepper_core::test_cs::TestConstraintSystem; - use pasta_curves::Fp; + use halo2curves::bn256::Fq as Fp; use expect_test::{expect, Expect}; fn expect_eq(computed: usize, expected: &Expect) { @@ -330,8 +328,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["5907"]); - expect_eq(cs.num_constraints(), &expect!["5799"]); + expect_eq(cs.scalar_aux().len(), &expect!["5541"]); + expect_eq(cs.num_constraints(), &expect!["5409"]); } #[test] @@ -358,8 +356,8 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["12075"]); - expect_eq(cs.num_constraints(), &expect!["11931"]); + expect_eq(cs.scalar_aux().len(), &expect!["11019"]); + expect_eq(cs.num_constraints(), &expect!["10881"]); } #[test] @@ -395,7 +393,7 @@ mod tests { } assert!(cs.is_satisfied()); expect_eq(cs.num_inputs(), &expect!["1"]); - expect_eq(cs.scalar_aux().len(), &expect!["14709"]); - expect_eq(cs.num_constraints(), &expect!["14493"]); + expect_eq(cs.scalar_aux().len(), &expect!["14055"]); + expect_eq(cs.num_constraints(), &expect!["13791"]); } } diff --git a/crates/emulated/src/field_element.rs b/crates/emulated/src/field_element.rs index 8d5cdf9..119e945 100644 --- a/crates/emulated/src/field_element.rs +++ b/crates/emulated/src/field_element.rs @@ -8,7 +8,7 @@ use bellpepper_core::{ }; use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError}; use ff::PrimeFieldBits; -use num_bigint::{BigInt, BigUint}; +use num_bigint::{BigInt, BigUint, Sign}; use num_traits::{One, Signed, Zero}; use crate::util::*; @@ -245,7 +245,7 @@ where CS: ConstraintSystem, { if self.is_constant() { - // FIXME: it's not necessarily unsat, could do the comparison like the other cases and allocate a constant bit + eprintln!("alloc_is_zero not implemented for constants"); return Err(SynthesisError::Unsatisfiable); } @@ -647,6 +647,57 @@ where Ok(inputs[0].clone()) } } + + /// Implements the `sgn0` function from [RFC 9380](https://datatracker.ietf.org/doc/html/rfc9380#name-the-sgn0-function) + /// which returns `x mod 2` + pub fn sgn0(&self, cs: &mut CS) -> Result + where + CS: ConstraintSystem, + { + self.enforce_width_conditional(&mut cs.namespace(|| "ensure bitwidths in input"))?; + + let least_sig = match &self.limbs { + EmulatedLimbs::Allocated(limbs) => limbs[0].get_value().unwrap(), + EmulatedLimbs::Constant(limbs) => limbs[0], + }; + + let (out_val, div) = { + let val = BigInt::from_bytes_le(Sign::Plus, least_sig.to_repr().as_ref()); + let out = &val % 2u64; + let div = &val / 2u64; + assert_eq!(2u64 * &div + &out, val.clone(), "sanity check"); + if out == BigInt::from(0u64) { + (false, div) + } else if out == BigInt::from(1u64) { + (true, div) + } else { + unreachable!("Division by 2 always returns 0 or 1") + } + }; + + let out = match &self.limbs { + EmulatedLimbs::Allocated(limbs) => { + let out_bit = + AllocatedBit::alloc(&mut cs.namespace(|| "alloc sgn0 out"), Some(out_val))?; + let div = AllocatedNum::alloc(&mut cs.namespace(|| "alloc sgn0 div"), || { + Ok(bigint_to_scalar(&div)) + })?; + + // enforce that least significant limb is divisible by 2 + let two = F::ONE + F::ONE; + cs.enforce( + || "enforce sgn0 bit", + |lc| lc + CS::one(), + |lc| lc + (two, div.get_variable()) + out_bit.get_variable(), // 2 * div + out + |lc| lc + &limbs[0].lc(F::ONE), // least_sig + ); + Boolean::from(out_bit) + } + EmulatedLimbs::Constant(_) => Boolean::Constant(out_val), + }; + + Ok(out) + } } #[cfg(test)] @@ -1393,4 +1444,65 @@ mod tests { assert!(cs.is_satisfied()); assert_eq!(cs.num_constraints(), 0); } + + #[test] + fn test_sgn0() { + let mut cs = TestConstraintSystem::::new(); + let zero = EmulatedFieldElement::::zero() + .sgn0(&mut cs.namespace(|| "sgn0(0)")) + .unwrap(); + assert!(matches!(zero, Boolean::Constant(false))); + let one = EmulatedFieldElement::::one() + .sgn0(&mut cs.namespace(|| "sgn0(1)")) + .unwrap(); + assert!(matches!(one, Boolean::Constant(true))); + let neg_one = EmulatedFieldElement::::one() + .neg(&mut cs.namespace(|| "-1")) + .unwrap(); + let neg_one = neg_one.sgn0(&mut cs.namespace(|| "sgn0(-1)")).unwrap(); + assert!(matches!(neg_one, Boolean::Constant(false))); + let neg_zero = EmulatedFieldElement::::zero() + .neg(&mut cs.namespace(|| "-0")) + .unwrap(); + let neg_zero = neg_zero.sgn0(&mut cs.namespace(|| "sgn0(-0)")).unwrap(); + assert!(matches!(neg_zero, Boolean::Constant(false))); + + // p-1 / 2 + let p_m1_over2 = EmulatedFieldElement::::from( + &((Ed25519Fp::modulus() - BigInt::from(1)) / BigInt::from(2)), + ); + let p_p1_over2 = p_m1_over2 + .add( + &mut cs.namespace(|| "p-1_over2 + 1"), + &EmulatedFieldElement::::one(), + ) + .unwrap(); + let neg_p_p1_over2 = p_p1_over2 + .neg(&mut cs.namespace(|| "-(p-1_over2+1)")) + .unwrap(); + let p_m1_over2 = p_m1_over2 + .sgn0(&mut cs.namespace(|| "sgn0(p-1_over2)")) + .unwrap(); + assert!(matches!(p_m1_over2, Boolean::Constant(false))); + let p_p1_over2 = p_p1_over2 + .sgn0(&mut cs.namespace(|| "sgn0(p-1_over2+1)")) + .unwrap(); + assert!(matches!(p_p1_over2, Boolean::Constant(true))); + let neg_p_p1_over2 = neg_p_p1_over2 + .sgn0(&mut cs.namespace(|| "sgn0(-(p-1_over2+1))")) + .unwrap(); + Boolean::enforce_equal( + &mut cs.namespace(|| "-(p-1_over2+1) == p_m1_over2"), + &neg_p_p1_over2, + &p_m1_over2, + ) + .unwrap(); + + if !cs.is_satisfied() { + eprintln!("{:?}", cs.which_is_unsatisfied()) + } + assert!(cs.is_satisfied()); + assert_eq!(cs.scalar_aux().len(), 0); + assert_eq!(cs.num_constraints(), 0); + } }