diff --git a/src/fp12.rs b/src/fp12.rs index 110dc234..10f2105c 100644 --- a/src/fp12.rs +++ b/src/fp12.rs @@ -162,6 +162,44 @@ impl Fp12 { } } + #[inline] + #[cfg(target_os = "zkvm")] + pub fn mul_inp(&mut self, other: &Fp12) { + let aa = self.c0 * other.c0; + let bb = self.c1 * other.c1; + self.c1.add_inp(&self.c0); + let mut o = other.c0; + o.add_inp(&other.c1); + self.c1 *= o; + self.c1.sub_inp(&aa); + self.c1.sub_inp(&bb); + self.c0 = bb.mul_by_nonresidue_owned(); + self.c0.add_inp(&aa); + } + + #[inline] + fn mul(&self, other: &Fp12) -> Self { + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + let mut out = self.clone(); + out.mul_inp(other); + out + } else { + let aa = self.c0 * other.c0; + let bb = self.c1 * other.c1; + let c1 = self.c1 + self.c0; + let o = other.c0 + other.c1; + let c1 = c1 * o; + let c1 = c1 - aa; + let c1 = c1 - bb; + let c0 = bb.mul_by_nonresidue(); + let c0 = c0 + aa; + + Fp12 { c0, c1 } + } + } + } + /// Raises this element to p. #[inline(always)] pub fn frobenius_map(&self) -> Self { @@ -192,6 +230,40 @@ impl Fp12 { Fp12 { c0, c1 } } + /// Raises this element to p. + #[inline] + #[cfg(target_os = "zkvm")] + pub fn frobenius_map_inp(&mut self) { + self.c0.frobenius_map_inp(); + self.c1.frobenius_map_inp(); + + const C1_MUL: Fp6 = Fp6 { + c0: Fp2 { + c0: Fp::from_raw_unchecked([ + 0x0708_9552_b319_d465, + 0xc669_5f92_b50a_8313, + 0x97e8_3ccc_d117_228f, + 0xa35b_aeca_b2dc_29ee, + 0x1ce3_93ea_5daa_ce4d, + 0x08f2_220f_b0fb_66eb, + ]), + c1: Fp::from_raw_unchecked([ + 0xb2f6_6aad_4ce5_d646, + 0x5842_a06b_fc49_7cec, + 0xcf48_95d4_2599_d394, + 0xc11b_9cba_40a8_e8d0, + 0x2e38_13cb_e5a0_de89, + 0x110e_efda_8884_7faf, + ]), + }, + c1: Fp2::zero(), + c2: Fp2::zero(), + }; + + // c1 = c1 * (u + 1)^((p - 1) / 6) + self.c1 *= C1_MUL; + } + #[inline] #[cfg(target_os = "zkvm")] pub fn square(&self) -> Self { @@ -237,17 +309,7 @@ impl<'a, 'b> Mul<&'b Fp12> for &'a Fp12 { #[inline] fn mul(self, other: &'b Fp12) -> Self::Output { - let aa = self.c0 * other.c0; - let bb = self.c1 * other.c1; - let o = other.c0 + other.c1; - let c1 = self.c1 + self.c0; - let c1 = c1 * o; - let c1 = c1 - aa; - let c1 = c1 - bb; - let c0 = bb.mul_by_nonresidue(); - let c0 = c0 + aa; - - Fp12 { c0, c1 } + self.mul(other) } } diff --git a/src/fp2.rs b/src/fp2.rs index 82f81e79..5d085b09 100644 --- a/src/fp2.rs +++ b/src/fp2.rs @@ -153,6 +153,14 @@ impl Fp2 { self.conjugate() } + /// Raises this element to p. + #[inline] + pub fn frobenius_map_inp(&mut self) { + // This is always just a conjugation. If you're curious why, here's + // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/ + self.conjugate_inp() + } + #[inline(always)] pub fn conjugate(&self) -> Self { Fp2 { @@ -161,6 +169,11 @@ impl Fp2 { } } + #[inline] + pub fn conjugate_inp(&mut self) { + self.c1 = -self.c1; + } + #[inline] #[cfg(target_os = "zkvm")] pub fn mul_by_nonresidue_inp(&mut self) { diff --git a/src/fp6.rs b/src/fp6.rs index 0b91d706..18eb0a46 100644 --- a/src/fp6.rs +++ b/src/fp6.rs @@ -165,6 +165,23 @@ impl Fp6 { } } + /// Multiply by quadratic nonresidue v. + #[cfg(target_os = "zkvm")] + pub fn mul_by_nonresidue_owned(self) -> Self { + // Given a + bv + cv^2, this produces + // av + bv^2 + cv^3 + // but because v^3 = u + 1, we have + // c(u + 1) + av + v^2 + let Fp6 { c0, c1, mut c2 } = self; + c2.mul_by_nonresidue_inp(); + + Fp6 { + c0: c2, + c1: c0, + c2: c1, + } + } + /// Raises this element to p. #[inline(always)] pub fn frobenius_map(&self) -> Self { @@ -203,6 +220,45 @@ impl Fp6 { Fp6 { c0, c1, c2 } } + /// Raises this element to p. + #[inline] + #[cfg(target_os = "zkvm")] + pub fn frobenius_map_inp(&mut self) { + self.c0.frobenius_map_inp(); + self.c1.frobenius_map_inp(); + self.c2.frobenius_map_inp(); + + const C1_MUL: Fp2 = Fp2 { + c0: Fp::zero(), + c1: Fp::from_raw_unchecked([ + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741, + ]), + }; + + const C2_MUL: Fp2 = Fp2 { + c0: Fp::from_raw_unchecked([ + 0x890d_c9e4_8675_45c3, + 0x2af3_2253_3285_a5d5, + 0x5088_0866_309b_7e2c, + 0xa20d_1b8c_7e88_1024, + 0x14e4_f04f_e2db_9068, + 0x14e5_6d3f_1564_853a, + ]), + c1: Fp::zero(), + }; + + // c1 = c1 * (u + 1)^((p - 1) / 3) + self.c1.mul_inp(&C1_MUL); + + // c2 = c2 * (u + 1)^((2p - 2) / 3) + self.c2.mul_inp(&C2_MUL); + } + #[inline(always)] pub fn is_zero(&self) -> Choice { self.c0.is_zero() & self.c1.is_zero() & self.c2.is_zero() @@ -250,10 +306,23 @@ impl Fp6 { // Each of these is a "sum of products", which we can compute efficiently. let a = self; - let b10_p_b11 = b.c1.c0 + b.c1.c1; - let b10_m_b11 = b.c1.c0 - b.c1.c1; - let b20_p_b21 = b.c2.c0 + b.c2.c1; - let b20_m_b21 = b.c2.c0 - b.c2.c1; + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + let mut b10_p_b11 = b.c1.c0; + b10_p_b11.add_inp(&b.c1.c1); + let mut b10_m_b11 = b.c1.c0; + b10_m_b11.sub_inp(&b.c1.c1); + let mut b20_p_b21 = b.c2.c0; + b20_p_b21.add_inp(&b.c2.c1); + let mut b20_m_b21 = b.c2.c0; + b20_m_b21.sub_inp(&b.c2.c1) + } else { + let b10_p_b11 = b.c1.c0 + b.c1.c1; + let b10_m_b11 = b.c1.c0 - b.c1.c1; + let b20_p_b21 = b.c2.c0 + b.c2.c1; + let b20_m_b21 = b.c2.c0 - b.c2.c1; + } + } Fp6 { c0: Fp2 { diff --git a/src/pairings.rs b/src/pairings.rs index 058abfc1..225fa7f7 100644 --- a/src/pairings.rs +++ b/src/pairings.rs @@ -48,55 +48,128 @@ impl MillerLoopResult { pub fn final_exponentiation(&self) -> Gt { #[must_use] fn fp4_square(a: Fp2, b: Fp2) -> (Fp2, Fp2) { - let t0 = a.square(); - let t1 = b.square(); - let mut t2 = t1.mul_by_nonresidue(); - let c0 = t2 + t0; - t2 = a + b; - t2 = t2.square(); - t2 -= t0; - let c1 = t2 - t1; - - (c0, c1) + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + // c0 = b.square().mul_by_nonresidue() + a.square() + // c1 = (a + b).square() - a.square() - b.square() + let mut t0 = a; + t0.square_inp(); + let mut c0 = b; + c0.square_inp(); + let mut c1 = a; + c1.add_inp(&b); + c1.square_inp(); + c1.sub_inp(&t0); + c1.sub_inp(&c0); + c0.mul_by_nonresidue_inp(); + c0.add_inp(&t0); + (c0, c1) + } else { + let t0 = a.square(); + let t1 = b.square(); + let mut t2 = t1.mul_by_nonresidue(); + let c0 = t2 + t0; + t2 = a + b; + t2 = t2.square(); + t2 -= t0; + let c1 = t2 - t1; + (c0, c1) + } + } } // Adaptation of Algorithm 5.5.4, Guide to Pairing-Based Cryptography // Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions // https://eprint.iacr.org/2009/565.pdf #[must_use] fn cyclotomic_square(f: Fp12) -> Fp12 { - let mut z0 = f.c0.c0; - let mut z4 = f.c0.c1; - let mut z3 = f.c0.c2; - let mut z2 = f.c1.c0; - let mut z1 = f.c1.c1; - let mut z5 = f.c1.c2; + let Fp12 { + c0: + Fp6 { + c0: mut z0, + c1: mut z4, + c2: mut z3, + }, + c1: + Fp6 { + c0: mut z2, + c1: mut z1, + c2: mut z5, + }, + } = f; let (t0, t1) = fp4_square(z0, z1); // For A - z0 = t0 - z0; - z0 = z0 + z0 + t0; + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + z0 = -z0; + z0.add_inp(&t0); + z0.double_inp(); + z0.add_inp(&t0); + + z1.add_inp(&t1); + z1.double_inp(); + z1.add_inp(&t1); + } else { + z0 = t0 - z0; + z0 = z0 + z0 + t0; - z1 = t1 + z1; - z1 = z1 + z1 + t1; + z1 = t1 + z1; + z1 = z1 + z1 + t1; + } + } - let (mut t0, t1) = fp4_square(z2, z3); - let (t2, t3) = fp4_square(z4, z5); + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + let (t0, t1) = fp4_square(z2, z3); + let (t2, mut t3) = fp4_square(z4, z5); + } else { + let (mut t0, t1) = fp4_square(z2, z3); + let (t2, t3) = fp4_square(z4, z5); + } + } // For C - z4 = t0 - z4; - z4 = z4 + z4 + t0; + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + z4 = -z4; + z4.add_inp(&t0); + z4.double_inp(); + z4.add_inp(&t0); + + z5.add_inp(&t1); + z5.double_inp(); + z5.add_inp(&t1); + } else { + z4 = t0 - z4; + z4 = z4 + z4 + t0; - z5 = t1 + z5; - z5 = z5 + z5 + t1; + z5 = t1 + z5; + z5 = z5 + z5 + t1; + } + } // For B - t0 = t3.mul_by_nonresidue(); - z2 = t0 + z2; - z2 = z2 + z2 + t0; + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + t3.mul_by_nonresidue_inp(); + z2.add_inp(&t3); + z2.double_inp(); + z2.add_inp(&t3); + + z3 = -z3; + z3.add_inp(&t2); + z3.double_inp(); + z3.add_inp(&t2); + } else { + t0 = t3.mul_by_nonresidue(); + z2 = t0 + z2; + z2 = z2 + z2 + t0; - z3 = t2 - z3; - z3 = z3 + z3 + t2; + z3 = t2 - z3; + z3 = z3 + z3 + t2; + } + } Fp12 { c0: Fp6 { @@ -111,49 +184,155 @@ impl MillerLoopResult { }, } } + #[cfg(target_os = "zkvm")] + fn cyclotomic_square_inp(f: &mut Fp12) { + // z0: f.c0.c0 + // z1: f.c1.c1 + // z2: f.c1.c0 + // z3: f.c0.c2 + // z4: f.c0.c1 + // z5: f.c1.c2 + + let (t0, t1) = fp4_square(f.c0.c0, f.c1.c1); + + // For A + f.c0.c0 = -f.c0.c0; + f.c0.c0.add_inp(&t0); + f.c0.c0.double_inp(); + f.c0.c0.add_inp(&t0); + + f.c1.c1.add_inp(&t1); + f.c1.c1.double_inp(); + f.c1.c1.add_inp(&t1); + + let (t0, t1) = fp4_square(f.c1.c0, f.c0.c2); + let (t2, mut t3) = fp4_square(f.c0.c1, f.c1.c2); + + // For C + f.c0.c1 = -f.c0.c1; + f.c0.c1.add_inp(&t0); + f.c0.c1.double_inp(); + f.c0.c1.add_inp(&t0); + + f.c1.c2.add_inp(&t1); + f.c1.c2.double_inp(); + f.c1.c2.add_inp(&t1); + + // For B + t3.mul_by_nonresidue_inp(); + f.c1.c0.add_inp(&t3); + f.c1.c0.double_inp(); + f.c1.c0.add_inp(&t3); + + f.c0.c2 = -f.c0.c2; + f.c0.c2.add_inp(&t2); + f.c0.c2.double_inp(); + f.c0.c2.add_inp(&t2); + } #[must_use] - fn cycolotomic_exp(f: Fp12) -> Fp12 { + fn cycolotomic_exp(f: &Fp12) -> Fp12 { let x = BLS_X; let mut tmp = Fp12::one(); let mut found_one = false; for i in (0..64).rev().map(|b| ((x >> b) & 1) == 1) { if found_one { - tmp = cyclotomic_square(tmp) + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + cyclotomic_square_inp(&mut tmp); + } else { + tmp = cyclotomic_square(tmp); + } + } } else { found_one = i; } if i { - tmp *= f; + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + tmp.mul_inp(f); + } else { + tmp *= f; + } + } } } tmp.conjugate() } - let mut f = self.0; - let mut t0 = f - .frobenius_map() - .frobenius_map() - .frobenius_map() - .frobenius_map() - .frobenius_map() - .frobenius_map(); - Gt(f.invert() - .map(|mut t1| { + cfg_if::cfg_if! { + if #[cfg(target_os = "zkvm")] { + let mut t0 = self.0; + t0.frobenius_map_inp(); + t0.frobenius_map_inp(); + t0.frobenius_map_inp(); + t0.frobenius_map_inp(); + t0.frobenius_map_inp(); + t0.frobenius_map_inp(); + + let mut t2 = self.0.invert().unwrap(); + t2.mul_inp(&t0); + let mut t1 = t2; + t2.frobenius_map_inp(); + t2.frobenius_map_inp(); + t2.mul_inp(&t1); + t1 = cyclotomic_square(t2); + t1.conjugate_inp(); + let mut t3 = cycolotomic_exp(&t2); + let mut t4 = cyclotomic_square(t3); + let mut t5 = t1; + t5.mul_inp(&t3); + t1 = cycolotomic_exp(&t5); + t0 = cycolotomic_exp(&t1); + let mut t6 = cycolotomic_exp(&t0); + t6.mul_inp(&t4); + t4 = cycolotomic_exp(&t6); + t5.conjugate_inp(); + t4.mul_inp(&t5); + t4.mul_inp(&t2); + t1.mul_inp(&t2); + t2.conjugate_inp(); + t1.frobenius_map_inp(); + t1.frobenius_map_inp(); + t1.frobenius_map_inp(); + t6.mul_inp(&t2); + t6.frobenius_map_inp(); + t3.mul_inp(&t0); + t3.frobenius_map_inp(); + t3.frobenius_map_inp(); + t3.mul_inp(&t1); + t3.mul_inp(&t6); + t3.mul_inp(&t4); + Gt(t3) + } else { + let mut t0 = self + .0 + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map() + .frobenius_map(); + + // We unwrap() because `MillerLoopResult` can only be constructed + // by a function within this crate, and we uphold the invariant + // that the enclosed value is nonzero. + let mut t1 = self.0.invert().unwrap(); + let mut t2 = t0 * t1; t1 = t2; t2 = t2.frobenius_map().frobenius_map(); t2 *= t1; t1 = cyclotomic_square(t2).conjugate(); - let mut t3 = cycolotomic_exp(t2); + let mut t3 = cycolotomic_exp(&t2); let mut t4 = cyclotomic_square(t3); let mut t5 = t1 * t3; - t1 = cycolotomic_exp(t5); - t0 = cycolotomic_exp(t1); - let mut t6 = cycolotomic_exp(t0); + t1 = cycolotomic_exp(&t5); + t0 = cycolotomic_exp(&t1); + let mut t6 = cycolotomic_exp(&t0); t6 *= t4; - t4 = cycolotomic_exp(t6); + t4 = cycolotomic_exp(&t6); t5 = t5.conjugate(); t4 *= t5 * t2; t5 = t2.conjugate(); @@ -165,14 +344,9 @@ impl MillerLoopResult { t3 = t3.frobenius_map().frobenius_map(); t3 *= t1; t3 *= t6; - f = t3 * t4; - - f - }) - // We unwrap() because `MillerLoopResult` can only be constructed - // by a function within this crate, and we uphold the invariant - // that the enclosed value is nonzero. - .unwrap()) + Gt(t3 * t4) + } + } } }