diff --git a/src/gadgets/bn254/fp254impl.rs b/src/gadgets/bn254/fp254impl.rs index 39b2aa22..506332c4 100644 --- a/src/gadgets/bn254/fp254impl.rs +++ b/src/gadgets/bn254/fp254impl.rs @@ -245,7 +245,7 @@ pub trait Fp254Impl { /// # Arguments /// * `circuit` - Circuit to add gates to /// * `a` - Wire in Montgomery form - /// * `b` - Constant in standard form + /// * `b` - Constant in Montgomery form /// /// # Returns /// Product in Montgomery form diff --git a/src/gadgets/bn254/fq2.rs b/src/gadgets/bn254/fq2.rs index d1f61007..dad89018 100644 --- a/src/gadgets/bn254/fq2.rs +++ b/src/gadgets/bn254/fq2.rs @@ -312,11 +312,8 @@ impl Fq2 { ) -> Fq2 { assert_eq!(b.len(), Fq::N_BITS); - // Convert constant components to Montgomery so the result stays in Montgomery form - let a0_m = Fq::as_montgomery(a.c0); - let a1_m = Fq::as_montgomery(a.c1); - let c0 = Fq::mul_by_constant_montgomery(circuit, b, &a0_m); - let c1 = Fq::mul_by_constant_montgomery(circuit, b, &a1_m); + let c0 = Fq::mul_by_constant_montgomery(circuit, b, &a.c0); + let c1 = Fq::mul_by_constant_montgomery(circuit, b, &a.c1); Fq2::from_components(c0, c1) } @@ -444,6 +441,11 @@ impl Fq2 { Fq2::from_components(c0_final, c1_final) } + + pub fn conjugate(circuit: &mut C, a: &Fq2) -> Fq2 { + let new_c1 = Fq::neg(circuit, &a.c1().clone()); + Fq2::from_components(a.c0().clone(), new_c1) + } } #[cfg(test)] diff --git a/src/gadgets/bn254/g1.rs b/src/gadgets/bn254/g1.rs index dfcc9919..47731653 100644 --- a/src/gadgets/bn254/g1.rs +++ b/src/gadgets/bn254/g1.rs @@ -6,7 +6,7 @@ use circuit_component_macro::component; use crate::{ CircuitContext, WireId, circuit::{FromWires, WiresObject}, - gadgets::bn254::{fp254impl::Fp254Impl, fq::Fq, fr::Fr}, + gadgets::{bigint, bn254::{fp254impl::Fp254Impl, fq::Fq, fr::Fr}}, }; #[derive(Clone, Debug)] @@ -398,6 +398,23 @@ impl G1Projective { z: p.z.clone(), } } + + /// check whether or not the point is on the curve or not + /// checks y^2=x^3+3z^6 (Jacobian projective coordinates) + #[component] + pub fn is_on_curve(circuit: &mut C, p: &G1Projective) -> WireId { + let x2 = Fq::square_montgomery(circuit, &p.x); + let x3 = Fq::mul_montgomery(circuit, &p.x, &x2); + let y2 = Fq::square_montgomery(circuit, &p.y); + let z2 = Fq::square_montgomery(circuit, &p.z); + let z4 = Fq::square_montgomery(circuit, &z2); + let z6 = Fq::mul_montgomery(circuit, &z2, &z4); + let triplez6 = Fq::triple(circuit, &z6); + let temp = Fq::add(circuit, &x3, &triplez6); + let should_be_zero = Fq::sub(circuit, &y2, &temp); + let result = bigint::equal_zero(circuit, &should_be_zero.0); + result + } } #[cfg(test)] @@ -842,4 +859,76 @@ mod tests { let actual_result = G1Projective::from_bits_unchecked(result.output_value.clone()); assert_eq!(actual_result, neg_a_mont); } + + #[test] + fn test_g1p_is_on_curve() { + // Generate random G1 points, a is on curve, b isn't + let a = rnd(); + let b = ark_bn254::G1Projective { + x: Fq::random(&mut trng()), + y: Fq::random(&mut trng()), + z: Fq::random(&mut trng()), + }; + + // Convert to Montgomery form + let a_mont = G1Projective::as_montgomery(a); + let b_mont = G1Projective::as_montgomery(b); + + // Define input structure + struct OneG1Input { + a: ark_bn254::G1Projective, + } + struct OneG1InputWire { + a: G1Projective, + } + impl crate::circuit::CircuitInput for OneG1Input { + type WireRepr = OneG1InputWire; + fn allocate(&self, issue: impl FnMut() -> WireId) -> Self::WireRepr { + OneG1InputWire { + a: G1Projective::new(issue), + } + } + fn collect_wire_ids(repr: &Self::WireRepr) -> Vec { + let mut wires = Vec::new(); + wires.extend(repr.a.x.iter()); + wires.extend(repr.a.y.iter()); + wires.extend(repr.a.z.iter()); + wires + } + } + impl> EncodeInput for OneG1Input { + fn encode(&self, repr: &OneG1InputWire, cache: &mut M) { + let a_fn = G1Projective::get_wire_bits_fn(&repr.a, &self.a).unwrap(); + for &wire_id in repr + .a + .x + .iter() + .chain(repr.a.y.iter()) + .chain(repr.a.z.iter()) + { + if let Some(bit) = a_fn(wire_id) { + cache.feed_wire(wire_id, bit); + } + } + } + } + + let inputs = OneG1Input { a: a_mont }; + let result: crate::circuit::StreamingResult<_, _, Vec> = + CircuitBuilder::streaming_execute(inputs, 10_000, |root, inputs_wire| { + let result = G1Projective::is_on_curve(root, &inputs_wire.a); + vec![result] + }); + + assert!(result.output_value.clone()[0]); + + let inputs = OneG1Input { a: b_mont }; + let result: crate::circuit::StreamingResult<_, _, Vec> = + CircuitBuilder::streaming_execute(inputs, 10_000, |root, inputs_wire| { + let result = G1Projective::is_on_curve(root, &inputs_wire.a); + vec![result] + }); + + assert!(!result.output_value.clone()[0]); + } } diff --git a/src/gadgets/bn254/g2.rs b/src/gadgets/bn254/g2.rs index 441a9e8c..f289c743 100644 --- a/src/gadgets/bn254/g2.rs +++ b/src/gadgets/bn254/g2.rs @@ -1,6 +1,7 @@ use std::{cmp::min, collections::HashMap, iter::zip}; -use ark_ff::Zero; +use ark_ec::bn::BnConfig; +use ark_ff::{AdditiveGroup, Zero}; use circuit_component_macro::component; use crate::{ @@ -496,6 +497,56 @@ impl G2Projective { acc } + // #[component(offcircuit_args = "s")] + pub fn scalar_mul_by_constant_scalar_montgomery( + circuit: &mut C, + s: &u64, + base: &G2Projective, + ) -> G2Projective { + + fn scalar_pieces(window: usize, scalar: u64) -> Vec { + let mut a = scalar; + let c = (1 << window) - 1; + let mut pieces = Vec::new(); + + while a != 0 { + pieces.push(a & c); + a >>= window; + } + pieces.reverse(); + pieces + } + + let n = 2_usize.pow(W as u32); + + let mut bases = Vec::new(); + let mut p = G2Projective::new_constant(&G2Projective::as_montgomery( + ark_bn254::G2Projective::default(), + )) + .unwrap(); + + for _ in 0..n { + bases.push(p.clone()); + p = G2Projective::add_montgomery(circuit, &p, &base); + } + + let pieces = scalar_pieces(W, *s); + + let mut acc = G2Projective::new_constant(&G2Projective::as_montgomery( + ark_bn254::G2Projective::default(), + )) + .unwrap(); + + for piece in pieces { + for _ in 0..W { + acc = G2Projective::double_montgomery(circuit, &acc); + } + acc = G2Projective::add_montgomery(circuit, &acc, &bases[piece as usize]); + } + + acc + } + pub fn msm_with_constant_bases_montgomery( circuit: &mut C, scalars: &Vec, @@ -524,12 +575,46 @@ impl G2Projective { z: p.z.clone(), } } + + #[component] + pub fn psi_montgomery(circuit: &mut C, p: &G2Projective) -> G2Projective { + let a = ark_bn254::Config::TWIST_MUL_BY_Q_X; + let b = ark_bn254::Config::TWIST_MUL_BY_Q_Y; + let y_conjugate = Fq2::conjugate(circuit, &p.y); + let new_y = Fq2::mul_by_constant_montgomery(circuit, &y_conjugate, &Fq2::as_montgomery(b)); + let x_conjugate = Fq2::conjugate(circuit, &p.x); + let new_x = Fq2::mul_by_constant_montgomery(circuit, &x_conjugate, &Fq2::as_montgomery(a)); + let new_p = G2Projective { + x: new_x, + y: new_y, + z: p.z.clone(), + }; + new_p + } + + #[component] + pub fn is_r_torsion_montgomery(circuit: &mut C, p: &G2Projective) -> WireId { + let x = 4965661367192848881; + let xp = G2Projective::scalar_mul_by_constant_scalar_montgomery::<_, 2>(circuit, &x, p); + let xp_plus_p = G2Projective::add_montgomery(circuit, &xp, p); + // ψ([x₀]P) + ψ²([x₀]P) - ψ³([2x₀]P) + let psi_1_xp = G2Projective::psi_montgomery(circuit, &xp); + let psi_2_xp = G2Projective::psi_montgomery(circuit, &psi_1_xp); + let psi_3_xp = G2Projective::psi_montgomery(circuit, &psi_2_xp); + let psi_3_xp_double = G2Projective::double_montgomery(circuit, &psi_3_xp); + let psi_3_xp_double_neg = G2Projective::neg(circuit, &psi_3_xp_double); + let a = G2Projective::add_montgomery(circuit, &psi_1_xp, &psi_2_xp); + let b = G2Projective::add_montgomery(circuit, &a, &psi_3_xp_double_neg); + let c = G2Projective::add_montgomery(circuit, &b, &xp_plus_p); + let result = Fq2::equal_constant(circuit, &c.z, &ark_bn254::Fq2::ZERO); + result + } } #[cfg(test)] mod tests { - use ark_ec::{CurveGroup, VariableBaseMSM}; - use ark_ff::UniformRand; + use ark_ec::{CurveGroup, VariableBaseMSM, short_weierstrass::SWCurveConfig}; + use ark_ff::{Field, UniformRand}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; @@ -794,7 +879,7 @@ mod tests { } #[test] - fn test_g2p_scalar_mul_with_constant_base_montgomery() { + fn test_g2p_scalar_mul_by_constant_base_montgomery() { let s = rnd_fr(&mut trng()); let p = rnd_g2(&mut trng()); let result = p * s; @@ -814,6 +899,30 @@ mod tests { assert_eq!(actual_result, G2Projective::as_montgomery(result)); } + #[test] + fn test_g2p_scalar_mul_by_constant_scalar_montgomery() { + let x = 4965661367192848881; + let s = x; + let p = rnd_g2(&mut trng()); + let result = p * ark_bn254::Fr::from(s); + + let p_mont = G2Projective::as_montgomery(p); + + let inputs = G2Input { points: [p_mont] }; + let circuit_result: crate::circuit::StreamingResult<_, _, Vec> = + CircuitBuilder::streaming_execute(inputs, 10_000, |root, inputs_wire| { + let result_wires = G2Projective::scalar_mul_by_constant_scalar_montgomery::<_, 2>( + root, + &s, + &inputs_wire.points[0], + ); + result_wires.to_wires_vec() + }); + + let actual_result = G2Projective::from_bits_unchecked(circuit_result.output_value.clone()); + assert_eq!(actual_result, G2Projective::as_montgomery(result)); + } + #[test] fn test_msm_with_constant_bases_montgomery() { let n = 1; @@ -873,4 +982,48 @@ mod tests { let actual_result = G2Projective::from_bits_unchecked(circuit_result.output_value.clone()); assert_eq!(actual_result, G2Projective::as_montgomery(result)); } + + #[test] + fn test_g2p_is_r_torsion_montgomery() { + // a point which is in r-torsion subgroup G2 + let p = rnd_g2(&mut trng()); + assert!(p.into_affine().is_on_curve()); + assert!(p.into_affine().is_in_correct_subgroup_assuming_on_curve()); + + let p_mont = G2Projective::as_montgomery(p); + + let inputs = G2Input { points: [p_mont] }; + let circuit_result: crate::circuit::StreamingResult<_, _, Vec> = + CircuitBuilder::streaming_execute(inputs, 10_000, |root, inputs_wire| { + let result_wires = G2Projective::is_r_torsion_montgomery(root, &inputs_wire.points[0]); + result_wires.to_wires_vec() + }); + + assert!(circuit_result.output_value[0].clone()); + + // a point which is NOT in r-torsion subgroup G2 + let px = Fq2::random(&mut trng()); + let px2 = px.square(); + let px3 = px2 * px; + let py2 = px3 + ark_bn254::g2::Config::COEFF_B; + let py = py2.sqrt().unwrap(); + let p = ark_bn254::G2Projective { + x: px, + y: py, + z: ark_bn254::Fq2::ONE + }; + assert!(p.into_affine().is_on_curve()); + assert!(!p.into_affine().is_in_correct_subgroup_assuming_on_curve()); + + let p_mont = G2Projective::as_montgomery(p); + + let inputs = G2Input { points: [p_mont] }; + let circuit_result: crate::circuit::StreamingResult<_, _, Vec> = + CircuitBuilder::streaming_execute(inputs, 10_000, |root, inputs_wire| { + let result_wires = G2Projective::is_r_torsion_montgomery(root, &inputs_wire.points[0]); + result_wires.to_wires_vec() + }); + + assert!(!circuit_result.output_value[0].clone()); + } } diff --git a/src/gadgets/bn254/pairing.rs b/src/gadgets/bn254/pairing.rs index 00e5b240..54e1e013 100644 --- a/src/gadgets/bn254/pairing.rs +++ b/src/gadgets/bn254/pairing.rs @@ -141,11 +141,8 @@ pub fn ell_eval_const( let c0_fq2 = Fq2::mul_constant_by_fq_montgomery(circuit, &coeffs.c0, &p.y); // c1' = coeffs.1 * p.x (in Fq2) as wires let c3_fq2 = Fq2::mul_constant_by_fq_montgomery(circuit, &coeffs.c1, &p.x); - // c2 is a constant (Fq2); for mul_by_constant_montgomery/add_constant paths - // we pass it in Montgomery form to match other Montgomery-form wires. - let c4_const = Fq2::as_montgomery(coeffs.c2); - Fq12::mul_by_034_constant4_montgomery(circuit, f, &c0_fq2, &c3_fq2, &c4_const) + Fq12::mul_by_034_constant4_montgomery(circuit, f, &c0_fq2, &c3_fq2, &coeffs.c2) } /// Evaluate a BN254 line with variable G2 coefficients at an affine G1 point and @@ -563,20 +560,20 @@ pub fn miller_loop_const_q_affine( } let c = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); let bit = ark_bn254::Config::ATE_LOOP_COUNT[i - 1]; if bit == 1 || bit == -1 { let c2 = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c2, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c2), p); } } // Final two steps let c_last = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c_last, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c_last), p); let c_last2 = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c_last2, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c_last2), p); f } @@ -615,14 +612,14 @@ pub fn multi_miller_loop_const_q_affine( let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } let bit = ark_bn254::Config::ATE_LOOP_COUNT[i - 1]; if bit == 1 || bit == -1 { let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } } } @@ -630,7 +627,7 @@ pub fn multi_miller_loop_const_q_affine( for _ in 0..2 { let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } } @@ -753,20 +750,20 @@ pub fn miller_loop_const_q( } let c = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c, &p_aff); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), &p_aff); let bit = ark_bn254::Config::ATE_LOOP_COUNT[i - 1]; if bit == 1 || bit == -1 { let c2 = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c2, &p_aff); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c2), &p_aff); } } // Final two additions outside the loop let c_last = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c_last, &p_aff); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c_last), &p_aff); let c_last2 = coeff_iter.next().expect("coeff present"); - f = ell_eval_const(circuit, &f, c_last2, &p_aff); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c_last2), &p_aff); f } @@ -814,14 +811,14 @@ pub fn multi_miller_loop_const_q( let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps_aff.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } let bit = ark_bn254::Config::ATE_LOOP_COUNT[i - 1]; if bit == 1 || bit == -1 { let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps_aff.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } } } @@ -830,7 +827,7 @@ pub fn multi_miller_loop_const_q( for _ in 0..2 { let coeffs_now = per_step_iter.next().expect("coeffs present"); for (c, p) in coeffs_now.into_iter().zip(ps_aff.iter()) { - f = ell_eval_const(circuit, &f, c, p); + f = ell_eval_const(circuit, &f, &Fq6::as_montgomery(*c), p); } } @@ -936,9 +933,7 @@ pub fn ell_by_constant_montgomery( let new_c0 = Fq2::mul_constant_by_fq_montgomery(circuit, c0, py); let new_c1 = Fq2::mul_constant_by_fq_montgomery(circuit, c1, px); - // c2 must be provided in Montgomery form for the constant path - let c2_m = Fq2::as_montgomery(*c2); - Fq12::mul_by_034_constant4_montgomery(circuit, f, &new_c0, &new_c1, &c2_m) + Fq12::mul_by_034_constant4_montgomery(circuit, f, &new_c0, &new_c1, &c2) } #[component(offcircuit_args = "q1,q2")] @@ -966,10 +961,10 @@ pub fn multi_miller_loop_groth16_evaluate_montgomery_fast( } let q1ell_next = q1_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q1ell_next, p1); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q1ell_next), p1); let q2ell_next = q2_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q2ell_next, p2); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q2ell_next), p2); let q3ell_next = q3_ell.next().unwrap().clone(); f = ell_montgomery(circuit, &f, &q3ell_next, p3); @@ -977,10 +972,10 @@ pub fn multi_miller_loop_groth16_evaluate_montgomery_fast( let bit = ark_bn254::Config::ATE_LOOP_COUNT[i - 1]; if bit == 1 || bit == -1 { let q1ell_next = q1_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q1ell_next, p1); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q1ell_next), p1); let q2ell_next = q2_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q2ell_next, p2); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q2ell_next), p2); let q3ell_next = q3_ell.next().unwrap().clone(); f = ell_montgomery(circuit, &f, &q3ell_next, p3); @@ -988,19 +983,19 @@ pub fn multi_miller_loop_groth16_evaluate_montgomery_fast( } let q1ell_next = q1_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q1ell_next, p1); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q1ell_next), p1); let q2ell_next = q2_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q2ell_next, p2); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q2ell_next), p2); let q3ell_next = q3_ell.next().unwrap().clone(); f = ell_montgomery(circuit, &f, &q3ell_next, p3); let q1ell_next = q1_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q1ell_next, p1); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q1ell_next), p1); let q2ell_next = q2_ell.next().unwrap(); - f = ell_by_constant_montgomery(circuit, &f, q2ell_next, p2); + f = ell_by_constant_montgomery(circuit, &f, &Fq6::as_montgomery(*q2ell_next), p2); let q3ell_next = q3_ell.next().unwrap().clone(); f = ell_montgomery(circuit, &f, &q3ell_next, p3); @@ -1994,6 +1989,7 @@ mod tests { let coeffs = ell_coeffs(q); // choose first step coeffs let coeff = coeffs[0]; + let coeff_m = Fq6::as_montgomery(coeff); // random G1 point and initial f=1 let p = (ark_bn254::G1Projective::generator() * rnd_fr(&mut rng)) @@ -2018,7 +2014,7 @@ mod tests { }; let result = CircuitBuilder::streaming_execute::<_, _, Fq12Output>(input, 10_000, |ctx, input| { - ell_eval_const(ctx, &input.f, &coeff, &input.p) + ell_eval_const(ctx, &input.f, &coeff_m, &input.p) }); assert_eq!(result.output_value.value, expected_m);