From 456f30a8497860f0f54436092cf6a4e84a3265ca Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Tue, 7 Apr 2020 19:29:54 -0700 Subject: [PATCH] Fix #172 and improve squaring code for deg-3 extensions (#174) --- r1cs-std/src/bits/uint64.rs | 50 +++--- r1cs-std/src/fields/fp12.rs | 51 +----- r1cs-std/src/fields/fp2.rs | 51 +----- r1cs-std/src/fields/fp3.rs | 209 ++++++++++++------------ r1cs-std/src/fields/fp4.rs | 53 +----- r1cs-std/src/fields/fp6_2over3.rs | 53 +----- r1cs-std/src/fields/fp6_3over2.rs | 203 +++++++++++------------ r1cs-std/src/fields/mod.rs | 18 +- r1cs-std/src/lib.rs | 2 +- r1cs-std/src/test_constraint_counter.rs | 8 +- 10 files changed, 255 insertions(+), 443 deletions(-) diff --git a/r1cs-std/src/bits/uint64.rs b/r1cs-std/src/bits/uint64.rs index babc1f1..b834479 100644 --- a/r1cs-std/src/bits/uint64.rs +++ b/r1cs-std/src/bits/uint64.rs @@ -14,7 +14,7 @@ use core::borrow::Borrow; #[derive(Clone, Debug)] pub struct UInt64 { // Least significant bit_gadget first - bits: Vec, + bits: Vec, value: Option, } @@ -56,7 +56,7 @@ impl UInt64 { } v - } + }, None => vec![None; 64], }; @@ -95,19 +95,19 @@ impl UInt64 { if b { value.as_mut().map(|v| *v |= 1); } - } + }, &Boolean::Is(ref b) => match b.get_value() { Some(true) => { value.as_mut().map(|v| *v |= 1); - } - Some(false) => {} + }, + Some(false) => {}, None => value = None, }, &Boolean::Not(ref b) => match b.get_value() { Some(false) => { value.as_mut().map(|v| *v |= 1); - } - Some(true) => {} + }, + Some(true) => {}, None => value = None, }, } @@ -129,7 +129,7 @@ impl UInt64 { .collect(); UInt64 { - bits: new_bits, + bits: new_bits, value: self.value.map(|v| v.rotate_right(by as u32)), } } @@ -194,12 +194,12 @@ impl UInt64 { match op.value { Some(val) => { result_value.as_mut().map(|v| *v += u128::from(val)); - } + }, None => { // If any of our operands have unknown value, we won't // know the value of the result result_value = None; - } + }, } // Iterate over each bit_gadget of the operand and add the operand to @@ -212,18 +212,18 @@ impl UInt64 { // Add coeff * bit_gadget lc += (coeff, bit.get_variable()); - } + }, Boolean::Not(ref bit) => { all_constants = false; // Add coeff * (1 - bit_gadget) = coeff * ONE - coeff * bit_gadget lc = lc + (coeff, CS::one()) - (coeff, bit.get_variable()); - } + }, Boolean::Constant(bit) => { if bit { lc += (coeff, CS::one()); } - } + }, } coeff.double_in_place(); @@ -270,7 +270,7 @@ impl UInt64 { result_bits.truncate(64); Ok(UInt64 { - bits: result_bits, + bits: result_bits, value: modular_value, }) } @@ -330,7 +330,7 @@ impl ToBytesGadget for UInt64 { let mut bytes = Vec::new(); for (i, chunk8) in self.to_bits_le().chunks(8).enumerate() { let byte = UInt8 { - bits: chunk8.to_vec(), + bits: chunk8.to_vec(), value: value_chunks[i], }; bytes.push(byte); @@ -397,7 +397,7 @@ mod test { match bit_gadget { &Boolean::Constant(bit_gadget) => { assert!(bit_gadget == ((b.value.unwrap() >> i) & 1 == 1)); - } + }, _ => unreachable!(), } } @@ -406,8 +406,8 @@ mod test { for x in v.iter().zip(expected_to_be_same.iter()) { match x { - (&Boolean::Constant(true), &Boolean::Constant(true)) => {} - (&Boolean::Constant(false), &Boolean::Constant(false)) => {} + (&Boolean::Constant(true), &Boolean::Constant(true)) => {}, + (&Boolean::Constant(false), &Boolean::Constant(false)) => {}, _ => unreachable!(), } } @@ -442,13 +442,13 @@ mod test { match b { &Boolean::Is(ref b) => { assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } + }, &Boolean::Not(ref b) => { assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } + }, &Boolean::Constant(b) => { assert!(b == (expected & 1 == 1)); - } + }, } expected >>= 1; @@ -483,7 +483,7 @@ mod test { &Boolean::Not(_) => panic!(), &Boolean::Constant(b) => { assert!(b == (expected & 1 == 1)); - } + }, } expected >>= 1; @@ -521,10 +521,10 @@ mod test { match b { &Boolean::Is(ref b) => { assert!(b.get_value().unwrap() == (expected & 1 == 1)); - } + }, &Boolean::Not(ref b) => { assert!(!b.get_value().unwrap() == (expected & 1 == 1)); - } + }, &Boolean::Constant(_) => unreachable!(), } @@ -560,7 +560,7 @@ mod test { match b { &Boolean::Constant(b) => { assert_eq!(b, tmp & 1 == 1); - } + }, _ => unreachable!(), } diff --git a/r1cs-std/src/fields/fp12.rs b/r1cs-std/src/fields/fp12.rs index 4df3ca8..55a4691 100644 --- a/r1cs-std/src/fields/fp12.rs +++ b/r1cs-std/src/fields/fp12.rs @@ -6,11 +6,11 @@ use algebra::{ fp6_3over2::{Fp6, Fp6Parameters}, Fp2Parameters, }, - BitIterator, Field, One, PrimeField, + BitIterator, PrimeField, }; use core::{borrow::Borrow, marker::PhantomData}; -use crate::{prelude::*, Assignment, Vec}; +use crate::{prelude::*, Vec}; type Fp2Gadget = super::fp2::Fp2Gadget< <

::Fp6Params as Fp6Parameters>::Fp2Params, @@ -546,47 +546,6 @@ where Ok(self) } - fn inverse>( - &self, - mut cs: CS, - ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - - // Karatsuba multiplication for Fp2 with the inverse: - // v0 = A.c0 * B.c0 - // v1 = A.c1 * B.c1 - // - // 1 = v0 + non_residue * v1 - // => v0 = 1 - non_residue * v1 - // - // 0 = result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 - // => v0 + v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Enforced with 2 constraints: - // A.c1 * B.c1 = v1 - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Reference: - // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // Constraint 1 - let v1 = self.c1.mul(cs.ns(|| "inv_constraint_1"), &inverse.c1)?; - - // Constraint 2 - let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; - let b0_plus_b1 = inverse.c0.add(cs.ns(|| "b0 + b1"), &inverse.c1)?; - - let one = Fp6::::one(); - let rhs = Self::mul_fp6_by_nonresidue(cs.ns(|| "nr * v1"), &v1)? - .sub(cs.ns(|| "sub v1"), &v1)? - .negate(cs.ns(|| "negate it"))? - .add_constant(cs.ns(|| "add one"), &one)?; - a0_plus_a1.mul_equals(cs.ns(|| "inv_constraint_2"), &b0_plus_b1, &rhs)?; - Ok(inverse) - } - fn mul_equals>( &self, mut cs: CS, @@ -639,16 +598,12 @@ where } fn cost_of_mul() -> usize { - unimplemented!() + 3 * Fp6Gadget::::cost_of_mul() } fn cost_of_mul_equals() -> usize { Self::cost_of_mul() } - - fn cost_of_inv() -> usize { - Self::cost_of_mul() + >::cost() - } } impl PartialEq for Fp12Gadget diff --git a/r1cs-std/src/fields/fp2.rs b/r1cs-std/src/fields/fp2.rs index 463bf75..f38e2bb 100644 --- a/r1cs-std/src/fields/fp2.rs +++ b/r1cs-std/src/fields/fp2.rs @@ -1,11 +1,11 @@ use algebra::{ fields::{Fp2, Fp2Parameters}, - Field, PrimeField, + PrimeField, }; use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, ConstraintVar, SynthesisError}; -use crate::{fields::fp::FpGadget, prelude::*, Assignment, Vec}; +use crate::{fields::fp::FpGadget, prelude::*, Vec}; #[derive(Derivative)] #[derivative(Debug(bound = "P: Fp2Parameters, ConstraintF: PrimeField"))] @@ -289,47 +289,6 @@ impl, ConstraintF: PrimeField> FieldGadget>( - &self, - mut cs: CS, - ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - - // Karatsuba multiplication for Fp2 with the inverse: - // v0 = A.c0 * B.c0 - // v1 = A.c1 * B.c1 - // - // 1 = v0 + non_residue * v1 - // => v0 = 1 - non_residue * v1 - // - // 0 = result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 - // => v0 + v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Enforced with 2 constraints: - // A.c1 * B.c1 = v1 - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Reference: - // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // Constraint 1 - let mut v1 = self.c1.mul(cs.ns(|| "inv_constraint_1"), &inverse.c1)?; - - // Constraint 2 - let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; - let b0_plus_b1 = inverse.c0.add(cs.ns(|| "b0 + b1"), &inverse.c1)?; - - let one = P::Fp::one(); - let rhs = v1 - .mul_by_constant_in_place(cs.ns(|| "(1 - nonresidue) * v1"), &(one - &P::NONRESIDUE))? - .add_constant_in_place(cs.ns(|| "add one"), &one)?; - a0_plus_a1.mul_equals(cs.ns(|| "inv_constraint_2"), &b0_plus_b1, rhs)?; - Ok(inverse) - } - fn mul_equals>( &self, mut cs: CS, @@ -450,16 +409,12 @@ impl, ConstraintF: PrimeField> FieldGadget usize { - 3 + 3 * FpGadget::::cost_of_mul() } fn cost_of_mul_equals() -> usize { Self::cost_of_mul() } - - fn cost_of_inv() -> usize { - 2 - } } impl, ConstraintF: PrimeField> PartialEq diff --git a/r1cs-std/src/fields/fp3.rs b/r1cs-std/src/fields/fp3.rs index ec5dfb9..ec705bb 100644 --- a/r1cs-std/src/fields/fp3.rs +++ b/r1cs-std/src/fields/fp3.rs @@ -1,14 +1,11 @@ use algebra::{ - fields::{ - fp3::{Fp3, Fp3Parameters}, - Field, - }, + fields::fp3::{Fp3, Fp3Parameters}, PrimeField, SquareRootField, }; use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, ConstraintVar, SynthesisError}; -use crate::{fields::fp::FpGadget, prelude::*, Assignment, Vec}; +use crate::{fields::fp::FpGadget, prelude::*, Vec}; #[derive(Derivative)] #[derivative(Debug( @@ -40,6 +37,7 @@ impl, ConstraintF: PrimeField + SquareRootFie _params: PhantomData, } } + /// Multiply a FpGadget by quadratic nonresidue P::NONRESIDUE. #[inline] pub fn mul_fp_gadget_by_nonresidue>( @@ -332,120 +330,113 @@ impl, ConstraintF: PrimeField + SquareRootFie Ok(Self::new(c0, c1, c2)) } - /// Use the Toom-Cook-3x method to compute multiplication. #[inline] - fn square>( + fn mul_equals>( &self, mut cs: CS, - ) -> Result { - // Uses Toom-Cook-3x multiplication from + other: &Self, + result: &Self, + ) -> Result<(), SynthesisError> { + // Karatsuba multiplication for Fp3: + // v0 = A.c0 * B.c0 + // v1 = A.c1 * B.c1 + // v2 = A.c2 * B.c2 + // result.c0 = v0 + β((a1 + a2)(b1 + b2) − v1 − v2) + // result.c1 = (a0 + a1)(b0 + b1) − v0 − v1 + βv2 + // result.c2 = (a0 + a2)(b0 + b2) − v0 + v1 − v2, + // We enforce this with six constraints: + // + // v0 = A.c0 * B.c0 + // v1 = A.c1 * B.c1 + // v2 = A.c2 * B.c2 // + // result.c0 - v0 + \beta*(v1 + v2) = β(a1 + a2)(b1 + b2)) + // result.c1 + v0 + v1 - βv2 = (a0 + a1)(b0 + b1) + // result.c2 + v0 - v1 + v2 = (a0 + a2)(b0 + b2) // Reference: // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // v0 = a(0)^2 = a0^2 - let v0 = self.c0.square(&mut cs.ns(|| "Calc v0"))?; - - // v1 = a(1)^2 = (a0 + a1 + a2)^2 - let v1 = { - let a0_plus_a1_plus_a2 = self - .c0 - .add(cs.ns(|| "a0 + a1"), &self.c1)? - .add(cs.ns(|| "a0 + a1 + a2"), &self.c2)?; - a0_plus_a1_plus_a2.square(&mut cs.ns(|| "(a0 + a1 + a2)^2"))? - }; - - // v2 = a(−1)^2 = (a0 − a1 + a2)^2 - let v2 = { - let a0_minus_a1_plus_a2 = self - .c0 - .sub(cs.ns(|| "a0 - a1"), &self.c1)? - .add(cs.ns(|| "a0 - a2 + a2"), &self.c2)?; - a0_minus_a1_plus_a2.square(&mut cs.ns(|| "(a0 - a1 + a2)^2"))? - }; - - // v3 = a(2)^2 = (a0 + 2a1 + 4a2)^2 - let v3 = { - let a1_double = self.c1.double(cs.ns(|| "2a1"))?; - let a2_quad = self.c2.double(cs.ns(|| "2a2"))?.double(cs.ns(|| "4a2"))?; - let a0_plus_2_a1_plus_4_a2 = self - .c0 - .add(cs.ns(|| "a0 + 2a1"), &a1_double)? - .add(cs.ns(|| "a0 + 2a1 + 4a2"), &a2_quad)?; - - a0_plus_2_a1_plus_4_a2.square(&mut cs.ns(|| "(a0 + 2a1 + 4a2)^2"))? - }; - - // v4 = a(∞)^2 = a2^2 - let v4 = self.c2.square(&mut cs.ns(|| "a2^2"))?; - - let two = P::Fp::one().double(); - let six = two.double() + &two; - let mut two_and_six = [two, six]; - algebra::fields::batch_inversion(&mut two_and_six); - let (two_inverse, six_inverse) = (two_and_six[0], two_and_six[1]); - - let half_v0 = v0.mul_by_constant(cs.ns(|| "half_v0"), &two_inverse)?; - let half_v1 = v1.mul_by_constant(cs.ns(|| "half_v1"), &two_inverse)?; - let one_sixth_v2 = v2.mul_by_constant(cs.ns(|| "one_sixth_v2"), &six_inverse)?; - let one_sixth_v3 = v3.mul_by_constant(cs.ns(|| "one_sixth_v3"), &six_inverse)?; - let two_v4 = v4.double(cs.ns(|| "double_v4"))?; - - // c0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) - let c0 = { - let mut c0_cs = cs.ns(|| "c0"); - // No constraints, only get a linear combination back. - let inner = half_v0 - .sub(c0_cs.ns(|| "sub1"), &half_v1)? - .sub(c0_cs.ns(|| "sub2"), &one_sixth_v2)? - .add(c0_cs.ns(|| "add3"), &one_sixth_v3)? - .sub(c0_cs.ns(|| "sub4"), &two_v4)?; - let non_residue_times_inner = - inner.mul_by_constant(c0_cs.ns(|| "mul_by_res"), &P::NONRESIDUE)?; - v0.add(c0_cs.ns(|| "add5"), &non_residue_times_inner)? - }; - - // −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 - let c1 = { - let mut c1_cs = cs.ns(|| "c1"); - let one_third_v2 = one_sixth_v2.double(c1_cs.ns(|| "v2_by_3"))?; - let non_residue_v4 = v4.mul_by_constant(c1_cs.ns(|| "mul_by_res"), &P::NONRESIDUE)?; - - half_v0 - .negate(c1_cs.ns(|| "neg1"))? - .add(c1_cs.ns(|| "add1"), &v1)? - .sub(c1_cs.ns(|| "sub2"), &one_third_v2)? - .sub(c1_cs.ns(|| "sub3"), &one_sixth_v3)? - .add(c1_cs.ns(|| "add4"), &two_v4)? - .add(c1_cs.ns(|| "add5"), &non_residue_v4)? - }; - - // -v0 + (1/2)v1 + (1/2)v2 −v4 - let c2 = { - let mut c2_cs = cs.ns(|| "c2"); - let half_v2 = v2.mul_by_constant(c2_cs.ns(|| "half_v2"), &two_inverse)?; - half_v1 - .add(c2_cs.ns(|| "add1"), &half_v2)? - .sub(c2_cs.ns(|| "sub1"), &v4)? - .sub(c2_cs.ns(|| "sub2"), &v0)? - }; - - Ok(Self::new(c0, c1, c2)) + // Devegili, OhEigeartaigh, Scott, Dahab + // + // This implementation adapted from + // https://github.com/ZencashOfficial/ginger-lib/blob/development/r1cs/gadgets/std/src/fields/fp3.rs + let v0 = self.c0.mul(cs.ns(|| "v0 = a0 * b0"), &other.c0)?; + let v1 = self.c1.mul(cs.ns(|| "v1 = a1 * b1"), &other.c1)?; + let v2 = self.c2.mul(cs.ns(|| "v2 = a2 * b2"), &other.c2)?; + + // Check c0 + let nr_a1_plus_a2 = self + .c1 + .add(cs.ns(|| "a1 + a2"), &self.c2)? + .mul_by_constant(cs.ns(|| "nr*(a1 + a2)"), &P::NONRESIDUE)?; + let b1_plus_b2 = other.c1.add(cs.ns(|| "b1 + b2"), &other.c2)?; + let nr_v1 = v1.mul_by_constant(cs.ns(|| "nr * v1"), &P::NONRESIDUE)?; + let nr_v2 = v2.mul_by_constant(cs.ns(|| "nr * v2"), &P::NONRESIDUE)?; + let to_check = result + .c0 + .sub(cs.ns(|| "c0 - v0"), &v0)? + .add(cs.ns(|| "c0 - v0 + nr * v1"), &nr_v1)? + .add(cs.ns(|| "c0 - v0 + nr * v1 + nr * v2"), &nr_v2)?; + nr_a1_plus_a2.mul_equals(cs.ns(|| "check c0"), &b1_plus_b2, &to_check)?; + + // Check c1 + let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; + let b0_plus_b1 = other.c0.add(cs.ns(|| "b0 + b1"), &other.c1)?; + let to_check = result + .c1 + .sub(cs.ns(|| "c1 - nr * v2"), &nr_v2)? + .add(cs.ns(|| "c1 - nr * v2 + v0"), &v0)? + .add(cs.ns(|| "c1 - nr * v2 + v0 + v1"), &v1)?; + a0_plus_a1.mul_equals(cs.ns(|| "check c1"), &b0_plus_b1, &to_check)?; + + // Check c2 + let a0_plus_a2 = self.c0.add(cs.ns(|| "a0 + a2"), &self.c2)?; + let b0_plus_b2 = other.c0.add(cs.ns(|| "b0 + b2"), &other.c2)?; + let to_check = result + .c2 + .add(cs.ns(|| "c2 + v0"), &v0)? + .sub(cs.ns(|| "c2 + v0 - v1"), &v1)? + .add(cs.ns(|| "c2 + v0 - v1 + v2"), &v2)?; + a0_plus_a2.mul_equals(cs.ns(|| "check c2"), &b0_plus_b2, &to_check)?; + Ok(()) } - // 18 constraints, we can probably do better but not sure it's worth it. + /// Use the Chung-Hasan asymmetric squaring formula. + /// + /// (Devegili OhEig Scott Dahab --- Multiplication and Squaring on + /// Abstract Pairing-Friendly + /// Fields.pdf; Section 4 (CH-SQR2)) #[inline] - fn inverse>( + fn square>( &self, mut cs: CS, ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - let one = Self::one(cs.ns(|| "one"))?; - inverse.mul_equals(cs.ns(|| "check inverse"), &self, &one)?; - Ok(inverse) + let a = self.c0.clone(); + let b = self.c1.clone(); + let c = self.c2.clone(); + + let s0 = a.square(cs.ns(|| "s0"))?; + let ab = a.mul(cs.ns(|| "ab"), &b)?; + let s1 = ab.double(cs.ns(|| "s1"))?; + let s2 = a + .sub(cs.ns(|| "a-b"), &b)? + .add(cs.ns(|| "plus c"), &c)? + .square(cs.ns(|| "s2"))?; + let s3 = b.mul(cs.ns(|| "bc"), &c)?.double(cs.ns(|| "s3"))?; + let s4 = c.square(cs.ns(|| "s4"))?; + + let c0 = Self::mul_fp_gadget_by_nonresidue(cs.ns(|| "c0 part 1"), &s3)? + .add(cs.ns(|| "c0"), &s0)?; + + let c1 = Self::mul_fp_gadget_by_nonresidue(cs.ns(|| "c1 part 1"), &s4)? + .add(cs.ns(|| "c1"), &s1)?; + + let c2 = s1 + .add(cs.ns(|| "c2 part1"), &s2)? + .add(cs.ns(|| "c2 part2"), &s3)? + .sub(cs.ns(|| "c2 part3"), &s0)? + .sub(cs.ns(|| "c2 part4"), &s4)?; + + Ok(Self::new(c0, c1, c2)) } #[inline] @@ -634,8 +625,8 @@ impl, ConstraintF: PrimeField + SquareRootFie 5 * FpGadget::::cost_of_mul() } - fn cost_of_inv() -> usize { - Self::cost_of_mul() + >::cost() + fn cost_of_mul_equals() -> usize { + 6 * FpGadget::::cost_of_mul() } } diff --git a/r1cs-std/src/fields/fp4.rs b/r1cs-std/src/fields/fp4.rs index c57774d..085e065 100644 --- a/r1cs-std/src/fields/fp4.rs +++ b/r1cs-std/src/fields/fp4.rs @@ -1,11 +1,11 @@ use algebra::{ fields::{Fp2, Fp2Parameters, Fp4, Fp4Parameters}, - BigInteger, Field, One, PrimeField, + BigInteger, PrimeField, }; use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, SynthesisError}; -use crate::{prelude::*, Assignment, Vec}; +use crate::{prelude::*, Vec}; type Fp2Gadget = super::fp2::Fp2Gadget<

::Fp2Params, ConstraintF>; @@ -308,48 +308,6 @@ where Ok(Self::new(c0, c1)) } - #[inline] - fn inverse>( - &self, - mut cs: CS, - ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - - // Karatsuba multiplication for Fp4 with the inverse: - // v0 = A.c0 * B.c0 - // v1 = A.c1 * B.c1 - // - // 1 = v0 + non_residue * v1 - // => v0 = 1 - non_residue * v1 - // - // 0 = result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 - // => v0 + v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Enforced with 2 constraints: - // A.c1 * B.c1 = v1 - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Reference: - // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // Constraint 1 - let v1 = self.c1.mul(cs.ns(|| "inv_constraint_1"), &inverse.c1)?; - - // Constraint 2 - let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; - let b0_plus_b1 = inverse.c0.add(cs.ns(|| "b0 + b1"), &inverse.c1)?; - - let one = Fp2::<

::Fp2Params>::one(); - let rhs = Self::mul_fp2_gadget_by_nonresidue(cs.ns(|| "nr * v1"), &v1)? - .sub(cs.ns(|| "sub v1"), &v1)? - .negate(cs.ns(|| "negate it"))? - .add_constant(cs.ns(|| "add one"), &one)?; - a0_plus_a1.mul_equals(cs.ns(|| "inv_constraint_2"), &b0_plus_b1, &rhs)?; - Ok(inverse) - } - fn mul_equals>( &self, mut cs: CS, @@ -475,17 +433,12 @@ where } fn cost_of_mul() -> usize { - 3 * as FieldGadget, ConstraintF>>::cost_of_mul( - ) + 3 * Fp2Gadget::::cost_of_mul() } fn cost_of_mul_equals() -> usize { Self::cost_of_mul() } - - fn cost_of_inv() -> usize { - unimplemented!() - } } impl PartialEq for Fp4Gadget diff --git a/r1cs-std/src/fields/fp6_2over3.rs b/r1cs-std/src/fields/fp6_2over3.rs index 9064022..7cd375a 100644 --- a/r1cs-std/src/fields/fp6_2over3.rs +++ b/r1cs-std/src/fields/fp6_2over3.rs @@ -3,12 +3,12 @@ use algebra::{ fp6_2over3::{Fp6, Fp6Parameters}, Fp3, Fp3Parameters, }, - BigInteger, Field, One, PrimeField, SquareRootField, + BigInteger, PrimeField, SquareRootField, }; use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, SynthesisError}; -use crate::{prelude::*, Assignment, Vec}; +use crate::{prelude::*, Vec}; type Fp3Gadget = super::fp3::Fp3Gadget<

::Fp3Params, ConstraintF>; @@ -291,48 +291,6 @@ where Ok(Self::new(c0, c1)) } - #[inline] - fn inverse>( - &self, - mut cs: CS, - ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - - // Karatsuba multiplication for Fp6 with the inverse: - // v0 = A.c0 * B.c0 - // v1 = A.c1 * B.c1 - // - // 1 = v0 + non_residue * v1 - // => v0 = 1 - non_residue * v1 - // - // 0 = result.c1 = (A.c0 + A.c1) * (B.c0 + B.c1) - v0 - v1 - // => v0 + v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Enforced with 2 constraints: - // A.c1 * B.c1 = v1 - // => 1 + (1 - non_residue) * v1 = (A.c0 + A.c1) * (B.c0 + B.c1) - // Reference: - // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // Constraint 1 - let v1 = self.c1.mul(cs.ns(|| "inv_constraint_1"), &inverse.c1)?; - - // Constraint 2 - let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; - let b0_plus_b1 = inverse.c0.add(cs.ns(|| "b0 + b1"), &inverse.c1)?; - - let one = Fp3::<

::Fp3Params>::one(); - let rhs = Self::mul_fp3_gadget_by_nonresidue(cs.ns(|| "nr * v1"), &v1)? - .sub(cs.ns(|| "sub v1"), &v1)? - .negate(cs.ns(|| "negate it"))? - .add_constant(cs.ns(|| "add one"), &one)?; - a0_plus_a1.mul_equals(cs.ns(|| "inv_constraint_2"), &b0_plus_b1, &rhs)?; - Ok(inverse) - } - fn mul_equals>( &self, mut cs: CS, @@ -458,17 +416,12 @@ where } fn cost_of_mul() -> usize { - 3 * as FieldGadget, ConstraintF>>::cost_of_mul( - ) + 2 * Fp3Gadget::::cost_of_mul() } fn cost_of_mul_equals() -> usize { Self::cost_of_mul() } - - fn cost_of_inv() -> usize { - unimplemented!() - } } impl PartialEq for Fp6Gadget diff --git a/r1cs-std/src/fields/fp6_3over2.rs b/r1cs-std/src/fields/fp6_3over2.rs index abb8f7b..b2635aa 100644 --- a/r1cs-std/src/fields/fp6_3over2.rs +++ b/r1cs-std/src/fields/fp6_3over2.rs @@ -8,7 +8,7 @@ use algebra::{ use core::{borrow::Borrow, marker::PhantomData}; use r1cs_core::{ConstraintSystem, ConstraintVar, SynthesisError}; -use crate::{prelude::*, Assignment, Vec}; +use crate::{prelude::*, Vec}; type Fp2Gadget = super::fp2::Fp2Gadget<

::Fp2Params, ConstraintF>; @@ -398,120 +398,113 @@ where Ok(Self::new(c0, c1, c2)) } - /// Use the Toom-Cook-3x method to compute multiplication. + /// Use the Chung-Hasan asymmetric squaring formula. + /// + /// (Devegili OhEig Scott Dahab --- Multiplication and Squaring on + /// Abstract Pairing-Friendly + /// Fields.pdf; Section 4 (CH-SQR2)) #[inline] fn square>( &self, mut cs: CS, ) -> Result { - // Uses Toom-Cook-3x multiplication from - // - // Reference: - // "Multiplication and Squaring on Pairing-Friendly Fields" - // Devegili, OhEigeartaigh, Scott, Dahab - - // v0 = a(0)^2 = a0^2 - let v0 = self.c0.square(&mut cs.ns(|| "Calc v0"))?; - - // v1 = a(1)^2 = (a0 + a1 + a2)^2 - let v1 = { - let a0_plus_a1_plus_a2 = self - .c0 - .add(cs.ns(|| "a0 + a1"), &self.c1)? - .add(cs.ns(|| "a0 + a1 + a2"), &self.c2)?; - a0_plus_a1_plus_a2.square(&mut cs.ns(|| "(a0 + a1 + a2)^2"))? - }; - - // v2 = a(−1)^2 = (a0 − a1 + a2)^2 - let v2 = { - let a0_minus_a1_plus_a2 = self - .c0 - .sub(cs.ns(|| "a0 - a1"), &self.c1)? - .add(cs.ns(|| "a0 - a2 + a2"), &self.c2)?; - a0_minus_a1_plus_a2.square(&mut cs.ns(|| "(a0 - a1 + a2)^2"))? - }; - - // v3 = a(2)^2 = (a0 + 2a1 + 4a2)^2 - let v3 = { - let a1_double = self.c1.double(cs.ns(|| "2a1"))?; - let a2_quad = self.c2.double(cs.ns(|| "2a2"))?.double(cs.ns(|| "4a2"))?; - let a0_plus_2_a1_plus_4_a2 = self - .c0 - .add(cs.ns(|| "a0 + 2a1"), &a1_double)? - .add(cs.ns(|| "a0 + 2a1 + 4a2"), &a2_quad)?; - - a0_plus_2_a1_plus_4_a2.square(&mut cs.ns(|| "(a0 + 2a1 + 4a2)^2"))? - }; - - // v4 = a(∞)^2 = a2^2 - let v4 = self.c2.square(&mut cs.ns(|| "a2^2"))?; - - let two = ::Fp::one().double(); - let six = two.double() + &two; - let mut two_and_six = [two, six]; - algebra::fields::batch_inversion(&mut two_and_six); - let (two_inverse, six_inverse) = (two_and_six[0], two_and_six[1]); - - let half_v0 = v0.mul_by_fp_constant(cs.ns(|| "half_v0"), &two_inverse)?; - let half_v1 = v1.mul_by_fp_constant(cs.ns(|| "half_v1"), &two_inverse)?; - let one_sixth_v2 = v2.mul_by_fp_constant(cs.ns(|| "one_sixth_v2"), &six_inverse)?; - let one_sixth_v3 = v3.mul_by_fp_constant(cs.ns(|| "one_sixth_v3"), &six_inverse)?; - let two_v4 = v4.double(cs.ns(|| "double_v4"))?; - - // c0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) - let c0 = { - let mut c0_cs = cs.ns(|| "c0"); - // No constraints, only get a linear combination back. - let inner = half_v0 - .sub(c0_cs.ns(|| "sub1"), &half_v1)? - .sub(c0_cs.ns(|| "sub2"), &one_sixth_v2)? - .add(c0_cs.ns(|| "add3"), &one_sixth_v3)? - .sub(c0_cs.ns(|| "sub4"), &two_v4)?; - let non_residue_times_inner = - inner.mul_by_constant(c0_cs.ns(|| "mul_by_res"), &P::NONRESIDUE)?; - v0.add(c0_cs.ns(|| "add5"), &non_residue_times_inner)? - }; - - // −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 - let c1 = { - let mut c1_cs = cs.ns(|| "c1"); - let one_third_v2 = one_sixth_v2.double(c1_cs.ns(|| "v2_by_3"))?; - let non_residue_v4 = v4.mul_by_constant(c1_cs.ns(|| "mul_by_res"), &P::NONRESIDUE)?; - - half_v0 - .negate(c1_cs.ns(|| "neg1"))? - .add(c1_cs.ns(|| "add1"), &v1)? - .sub(c1_cs.ns(|| "sub2"), &one_third_v2)? - .sub(c1_cs.ns(|| "sub3"), &one_sixth_v3)? - .add(c1_cs.ns(|| "add4"), &two_v4)? - .add(c1_cs.ns(|| "add5"), &non_residue_v4)? - }; - - // -v0 + (1/2)v1 + (1/2)v2 −v4 - let c2 = { - let mut c2_cs = cs.ns(|| "c2"); - let half_v2 = v2.mul_by_fp_constant(c2_cs.ns(|| "half_v2"), &two_inverse)?; - half_v1 - .add(c2_cs.ns(|| "add1"), &half_v2)? - .sub(c2_cs.ns(|| "sub1"), &v4)? - .sub(c2_cs.ns(|| "sub2"), &v0)? - }; + let a = self.c0.clone(); + let b = self.c1.clone(); + let c = self.c2.clone(); + + let s0 = a.square(cs.ns(|| "s0"))?; + let ab = a.mul(cs.ns(|| "ab"), &b)?; + let s1 = ab.double(cs.ns(|| "s1"))?; + let s2 = a + .sub(cs.ns(|| "a-b"), &b)? + .add(cs.ns(|| "plus c"), &c)? + .square(cs.ns(|| "s2"))?; + let s3 = b.mul(cs.ns(|| "bc"), &c)?.double(cs.ns(|| "s3"))?; + let s4 = c.square(cs.ns(|| "s4"))?; + + let c0 = Self::mul_fp2_gadget_by_nonresidue(cs.ns(|| "c0 part 1"), &s3)? + .add(cs.ns(|| "c0"), &s0)?; + + let c1 = Self::mul_fp2_gadget_by_nonresidue(cs.ns(|| "c1 part 1"), &s4)? + .add(cs.ns(|| "c1"), &s1)?; + + let c2 = s1 + .add(cs.ns(|| "c2 part1"), &s2)? + .add(cs.ns(|| "c2 part2"), &s3)? + .sub(cs.ns(|| "c2 part3"), &s0)? + .sub(cs.ns(|| "c2 part4"), &s4)?; Ok(Self::new(c0, c1, c2)) } - // 18 constraints, we can probably do better but not sure it's worth it. #[inline] - fn inverse>( + fn mul_equals>( &self, mut cs: CS, - ) -> Result { - let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { - self.get_value().and_then(|val| val.inverse()).get() - })?; - let one = Self::one(cs.ns(|| "one"))?; - inverse.mul_equals(cs.ns(|| "check inverse"), &self, &one)?; - Ok(inverse) + other: &Self, + result: &Self, + ) -> Result<(), SynthesisError> { + // Karatsuba multiplication for Fp3: + // v0 = A.c0 * B.c0 + // v1 = A.c1 * B.c1 + // v2 = A.c2 * B.c2 + // result.c0 = v0 + β((a1 + a2)(b1 + b2) − v1 − v2) + // result.c1 = (a0 + a1)(b0 + b1) − v0 − v1 + βv2 + // result.c2 = (a0 + a2)(b0 + b2) − v0 + v1 − v2, + // We enforce this with six constraints: + // + // v0 = A.c0 * B.c0 + // v1 = A.c1 * B.c1 + // v2 = A.c2 * B.c2 + // + // result.c0 - v0 + \beta*(v1 + v2) = β(a1 + a2)(b1 + b2)) + // result.c1 + v0 + v1 - βv2 = (a0 + a1)(b0 + b1) + // result.c2 + v0 - v1 + v2 = (a0 + a2)(b0 + b2) + // Reference: + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Devegili, OhEigeartaigh, Scott, Dahab + // + // This implementation adapted from + // https://github.com/ZencashOfficial/ginger-lib/blob/development/r1cs/gadgets/std/src/fields/fp3.rs + let v0 = self.c0.mul(cs.ns(|| "v0 = a0 * b0"), &other.c0)?; + let v1 = self.c1.mul(cs.ns(|| "v1 = a1 * b1"), &other.c1)?; + let v2 = self.c2.mul(cs.ns(|| "v2 = a2 * b2"), &other.c2)?; + + // Check c0 + let nr_a1_plus_a2 = self + .c1 + .add(cs.ns(|| "a1 + a2"), &self.c2)? + .mul_by_constant(cs.ns(|| "nr*(a1 + a2)"), &P::NONRESIDUE)?; + let b1_plus_b2 = other.c1.add(cs.ns(|| "b1 + b2"), &other.c2)?; + let nr_v1 = v1.mul_by_constant(cs.ns(|| "nr * v1"), &P::NONRESIDUE)?; + let nr_v2 = v2.mul_by_constant(cs.ns(|| "nr * v2"), &P::NONRESIDUE)?; + let to_check = result + .c0 + .sub(cs.ns(|| "c0 - v0"), &v0)? + .add(cs.ns(|| "c0 - v0 + nr * v1"), &nr_v1)? + .add(cs.ns(|| "c0 - v0 + nr * v1 + nr * v2"), &nr_v2)?; + nr_a1_plus_a2.mul_equals(cs.ns(|| "check c0"), &b1_plus_b2, &to_check)?; + + // Check c1 + let a0_plus_a1 = self.c0.add(cs.ns(|| "a0 + a1"), &self.c1)?; + let b0_plus_b1 = other.c0.add(cs.ns(|| "b0 + b1"), &other.c1)?; + let to_check = result + .c1 + .sub(cs.ns(|| "c1 - nr * v2"), &nr_v2)? + .add(cs.ns(|| "c1 - nr * v2 + v0"), &v0)? + .add(cs.ns(|| "c1 - nr * v2 + v0 + v1"), &v1)?; + a0_plus_a1.mul_equals(cs.ns(|| "check c1"), &b0_plus_b1, &to_check)?; + + // Check c2 + let a0_plus_a2 = self.c0.add(cs.ns(|| "a0 + a2"), &self.c2)?; + let b0_plus_b2 = other.c0.add(cs.ns(|| "b0 + b2"), &other.c2)?; + let to_check = result + .c2 + .add(cs.ns(|| "c2 + v0"), &v0)? + .sub(cs.ns(|| "c2 + v0 - v1"), &v1)? + .add(cs.ns(|| "c2 + v0 - v1 + v2"), &v2)?; + a0_plus_a2.mul_equals(cs.ns(|| "check c2"), &b0_plus_b2, &to_check)?; + Ok(()) } #[inline] @@ -704,8 +697,8 @@ where 5 * Fp2Gadget::::cost_of_mul() } - fn cost_of_inv() -> usize { - Self::cost_of_mul() + >::cost() + fn cost_of_mul_equals() -> usize { + 6 * Fp2Gadget::::cost_of_mul() } } diff --git a/r1cs-std/src/fields/mod.rs b/r1cs-std/src/fields/mod.rs index 75f617c..60e22de 100644 --- a/r1cs-std/src/fields/mod.rs +++ b/r1cs-std/src/fields/mod.rs @@ -2,7 +2,7 @@ use algebra::{fields::BitIterator, Field}; use core::fmt::Debug; use r1cs_core::{ConstraintSystem, SynthesisError}; -use crate::prelude::*; +use crate::{prelude::*, Assignment}; pub mod fp; pub mod fp12; @@ -188,7 +188,17 @@ pub trait FieldGadget: Ok(self) } - fn inverse>(&self, _: CS) -> Result; + fn inverse>( + &self, + mut cs: CS, + ) -> Result { + let one = Self::one(&mut cs.ns(|| "one"))?; + let inverse = Self::alloc(&mut cs.ns(|| "alloc inverse"), || { + self.get_value().and_then(|val| val.inverse()).get() + })?; + self.mul_equals(cs.ns(|| "check inv"), &inverse, &one)?; + Ok(inverse) + } fn frobenius_map>( &self, @@ -255,7 +265,9 @@ pub trait FieldGadget: Self::cost_of_mul() + >::cost() } - fn cost_of_inv() -> usize; + fn cost_of_inv() -> usize { + Self::cost_of_mul_equals() + } } #[cfg(test)] diff --git a/r1cs-std/src/lib.rs b/r1cs-std/src/lib.rs index a458af2..6736471 100644 --- a/r1cs-std/src/lib.rs +++ b/r1cs-std/src/lib.rs @@ -33,8 +33,8 @@ use ralloc::{collections::BTreeMap, string::String, vec::Vec}; #[cfg(feature = "std")] use std::{collections::BTreeMap, string::String, vec::Vec}; -pub mod test_constraint_system; pub mod test_constraint_counter; +pub mod test_constraint_system; pub mod bits; pub use self::bits::*; diff --git a/r1cs-std/src/test_constraint_counter.rs b/r1cs-std/src/test_constraint_counter.rs index 359f71d..0adf53e 100644 --- a/r1cs-std/src/test_constraint_counter.rs +++ b/r1cs-std/src/test_constraint_counter.rs @@ -4,16 +4,16 @@ use r1cs_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Vari /// Constraint counter for testing purposes. pub struct ConstraintCounter { - pub num_inputs: usize, - pub num_aux: usize, + pub num_inputs: usize, + pub num_aux: usize, pub num_constraints: usize, } impl ConstraintCounter { pub fn new() -> Self { Self { - num_aux: 0, - num_inputs: 0, + num_aux: 0, + num_inputs: 0, num_constraints: 0, } }