diff --git a/examples/bfv.rs b/examples/bfv.rs index 4b11654..36460f3 100644 --- a/examples/bfv.rs +++ b/examples/bfv.rs @@ -2,6 +2,7 @@ use std::env::var; use std::vec; use clap::Parser; +use halo2_base::safe_types::GateInstructions; use halo2_base::safe_types::RangeChip; use halo2_base::safe_types::RangeInstructions; use halo2_base::utils::ScalarField; @@ -25,7 +26,6 @@ use zk_fhe::chips::poly_operations::{ /// * `DEG`: Degree of the cyclotomic polynomial `cyclo` of the polynomial ring R_q. /// * `Q`: Modulus of the cipher text field /// * `T`: Modulus of the plaintext field -/// * `DELTA` : Q/T rounded to the lower integer /// * `B`: Upper bound of the Gaussian distribution Chi Error. It is defined as 6 * sigma /// /// # Fields @@ -36,22 +36,27 @@ use zk_fhe::chips::poly_operations::{ /// * `u`: Ephemeral key polynomial coefficients from the distribution ChiKey [a_DEG-1, a_DEG-2, ..., a_1, a_0] /// * `e0`: Error polynomial coefficients from the distribution ChiError [a_DEG-1, a_DEG-2, ..., a_1, a_0] /// * `e1`: Error polynomial coefficients from the distribution ChiError [a_DEG-1, a_DEG-2, ..., a_1, a_0] +/// * `c0`: First ciphertext component polynomial coefficients of degree DEG-1 [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term +/// * `c1`: Second ciphertext component polynomial coefficients of degree DEG-1 [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term -/// # Assumes that the following checks have been performed outside the circuit -/// - `DEG` must be a power of 2 -/// - `Q` must be a prime number -/// - `Q` must be greater than 1. -/// - If n is the number of bits of Q, and m is the number of bits of the prime field of the circuit. n must be set such that (n * 2) + 2 < m to avoid overflow of the coefficients of the polynomials -/// - `T` must be a prime number and must be greater than 1 and less than `Q` -/// - `B` must be a positive integer -/// - `pk0` and `pk1` must be polynomials in the R_q ring. The ring R_q is defined as R_q = Z_q[x]/(x^DEG + 1) -/// - `cyclo` must be the cyclotomic polynomial of degree `DEG` => x^DEG + 1 +/// # Assumptions (to be checked outside the circuit) +/// +/// * `DEG` must be a power of 2 +/// * `Q` must be a prime number and be greater than 1. +/// * `Q` must be less than 2^64 +/// * `Q` is less than (Q-1) * (Q-1) * DEG < p where p is the prime field of the circuit +/// * `T` must be a prime number and must be greater than 1 and less than `Q` +/// * `B` must be a positive integer and must be less than `Q` +/// * `cyclo` must be the cyclotomic polynomial of degree `DEG` => x^DEG + 1 (this is a public output of the circuit) +/// * `pk0` and `pk1` must be polynomials in the R_q ring. The ring R_q is defined as R_q = Z_q[x]/(x^DEG + 1) -// DEG and Q Parameters of the BFV encryption scheme chosen according to TABLES of RECOMMENDED PARAMETERS for 128-bits security level +// For real world applications, the parameters should be chosen according to the security level required. +// DEG and Q Parameters of the BFV encryption scheme should be chosen according to TABLES of RECOMMENDED PARAMETERS for 128-bits security level // https://homomorphicencryption.org/wp-content/uploads/2018/11/HomomorphicEncryptionStandardv1.1.pdf -// B is the upper bound of the distribution Chi Error. We pick standard deviation ๐œŽ โ‰ˆ 3.2 according to the HomomorphicEncryptionStandardv1 paper. -// T has been picked according to Lattigo (https://github.com/tuneinsight/lattigo/blob/master/bfv/params.go) implementation -// As suggest by https://eprint.iacr.org/2021/204.pdf (paragraph 2) we take B = 6ฯƒerr +// B is the upper bound of the distribution Chi Error. Pick standard deviation ๐œŽ โ‰ˆ 3.2 according to the HomomorphicEncryptionStandardv1 paper. +// T should be be picked according to Lattigo (https://github.com/tuneinsight/lattigo/blob/master/bfv/params.go) implementation +// As suggest by https://eprint.iacr.org/2021/204.pdf (paragraph 2) B = 6ฯƒerr +// These are just parameters used for fast testing const DEG: usize = 4; const Q: u64 = 4637; const T: u64 = 7; @@ -59,14 +64,14 @@ const B: u64 = 18; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CircuitInput { - pub pk0: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Should live in R_q (to be checked outside the circuit) - pub pk1: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Should live in R_q (to be checked outside the circuit) - pub m: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Should in R_t (checked inside the circuit) - pub u: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Lives in R_q (checked inside the circuit) - pub e0: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Lives in R_q (checked inside the circuit) - pub e1: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Lives in R_q (checked inside the circuit) - pub c0: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. It is compared to the ciphertext c0 generated as output by the circuit - pub c1: Vec, // polynomial coefficients [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. It is compared to the ciphertext c1 generated as output by the circuit + pub pk0: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PUBLIC. Should live in R_q according to assumption + pub pk1: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PUBLIC. Should live in R_q according to assumption + pub m: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PRIVATE. Should in R_t (enforced inside the circuit) + pub u: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PRIVATE. Should live in R_q and be sampled from the distribution ChiKey (checked inside the circuit) + pub e0: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PRIVATE. Should live in R_q and be sampled from the distribution ChiError (checked inside the circuit) + pub e1: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. PRIVATE. Should live in R_q and be sampled from the distribution ChiError (checked inside the circuit) + pub c0: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. Should live in R_q. This is just a test value compared to the ciphertext c0 generated as (public) output by the circuit + pub c1: Vec, // polynomial coefficients [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. Should live in R_q. This is just a test value compared to the ciphertext c1 generated as (public) output by the circuit } fn bfv_encryption_circuit( @@ -125,6 +130,7 @@ fn bfv_encryption_circuit( const DELTA: u64 = Q / T; // Q/T rounded to the lower integer + // This is a setup necessary for halo2_lib in order to create the range chip // lookup bits must agree with the size of the lookup table, which is specified by an environmental variable let lookup_bits = var("LOOKUP_BITS") .unwrap_or_else(|_| panic!("LOOKUP_BITS not set")) @@ -134,7 +140,11 @@ fn bfv_encryption_circuit( let range = RangeChip::default(lookup_bits); // Assign the cyclotomic polynomial to the circuit -> x^DEG + 1 - // Performing the assignemnt for the index 0, using a for loop from 1 to DEG - 1, and performing the assignment for the index DEG enforces that the degree of the polynomial is DEG + // Performing the assignemnt for the index 0, using a for loop from 1 to DEG - 1, and performing the assignment for the index DEG enforces that: + // - the degree of the polynomial is DEG + // - the leading coefficient is 1 + // - the constant term is 1 + // - all the other coefficients are 0 let mut cyclo = vec![]; let leading_coefficient = F::from(1); @@ -153,36 +163,37 @@ fn bfv_encryption_circuit( assert!(cyclo.len() - 1 == DEG); - /* Constraints on e0 - - e0 must be a polynomial in the R_q ring => Coefficients must be in the [0, Q) range and the degree of e0 must be DEG - 1 + /* constraint on e0 + - e0 must be a polynomial in the R_q ring => Coefficients must be in the [0, Q-1] range and the degree of e0 must be DEG - 1 - e0 must be sampled from the distribution ChiError Approach: - `check_poly_from_distribution_chi_error` chip guarantees that the coefficients of e0 are in the range [0, b] OR [q-b, q-1] - - As this range is a subset of the [0, Q) range, the coefficients of e0 are in the [0, Q) range - - The assignment for loop above guarantees that the degree of e0 is DEG - 1 + - As this range is a subset of the [0, Q-1] range, the coefficients of e0 are guaranteed to be in the [0, Q-1] range + - The assignment for loop above enforces that the degree of e0 is DEG - 1 */ - /* Constraints on e1 + /* constraint on e1 Same as e0 */ + // Assumption for the chip is that B < Q which is satisfied by circuit assumption check_poly_from_distribution_chi_error::<{ DEG - 1 }, Q, B, F>(ctx, e0.clone(), &range); check_poly_from_distribution_chi_error::<{ DEG - 1 }, Q, B, F>(ctx, e1.clone(), &range); - /* Constraints on u - - u must be a polynomial in the R_q ring => Coefficients must be in the [0, Q) range and the degree of u must be DEG - 1 + /* constraint on u + - u must be a polynomial in the R_q ring => Coefficients must be in the [0, Q-1] range and the degree of u must be DEG - 1 - u must be sampled from the distribution ChiKey Approach: - - `check_poly_from_distribution_chi_key` chip guarantees that the coefficients of u are in the range [0, 1, Q-1] - - As this range is a subset of the [0, Q) range, the coefficients of u are in the [0, Q) range + - `check_poly_from_distribution_chi_key` chip guarantees that the coefficients of u are either 0, 1 or Q-1 + - As this range is a subset of the [0, Q-1] range, the coefficients of u are guaranteed to be in the [0, Q-1] range - The assignment for loop above guarantees that the degree of u is DEG - 1 */ check_poly_from_distribution_chi_key::<{ DEG - 1 }, Q, F>(ctx, u.clone(), range.gate()); - /* Constraints on m + /* constraint on m - m must be a polynomial in the R_t ring => Coefficients must be in the [0, T/2] OR [Q - T/2, Q - 1] range and the degree of m must be DEG - 1 Approach: @@ -190,48 +201,75 @@ fn bfv_encryption_circuit( - The assignment for loop above guarantees that the degree of m is DEG - 1 */ - // TO DO: apply constraint on the coefficients of m! - - // 1. COMPUTE C0 + // 1. COMPUTE C0 (c0 is the first ciphertext component) // pk0 * u // Perform the polynomial multiplication between pk0 and u. - // OVERFLOW ANALYSIS - // The coefficients of pk0 are in the range [0, Q) according to the check to be performed outside the circuit. - // The coefficients of u are either [0, 1, Q-1] according to the constraints set above. - // The coefficients of pk0_u are calcualted as $c_k = \sum_{i=0}^{k} pk0[i] * u[k - i]$. Where k is the index of the coefficient of pk0_u. + // DEGREE ANALYSIS + // The degree of pk0 is DEG - 1 according to the constraint set above + // The degree of u is DEG - 1 according to the constraint set above + // The degree of pk0_u is constrained to be DEG - 1 + DEG - 1 = 2*DEG - 2 according to the logic of the `poly_mul_equal_deg` chip + + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of pk0 are in the range [0, Q-1] according to the assumption of the circuit + // The coefficients of u are either 0, 1 or Q-1 according to the constraint set above. + // The coefficients of pk0_u are calculated as $c_{k} = \sum_{i=0}^{k} pk0[i] * u[k - i]$. Where k is the index of the coefficient c of pk0_u. + // For two polynomials of the same degree n, the maximum number of multiplications in the sum is for k = n. Namely for the coefficient c_n. + // The number of multiplications in the sum for the coefficient c_n is n + 1. // Given that the input polynomials are of degree DEG - 1, the maximum number of multiplications in the sum is for k = DEG - 1. - // In that case there are DEG multiplications in the sum. - // For that particular coefficient, the maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) * DEG. - // The maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) * DEG. - // Q needs to be chosen such that (Q-1) * (Q-1) * DEG < p where p is the prime field of the circuit in order to avoid overflow during the multiplication. + // In that case there are max DEG multiplications in the sum. + // It follows that the maximum value that a coefficient of pk0_u can have is (Q-1) * (Q-1) * DEG. + // Q needs to be chosen such that (Q-1) * (Q-1) * DEG < p where p is the prime field of the circuit in order to avoid overflow during the polynomial multiplication. + // (Q-1) * (Q-1) * DEG < p according to the assumption of the circuit. let pk0_u = poly_mul_equal_deg::<{ DEG - 1 }, F>(ctx, pk0.clone(), u.clone(), &range.gate()); // pk0_u is a polynomial of degree (DEG - 1) * 2 = 2*DEG - 2 // pk0_u has coefficients in the [0, (Q-1) * (Q-1) * DEG] range + // Reduce the coefficients by modulo `Q` // get the number of bits needed to represent the value of (Q-1) * (Q-1) * DEG + let binary_representation = format!("{:b}", ((Q - 1) * (Q - 1) * (DEG as u64))); let num_bits_1 = binary_representation.len(); + // The coefficients of pk0_u are in the range [0, (Q-1) * (Q-1) * DEG] according to the polynomial multiplication constraint set above. + // Therefore the coefficients of pk0_u are known to have <= `num_bits_1` bits, therefore they satisfy the assumption of the `poly_reduce` chip + let pk0_u = poly_reduce::<{ 2 * DEG - 2 }, Q, F>(ctx, pk0_u, &range, num_bits_1); // pk0_u is a polynomial of degree (DEG - 1) * 2 = 2*DEG - 2 - // pk0_u has coefficients in the [0, Q) range + // pk0_u has coefficients in the [0, Q-1] range // cyclo is a polynomial of degree DEG // Reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 to get a polynomial of degree DEG - 1 + + // Dealing with the assumption of the `poly_divide_by_cyclo` chip + // - The degree of dividend (pk0_u) is equal to (2 * DEG) - 2 according to the constraint set above + // - The coefficients of dividend are in the [0, Q-1] range according to the constraint set above + // - The divisor is a cyclotomic polynomial of degree DEG with coefficients either 0 or 1 + // - The coefficients of dividend and divisor can be expressed as u64 values as long as Q - 1 is less than 2^64 + // - Q is chosen such that (Q-1) * (2 * DEG - 2 - DEG + 1)] + Q-1 < p. Note that this is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. + let pk0_u = poly_divide_by_cyclo::<{ 2 * DEG - 2 }, DEG, Q, F>(ctx, pk0_u, cyclo.clone(), &range); // assert that the degree of pk0_u is 2*DEG - 2 + assert_eq!(pk0_u.len() - 1, 2 * DEG - 2); // But actually, the degree of pk0_u is DEG - 1, the first DEG - 1 coefficients are just zeroes - // Therefore, we need to trim the first DEG - 1 coefficients + + // Enforce that the first DEG - 1 coefficients of pk0_u are zeroes + + for i in 0..DEG - 1 { + let bool = range.gate().is_equal(ctx, pk0_u[i], Constant(F::from(0))); + range.gate().assert_is_const(ctx, &bool, &F::from(1)); + } + + // Therefore, we can safely trim the first DEG - 1 coefficients from pk0_u let mut pk0_u_trimmed = vec![]; for i in DEG - 1..pk0_u.len() { @@ -239,6 +277,7 @@ fn bfv_encryption_circuit( } // assert that the degree of pk0_u_trimmed is DEG - 1 + assert_eq!(pk0_u_trimmed.len() - 1, DEG - 1); // pk0_u_trimmed is a polynomial in the R_q ring! @@ -247,122 +286,209 @@ fn bfv_encryption_circuit( // Perform the polynomial scalar multiplication between m and delta. - // OVERFLOW ANALYSIS + // DEGREE ANALYSIS + // The degree of m is DEG - 1 according to the constraint set above + // Delta is a scalar constant + // The degree of m_delta is constrained to be DEG - 1 according to the logic of the `poly_scalar_mul` chip + + // COEFFICIENTS OVERFLOW ANALYSIS // The coefficients of m are in the range [0, T/2] OR [Q - T/2, Q - 1] according to the constaints set above. - // Delta is a constant in the range [0, Q) as it is defined as Q/T rounded to the lower integer and T < Q and T > 1. - // The maximum value of the coffiecient of m_delta is (Q-1) * (Q-1) = Q^2 - 2Q + 1. - // T has to be less than Q (check performed outside the circuit). - // If the previous condition (Q^2 - 2Q + 1 < p) is satisfied there is no risk of overflow during the scalar multiplication. + // Delta is a constant equal to Q/T (integer division) where T < Q according to the assumption of the circuit. + // The maximum value of a coefficient of m_delta is (Q-1) * (Q/T) + // If the condition (Q-1) * (Q/T) < p is satisfied there is no risk of overflow during the scalar multiplication. + // Note that this condition is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. let m_delta = poly_scalar_mul::<{ DEG - 1 }, F>(ctx, m.clone(), Constant(F::from(DELTA)), range.gate()); + // m_delta is a polynomial of degree DEG - 1 + // Coefficients of m_delta are in the [0, (Q-1) * (Q/T)] range + // Reduce the coefficients of `m_delta` by modulo `Q` + + // get the number of bits needed to represent the value of (Q-1) * (Q/T) + + let binary_representation = format!("{:b}", ((Q - 1) * (Q / T))); + let num_bits_2 = binary_representation.len(); + + // The coefficients of m_delta are in the range [0, (Q-1) * (Q/T)] according to the polynomial scalar multiplication constraint set above. + // Therefore the coefficients of m_delta are known to have <= `num_bits_2` bits, therefore they satisfy the assumption of the `poly_reduce` chip + + let m_delta = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, m_delta, &range, num_bits_2); + // Note: Scalar multiplication does not change the degree of the polynomial, therefore we do not need to reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 - let m_delta = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, m_delta, &range, num_bits_1); // m_delta is a polynomial in the R_q ring // pk0_u_trimmed + m_delta // Perform the polynomial addition between pk0_u_trimmed and m_delta. - // Note: Addition does not change the degree of the polynomial, therefore we do not need to reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 - // OVERFLOW ANALYSIS - // The coefficients of pk0_u_trimmed and m_delta are in the [0, Q) range according to the constraints set above. - // The maximum value of the coffiecient of pk0_u_trimmed_plus_m_delta is (Q-1) + (Q-1) = 2Q - 2. - // If the previous condition (Q^2 - 2Q + 1 < p) is satisfied there is no risk of overflow during the addition. + // DEGREE ANALYSIS + // The degree of pk0_u_trimmed is DEG - 1 according to the constraint set above + // The degree of m_delta is DEG - 1 according to the constraint set above + // The degree of pk0_u_trimmed_plus_m_delta is constrained to be DEG - 1 according to the logic of the `poly_add` chip + + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of pk0_u_trimmed and m_delta are in the [0, Q -1] range according to the constraint set above. + // The coefficients of m_delta are in the [0, Q -1] range according to the constraint set above. + // The maximum value of the coefficient of pk0_u_trimmed_plus_m_delta is (Q-1) + (Q-1) = 2Q - 2. + // If the condition (Q-1) + (Q-1) < p is satisfied there is no risk of overflow during the polynomial addition. + // Note that this condition is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. let pk0_u_trimmed_plus_m_delta = poly_add::<{ DEG - 1 }, F>(ctx, pk0_u_trimmed, m_delta, range.gate()); - // Reduce the coefficients of `pk0_u_trimmed_plus_m_delta` by modulo `Q` - let pk0_u_trimmed_plus_m_delta = - poly_reduce::<{ DEG - 1 }, Q, F>(ctx, pk0_u_trimmed_plus_m_delta, &range, num_bits_1); + // Reduce the coefficients of `m_delta` by modulo `Q` + // Coefficients of pk0_u_trimmed_plus_m_delta are in the [0, 2Q - 2] range + // get the number of bits needed to represent the value of 2Q - 2 + + let binary_representation = format!("{:b}", (2 * Q - 2)); + let num_bits_3 = binary_representation.len(); + + // The coefficients of pk0_u_trimmed_plus_m_delta are in the range [0, 2Q - 2] according to the polynomial addition constraint set above. + // Therefore the coefficients of m_delta are known to have <= `num_bits_3` bits, therefore they satisfy the assumption of the `poly_reduce` chip + + // Reduce the coefficients of `pk0_u_trimmed_plus_m_delta` by modulo `Q` + + let pk0_u_trimmed_plus_m_delta = + poly_reduce::<{ DEG - 1 }, Q, F>(ctx, pk0_u_trimmed_plus_m_delta, &range, num_bits_3); + + // Note: Addition does not change the degree of the polynomial, therefore we do not need to reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 // pk0_u_trimmed_plus_m_delta is a polynomial in the R_q ring // c0 = pk0_u_trimmed_plus_m_delta + e0 // Perform the polynomial addition between pk0_u_trimmed_plus_m_delta and e0. - // OVERFLOW ANALYSIS - // The coefficients of pk0_u_trimmed_plus_m_delta are in the [0, Q) range according to the constraints set above. - // The coefficients of e0 are in the range [0, b] OR [q-b, q-1] according to the constraints set above. - // The maximum value of the coffiecient of c0 is (Q-1) + (Q-1) = 2Q - 2. - // If the previous condition (Q^2 - 2Q + 1 < p) is satisfied there is no risk of overflow during the addition. + // DEGREE ANALYSIS + // The degree of pk0_u_trimmed_plus_m_delta is DEG - 1 according to the constraint set above + // The degree of e0 is DEG - 1 according to the constraint set above + // The degree of c0 is constrained to be DEG - 1 according to the logic of the `poly_add` chip + + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of pk0_u_trimmed_plus_m_delta and m_delta are in the [0, Q -1] range according to the constraint set above. + // The cofficients of e0 are in the range [0, b] OR [q-b, q-1] according to the constraint set above. + // The maximum value of the coefficient of c0 is (Q-1) + (Q-1) = 2Q - 2. + // If the condition (Q-1) + (Q-1) < p is satisfied there is no risk of overflow during the polynomial addition. + // Note that this condition is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. let c0 = poly_add::<{ DEG - 1 }, F>(ctx, pk0_u_trimmed_plus_m_delta, e0, range.gate()); - // get the number of bits needed to represent the value of 2Q - 2. - let binary_representation = format!("{:b}", 2 * Q - 2); - let num_bits_2 = binary_representation.len(); + // The coefficients of c0 are in the range [0, 2Q - 2] according to the polynomial addition constraint set above. + // Therefore the coefficients of c0 are known to have <= `num_bits_3` bits, therefore they satisfy the assumption of the `poly_reduce` chip + + // Reduce the coefficients of `pk0_u_trimmed_plus_m_delta` by modulo `Q` + + let c0 = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, c0, &range, num_bits_3); - // Reduce the coefficients by modulo `Q` // Note: Addition does not change the degree of the polynomial, therefore we do not need to reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 - let c0 = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, c0, &range, num_bits_2); + // c0 is a polynomial in the R_q ring! - // c0 is a polynomial in the R_q ring - - // 1. COMPUTE C1 + // 1. COMPUTE C1 (c1 is the second ciphertext component) // pk1 * u // Perform the polynomial multiplication between pk1 and u. - // OVERFLOW ANALYSIS - // The coefficients of pk1 are in the range [0, Q) according to the check to be performed outside the circuit. - // The coefficients of u are either [0, 1, Q-1] according to the constraints set above. - // The coefficients of pk1_u are calcualted as $c_k = \sum_{i=0}^{k} pk1[i] * u[k - i]$. Where k is the index of the coefficient of pk1_u. + // DEGREE ANALYSIS + // The degree of pk1 is DEG - 1 according to the constraint set above + // The degree of u is DEG - 1 according to the constraint set above + // The degree of pk1_u is constrained to be DEG - 1 + DEG - 1 = 2*DEG - 2 according to the logic of the `poly_mul_equal_deg` chip + + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of pk1 are in the range [0, Q-1] according to the assumption of the circuit + // The coefficients of u are either 0, 1 or Q-1 according to the constraint set above. + // The coefficients of pk1_u are calculated as $c_{k} = \sum_{i=0}^{k} pk1[i] * u[k - i]$. Where k is the index of the coefficient c of pk1_u. + // For two polynomials of the same degree n, the maximum number of multiplications in the sum is for k = n. Namely for the coefficient c_n. + // The number of multiplications in the sum for the coefficient c_n is n + 1. // Given that the input polynomials are of degree DEG - 1, the maximum number of multiplications in the sum is for k = DEG - 1. - // In that case there are DEG multiplications in the sum. - // For that particular coefficient, the maximum value of the coffiecient of pk1_u is (Q-1) * (Q-1) * DEG. - // The maximum value of the coffiecient of pk1_u is (Q-1) * (Q-1) * DEG. - // Q needs to be chosen such that (Q-1) * (Q-1) * DEG < p where p is the prime field of the circuit in order to avoid overflow during the multiplication. + // In that case there are max DEG multiplications in the sum. + // It follows that the maximum value that a coefficient of pk1_u can have is (Q-1) * (Q-1) * DEG. + // Q needs to be chosen such that (Q-1) * (Q-1) * DEG < p where p is the prime field of the circuit in order to avoid overflow during the polynomial multiplication. + // (Q-1) * (Q-1) * DEG < p according to the assumption of the circuit. + let pk1_u = poly_mul_equal_deg::<{ DEG - 1 }, F>(ctx, pk1.clone(), u, range.gate()); // pk1_u is a polynomial of degree (DEG - 1) * 2 = 2*DEG - 2 // pk1_u has coefficients in the [0, (Q-1) * (Q-1) * DEG] range + // Reduce the coefficients by modulo `Q` + + // The coefficients of pk1_u are in the range [0, (Q-1) * (Q-1) * DEG] according to the polynomial multiplication constraint set above. + // Therefore the coefficients of pk1_u are known to have <= `num_bits_1` bits, therefore they satisfy the assumption of the `poly_reduce` chip + let pk1_u = poly_reduce::<{ 2 * DEG - 2 }, Q, F>(ctx, pk1_u, &range, num_bits_1); // pk1_u is a polynomial of degree (DEG - 1) * 2 = 2*DEG - 2 - // pk1_u has coefficients in the [0, Q) range + // pk1_u has coefficients in the [0, Q-1] range // cyclo is a polynomial of degree DEG // Reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 to get a polynomial of degree DEG - 1 + + // Dealing with the assumption of the `poly_divide_by_cyclo` chip + // - The degree of dividend (pk0_1) is equal to (2 * DEG) - 2 according to the constraint set above + // - The coefficients of dividend are in the [0, Q-1] range according to the constraint set above + // - The divisor is a cyclotomic polynomial of degree DEG with coefficients either 0 or 1 + // - The coefficients of dividend and divisor can be expressed as u64 values as long as Q - 1 is less than 2^64 + // - Q is chosen such that (Q-1) * (2 * DEG - 2 - DEG + 1)] + Q-1 < p. Note that this is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. + let pk1_u = poly_divide_by_cyclo::<{ 2 * DEG - 2 }, DEG, Q, F>(ctx, pk1_u, cyclo.clone(), &range); // assert that the degree of pk1_u is 2*DEG - 2 + assert_eq!(pk1_u.len() - 1, 2 * DEG - 2); // But actually, the degree of pk1_u is DEG - 1, the first DEG - 1 coefficients are just zeroes - // Therefore, we need to trim the first DEG - 1 coefficients + + // Enforce that the first DEG - 1 coefficients of pk1_u are zeroes + + for i in 0..DEG - 1 { + let bool = range.gate().is_equal(ctx, pk1_u[i], Constant(F::from(0))); + range.gate().assert_is_const(ctx, &bool, &F::from(1)); + } + + // Therefore, we can safely trim the first DEG - 1 coefficients from pk1_u + let mut pk1_u_trimmed = vec![]; for i in DEG - 1..pk1_u.len() { pk1_u_trimmed.push(pk1_u[i]); } // assert that the degree of pk1_u_trimmed is DEG - 1 + assert_eq!(pk1_u_trimmed.len() - 1, DEG - 1); - // pk1_u_trimmed is a polynomial in the R_q ring + // pk1_u_trimmed is a polynomial in the R_q ring! - // c1 = pk1_u_trimmed + e0 + // c1 = pk1_u_trimmed + e1 - // OVERFLOW ANALYSIS - // The coefficients of pk1_u are in the [0, Q) range according to the constraints set above. - // The coefficients of e1 are in the range [0, b] OR [q-b, q-1] according to the constraints set above. - // The maximum value of the coffiecient of c1 is (Q-1) + (Q-1) = 2Q - 2. - // If the previous condition (Q^2 - 2Q + 1 < p) is satisfied there is no risk of overflow during the addition. + // Perform the polynomial addition between pk1_u_trimmed and e1. + + // DEGREE ANALYSIS + // FIX The degree of pk1_u_trimmed is DEG - 1 according to the constraint set above + // The degree of e1 is DEG - 1 according to the constraint set above + // The degree of c1 is constrained to be DEG - 1 according to the logic of the `poly_add` chip + + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of pk1_u_trimmed are in the [0, Q-1] range according to the constraint set above. + // The cofficients of e1 are in the range [0, b] OR [q-b, q-1] according to the constraint set above. + // The maximum value of the coefficient of c1 is (Q-1) + (Q-1) = 2Q - 2. + // If the condition (Q-1) + (Q-1) < p is satisfied there is no risk of overflow during the polynomial addition. + // Note that this condition is a subset of the condition (Q-1) * (Q-1) * DEG < p which is an assumption of the circuit. + + // Perform the polynomial addition between pk1_u_trimmed and e1. - // Perform the polynomial addition between pk1_u and e1. let c1 = poly_add::<{ DEG - 1 }, F>(ctx, pk1_u_trimmed, e1, range.gate()); - // Reduce the coefficients by modulo `Q` - let c1 = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, c1, &range, num_bits_2); + // The coefficients of c1 are in the range [0, 2Q - 2] according to the polynomial addition constraint set above. + // Therefore the coefficients of c1 are known to have <= `num_bits_3` bits, therefore they satisfy the assumption of the `poly_reduce` chip + + // Reduce the coefficients of `c1` by modulo `Q` + + let c1 = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, c1, &range, num_bits_3); // Note: Addition does not change the degree of the polynomial, therefore we do not need to reduce the coefficients by the cyclotomic polynomial of degree `DEG` => x^DEG + 1 - // c1 is a polynomial in the R_q ring // That that c0 and c1 computed inside the circuit are equal to the ciphertexts provided as input in the test vector json file diff --git a/src/chips/poly_distribution.rs b/src/chips/poly_distribution.rs index 3925374..3d774e7 100644 --- a/src/chips/poly_distribution.rs +++ b/src/chips/poly_distribution.rs @@ -8,8 +8,10 @@ use halo2_base::Context; use halo2_base::QuantumCell::Constant; /// Enforce that polynomial a of degree DEG is sampled from the distribution chi error -/// Namely, that the coefficients are in the range [0, B] OR [Q-B, Q-1] -/// DEG is the degree of the polynomial +/// +/// * Namely, that the coefficients are in the range [0, B] OR [Q-B, Q-1] +/// * DEG is the degree of the polynomial +/// * Assumes that B < Q pub fn check_poly_from_distribution_chi_error< const DEG: usize, const Q: u64, @@ -29,7 +31,7 @@ pub fn check_poly_from_distribution_chi_error< // - Check that coeff is in the range [Q-B, Q-1] and store the boolean result in in_partial_range_2_vec // We then perform (`in_partial_range_1` OR `in_partial_range_2`) to check that coeff is in the range [0, B] OR [Q-B, Q-1] // The result of this check is stored in the `in_range` vector. - // The bool value of `in_range` is then enforced to be true + // All the boolean values in `in_range` are then enforced to be true let mut in_range_vec = Vec::with_capacity(DEG + 1); // get the number of bits needed to represent the value of Q @@ -37,25 +39,26 @@ pub fn check_poly_from_distribution_chi_error< let q_bits = binary_representation.len(); for coeff in &a { - // First of all, enforce that coefficients are in the [0, 2^q_bits) range to satisfy the assumption of `is_less_than` chip - range.is_less_than_safe(ctx, *coeff, 1 << q_bits as u64); + // First of all, enforce that coefficient is in the [0, 2^q_bits] range + let bool = range.is_less_than_safe(ctx, *coeff, (1 << q_bits as u64) + 1); + range.gate().assert_is_const(ctx, &bool, &F::from(1)); // Check for the range [0, B] // coeff is known are known to have <= `q_bits` bits according to the constraint set above - // B + 1 is known to have <= `q_bits` bits as public constant + // B + 1 is known to have <= `q_bits` bits according to assumption of the chip // Therefore it satisfies the assumption of `is_less_than` chip let in_partial_range_1 = range.is_less_than(ctx, *coeff, Constant(F::from(B + 1)), q_bits); // Check for the range [Q-B, Q-1] // coeff is known are known to have <= `q_bits` bits according to the constraint set above - // Q - B is known to have <= `q_bits` bits as public constant + // Q - B is known to have <= `q_bits` bits according to assumption of the chip // Therefore it satisfies the assumption of `is_less_than` chip let not_in_range_lower_bound = range.is_less_than(ctx, *coeff, Constant(F::from(Q - B)), q_bits); let in_range_lower_bound = range.gate.not(ctx, not_in_range_lower_bound); // coeff is known are known to have <= `q_bits` bits according to the constraint set above - // Q is known to have <= `q_bits` bits as public constant + // Q is known to have <= `q_bits` by definition // Therefore it satisfies the assumption of `is_less_than` chip let in_range_upper_bound = range.is_less_than(ctx, *coeff, Constant(F::from(Q)), q_bits); let in_partial_range_2 = range @@ -75,8 +78,9 @@ pub fn check_poly_from_distribution_chi_error< } /// Enforce that polynomial a of degree DEG is sampled from the distribution chi key -/// Namely, that the coefficients are in the range [0, 1, Q-1]. -/// DEG is the degree of the polynomial +/// +/// * Namely, that the coefficients are in the range [0, 1, Q-1]. +/// * DEG is the degree of the polynomial pub fn check_poly_from_distribution_chi_key( ctx: &mut Context, a: Vec>, diff --git a/src/chips/poly_operations.rs b/src/chips/poly_operations.rs index da40844..2aeadf5 100644 --- a/src/chips/poly_operations.rs +++ b/src/chips/poly_operations.rs @@ -9,8 +9,10 @@ use halo2_base::Context; use halo2_base::QuantumCell; /// Build the sum of the polynomials a and b as sum of the coefficients -/// DEG is the degree of the input polynomials -/// Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// +/// * DEG is the degree of the input polynomials +/// * Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * It assumes that the coefficients are constrained such to overflow during the polynomial addition pub fn poly_add( ctx: &mut Context, a: Vec>, @@ -21,12 +23,12 @@ pub fn poly_add( assert_eq!(a.len() - 1, b.len() - 1); assert_eq!(a.len() - 1, DEG); - let c: Vec> = a - .iter() - .zip(b.iter()) - .take(2 * (DEG) - 1) - .map(|(&a, &b)| gate.add(ctx, a, b)) - .collect(); + let mut c = vec![]; + + for i in 0..=DEG { + let val = gate.add(ctx, a[i], b[i]); + c.push(val); + } // assert that the sum polynomial has degree DEG assert_eq!(c.len() - 1, DEG); @@ -35,9 +37,11 @@ pub fn poly_add( } /// Build the product of the polynomials a and b as dot product of the coefficients of a and b -/// Compared to `poly_mul_diff_deg`, this function assumes that the polynomials have the same degree and therefore optimizes the computation -/// DEG is the degree of the input polynomials -/// Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// +/// * Compared to `poly_mul_diff_deg`, this function assumes that the polynomials have the same degree and therefore optimizes the computation +/// * DEG is the degree of the input polynomials +/// * Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * It assumes that the coefficients are constrained such to overflow during the polynomial multiplication pub fn poly_mul_equal_deg( ctx: &mut Context, a: Vec>, @@ -81,8 +85,10 @@ pub fn poly_mul_equal_deg( } /// Build the product of the polynomials a and b as dot product of the coefficients of a and b -/// Compared to `poly_mul_equal_deg`, this function doesn't assume that the polynomials have the same degree. Therefore the computation is less efficient. -/// Input polynomials are parsed as a vector of assigned coefficients [a_n, a_n-1, ..., a_1, a_0] where a_0 is the constant term and n is the degree of the polynomial +/// +/// * Compared to `poly_mul_equal_deg`, this function doesn't assume that the polynomials have the same degree. Therefore the computation is less efficient. +/// * Input polynomials are parsed as a vector of assigned coefficients [a_n, a_n-1, ..., a_1, a_0] where a_0 is the constant term and n is the degree of the polynomial +/// * It assumes that the coefficients are constrained such to overflow during the polynomial multiplication pub fn poly_mul_diff_deg( ctx: &mut Context, a: Vec>, @@ -122,8 +128,10 @@ pub fn poly_mul_diff_deg( } /// Build the scalar multiplication of the polynomials a and the scalar k as scalar multiplication of the coefficients of a and k -/// DEG is the degree of the polynomial -/// Input polynomial is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// +/// * DEG is the degree of the polynomial +/// * Input polynomial is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * It assumes that the coefficients are constrained such to overflow during the scalar multiplication pub fn poly_scalar_mul( ctx: &mut Context, a: Vec>, @@ -133,7 +141,12 @@ pub fn poly_scalar_mul( // assert that the degree of the polynomial a is equal to DEG assert_eq!(a.len() - 1, DEG); - let c: Vec> = a.iter().map(|&a| gate.mul(ctx, a, b)).collect(); + let mut c = vec![]; + + for i in 0..=DEG { + let val = gate.mul(ctx, a[i], b); + c.push(val); + } // assert that the product polynomial has degree DEG assert_eq!(c.len() - 1, DEG); @@ -142,9 +155,10 @@ pub fn poly_scalar_mul( } /// Takes a polynomial represented by its coefficients in a vector and output a new polynomial reduced by applying modulo Q to each coefficient -/// DEG is the degree of the polynomial -/// Input polynomial is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term -/// It assumes that the coefficients of the input polynomial can be expressed in at most num_bits bits +/// +/// * DEG is the degree of the polynomial +/// * Input polynomial is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * It assumes that the coefficients of the input polynomial can be expressed in at most num_bits bits pub fn poly_reduce( ctx: &mut Context, input: Vec>, @@ -154,12 +168,13 @@ pub fn poly_reduce( // Assert that degree of input polynomial is equal to the constant DEG assert_eq!(input.len() - 1, DEG); + let mut rem_assigned = vec![]; + // Enforce that in_assigned[i] % Q = rem_assigned[i] - let rem_assigned: Vec> = input - .iter() - .take(2 * DEG - 1) - .map(|&x| range.div_mod(ctx, x, Q, num_bits).1) - .collect(); + for i in 0..=DEG { + let rem = range.div_mod(ctx, input[i], Q, num_bits).1; + rem_assigned.push(rem); + } // assert that the reduced polynomial has degree DEG assert_eq!(rem_assigned.len() - 1, DEG); @@ -167,15 +182,19 @@ pub fn poly_reduce( rem_assigned } -/// Takes a polynomial `divisor` represented by its coefficients in a vector +/// Takes a polynomial `divisor` represented by its coefficients in a vector. /// Takes a cyclotomic polynomial `dividend` f(x)=x^m+1 (m is a power of 2) of the form represented by its coefficients in a vector /// Output the remainder of the division of `dividend` by `dividend` as a vector of coefficients -/// DEG_DVD is the degree of the `dividend` polynomial -/// DEG_DVS is the degree of the `divisor` polynomial -/// Q is the modulus of the Ring -/// Input polynomials is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term -/// Assumes that the coefficients of `dividend` are in the range [0, Q - 1] -/// Assumes that divisor is a cyclotomic polynomial with coefficients either 0 or 1 +/// +/// * DEG_DVD is the degree of the `dividend` polynomial +/// * DEG_DVS is the degree of the `divisor` polynomial +/// * Q is the modulus of the Ring +/// * Input polynomials is parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * Assumes that the degree of dividend is equal to (2 * DEG_DVS) - 2 +/// * Assumes that the coefficients of `dividend` are in the range [0, Q - 1] +/// * Assumes that divisor is a cyclotomic polynomial with coefficients either 0 or 1 +/// * Assumes that dividend and divisor can be expressed as u64 values +/// * Assumes that Q is chosen such that (Q-1) * (DEG_DVD - DEG_DVS + 1)] + Q-1 < p where p is the prime field of the circuit in order to avoid overflow during the multiplication pub fn poly_divide_by_cyclo< const DEG_DVD: usize, const DEG_DVS: usize, @@ -191,6 +210,8 @@ pub fn poly_divide_by_cyclo< assert_eq!(dividend.len() - 1, DEG_DVD); // Assert that degree of divisor poly is equal to the constant DEG_DVS assert_eq!(divisor.len() - 1, DEG_DVS); + // Assert that degree of dividend is equal to (2 * DEG_DVS) - 2 + assert_eq!(dividend.len() - 1, (2 * DEG_DVS) - 2); // DEG_DVS must be strictly less than DEG_DVD assert!(DEG_DVS < DEG_DVD); @@ -244,6 +265,12 @@ pub fn poly_divide_by_cyclo< remainder.push(assigned_val); } + // assert that the degree of quotient is DEG_DVD - DEG_DVS + assert_eq!(quotient.len() - 1, DEG_DVD - DEG_DVS); + + // assert that the degree of remainder is DEG_DVD + assert_eq!(remainder.len() - 1, DEG_DVD); + // Quotient is obtained by dividing the coefficients of the dividend by the highest degree coefficient of divisor // The coefficients of dividend are in the range [0, Q - 1] by assumption. // The leading coefficient of divisor is 1 by assumption. @@ -258,7 +285,7 @@ pub fn poly_divide_by_cyclo< // The coefficients of quotient are in the range [0, Q - 1] by constraint set above. // The coefficients of divisior are either 0, 1 by assumption of the cyclotomic polynomial. // It follows that the coefficients of quotient * divisor are in the range [0, Q - 1] - // The remainder might have coefficients that are negative. In that case we add Q to them to make them positive. + // The remainder (as result dividend - (quotient * divisor)) might have coefficients that are negative. In that case we add Q to them to make them positive. // Therefore, the coefficients of remainder are in the range [0, Q - 1] // Since the remainder is computed outside the circuit, we need to enforce this constraint for i in 0..DEG_DVS { @@ -271,56 +298,63 @@ pub fn poly_divide_by_cyclo< // Quotient is of degree DEG_DVD - DEG_DVS // Divisor is of degree DEG_DVS // Quotient * divisor is of degree DEG_DVD - // Remainder is of degree DEG_DVS - 1 + // Remainder is of degree DEG_DVD // Quotient * divisor + rem is of degree DEG_DVD // Dividend is of degree DEG_DVD - // Perform the multiplication between quotient and divisor - // No risk of overflowing the circuit prime here when performing multiplication across coefficients + // Perform the polynomial multiplication between quotient and divisor + + // COEFFICIENTS OVERFLOW ANALYSIS // The coefficients of quotient are in the range [0, Q - 1] by constraint set above. // The coefficients of divisor are either 0, 1 by assumption. - - // We use a polynomial multiplication algorithm that does not require the input polynomials to be of the same degree - let prod = poly_mul_diff_deg(ctx, quotient, divisor, range.gate()); - - // The coefficients of Divisor are in the range [0, Q - 1] by assumption. - // The coefficients of Quotient are in the range [0, Q - 1] as per constraint set above. - // The coefficients of pk0_u are calcualted as $c_k = \sum_{i=0}^{k} pk0[i] * u[k - i]$. Where k is the index of the coefficient of pk0_u. + // The coefficients of prod are calculated as $c_{k} = \sum_{i=0}^{k} quotient[i] * divisor[k - i]$. Where k is the index of the coefficient c of prod. + // For two polynomials of differents degree n and m (where m < n), the max number of multiplication performed inside the summation is m + 1. // The quotient is of degree DEG_DVD - DEG_DVS // The divisor is of degree DEG_DVS - // The maximum number of multiplications in the sum is DEG_DVD + 1 - // For that particular coefficient, the maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) * (DEG_DVD + 1). - // The maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) * (DEG_DVD + 1). - // Q needs to be chosen such that (Q-1) * (Q-1) * (DEG_DVD + 1) < p where p is the prime field of the circuit in order to avoid overflow during the multiplication. - // Therefore, the coefficients of prod are in the range [0, (Q-1) * (Q-1) * (DEG_DVD + 1)] + // Since DEG_DVD = (2 * DEG_DVS) - 2 it follows that the degree of the divisor is greater than the degree of quotient. + // In that case there are max (degree quotient + 1) multiplications in the sum. Namely DEG_DVD - DEG_DVS + 1 multiplications. + // The maximum value of the coffiecient of prod is (Q-1) * (1) * (DEG_DVD - DEG_DVS + 1). + // Q needs to be chosen such that (Q-1) * (DEG_DVD - DEG_DVS + 1) < p where p is the prime field of the circuit in order to avoid overflow during the multiplication. + // Note that this is a subset of the assumption of the circuit + // Therefore, the coefficients of prod are in the range [0, (Q-1) * (DEG_DVD - DEG_DVS + 1)] + + // We use a polynomial multiplication algorithm that does not require the input polynomials to be of the same degree + + let prod = poly_mul_diff_deg(ctx, quotient, divisor, range.gate()); // The degree of prod is DEG_DVD assert_eq!(prod.len() - 1, DEG_DVD); // Perform the addition between prod and remainder - // The degree of prod is DEG_DVD - // The degree of remainder is DEG_DVS - 1 - // prod + remainder + // DEGREE ANALYSIS + // Prod is of degree DEG_DVD + // Remainder is of degree DEG_DVD + // Prod + rem is of degree DEG_DVD - // No risk of overflowing the circuit prime here when performing addition across coefficients - // The coefficients of prod are in the range [0, (Q-1) * (Q-1) * (DEG_DVD + 1)] by the operation above. + // COEFFICIENTS OVERFLOW ANALYSIS + // The coefficients of prod are in the range [0, (Q-1) * (DEG_DVD - DEG_DVS + 1)] by the constraint above. // The coefficients of remainder are in the range [0, Q - 1] by constraint set above. + // Therefore, the coefficients of prod + remainder are in the range [0, [0, (Q-1) * (DEG_DVD - DEG_DVS + 1)] + Q-1]. + // Q needs to be chosen such that (Q-1) * (DEG_DVD - DEG_DVS + 1)] + Q-1 < p where p is the prime field of the circuit in order to avoid overflow during the addition. + // This is true by assumption of the chip. let sum = poly_add::(ctx, prod, remainder.clone(), range.gate()); // assert that the degree of sum is DEG_DVD assert_eq!(sum.len() - 1, DEG_DVD); - // The coefficients of prod are in the range [0, (Q-1) * (Q-1) * (DEG_DVD + 1)] - // The coefficients of remainder are in the range [0, Q - 1] by constraint set above. - // Therefore, the coefficients of sum are in the range [0, (Q-1) * (Q-1) * (DEG_DVD + 1) + Q - 1]. // We can reduce the coefficients of sum modulo Q to make them in the range [0, Q - 1] - // get the number of bits needed to represent the value of 2 * (Q - 1) - let binary_representation = format!("{:b}", (Q - 1) * (Q - 1) * ((DEG_DVD as u64) + 1) + Q - 1); // Convert to binary (base-2) + // get the number of bits needed to represent the value of (Q-1) * (DEG_DVD - DEG_DVS + 1)] + Q-1 + let binary_representation = format!( + "{:b}", + (Q - 1) * (DEG_DVD as u64 - DEG_DVS as u64 + 1) + (Q - 1) + ); // Convert to binary (base-2) let num_bits = binary_representation.len(); + // The coefficients of sum are in the range [0, (Q-1) * (DEG_DVD - DEG_DVS + 1)] + Q-1] according to the polynomial addition constraint set above. + // Therefore the coefficients of sum are known to have <= `num_bits` bits, therefore they satisfy the assumption of the `poly_reduce` chip let sum_mod = poly_reduce::(ctx, sum, range, num_bits); // assert that the degree of sum_mod is DEG_DVD diff --git a/src/chips/utils.rs b/src/chips/utils.rs index 3ab2fc9..e9961a2 100644 --- a/src/chips/utils.rs +++ b/src/chips/utils.rs @@ -2,10 +2,12 @@ use halo2_base::{utils::ScalarField, AssignedValue}; /// Performs long polynomial division on two polynomials /// Returns the quotient and remainder -/// Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term -/// DEG_DVD is the degree of the dividend -/// DEG_DVS is the degree of the divisor -/// Q is the modulus of the Ring. All the coefficients will be in the range [0, Q-1] +/// +/// * Input polynomials are parsed as a vector of assigned coefficients [a_DEG, a_DEG-1, ..., a_1, a_0] where a_0 is the constant term +/// * DEG_DVD is the degree of the dividend +/// * DEG_DVS is the degree of the divisor +/// * Q is the modulus of the Ring. All the coefficients will be in the range [0, Q-1] +/// * Assumes that coefficients of the dividend and divisor are u64 values pub fn div_euclid( dividend: &Vec, divisor: &Vec, @@ -85,7 +87,8 @@ pub fn div_euclid( } /// Convert a vector of AssignedValue to a vector of u64 -/// Assumes that each element of AssignedValue can be represented in 8 bytes +/// +/// * Assumes that each element of AssignedValue can be represented in 8 bytes pub fn vec_assigned_to_vec_u64(vec: &Vec>) -> Vec { let mut vec_u64 = Vec::new();