mirror of
https://github.com/enricobottazzi/zk-fhe.git
synced 2026-01-09 05:08:04 -05:00
Merge pull request #3 from yuriko627/feat-poly-reduce
add circuit full flow
This commit is contained in:
13
README.md
13
README.md
@@ -7,4 +7,15 @@ The application is not production ready and is only meant to be used for educati
|
||||
|
||||
`LOOKUP_BITS=8 cargo run --example bfv -- --name bfv -k 14 mock`
|
||||
|
||||
The input data is located in the `data` folder. This file can be generated using [rlwe-py](https://github.com/yuriko627/rlwe-py)
|
||||
The input data is located in the `data` folder. This test vector file can be generated using [rlwe-py](https://github.com/yuriko627/rlwe-py)
|
||||
|
||||
### Chips
|
||||
|
||||
`check_poly_from_distribution_chi_error` - Enforces polynomial to be sampled from the chi distribution
|
||||
`check_poly_from_distribution_chi_key` - Enforces polynomial to be sampled from the chi key
|
||||
`poly_add` - Enforces polynomial addition
|
||||
`poly_mul_equal_deg` - Enforces polynomial multiplication between polynomials of equal degree
|
||||
`poly_mul_diff_deg` - Enforces polynomial multiplication between polynomials of different degree
|
||||
`poly_scalar_mul` - Enforces scalar multiplication of a polynomial
|
||||
`poly_reduce` - Enforces reduction of polynomial coefficients by a modulus
|
||||
`poly_divide_by_cyclo` - Enforces the reduction of a polynomial by a cyclotomic polynomial
|
||||
8211
data/bfv.in
8211
data/bfv.in
File diff suppressed because it is too large
Load Diff
530
examples/bfv.rs
530
examples/bfv.rs
@@ -2,7 +2,7 @@ use std::env::var;
|
||||
use std::vec;
|
||||
|
||||
use clap::Parser;
|
||||
use halo2_base::gates::GateChip;
|
||||
use halo2_base::safe_types::GateInstructions;
|
||||
use halo2_base::safe_types::RangeChip;
|
||||
use halo2_base::safe_types::RangeInstructions;
|
||||
use halo2_base::utils::ScalarField;
|
||||
@@ -15,74 +15,79 @@ use serde::{Deserialize, Serialize};
|
||||
use zk_fhe::chips::poly_distribution::{
|
||||
check_poly_from_distribution_chi_error, check_poly_from_distribution_chi_key,
|
||||
};
|
||||
use zk_fhe::chips::poly_operations::{poly_add, poly_mul_equal_deg, poly_scalar_mul};
|
||||
use zk_fhe::chips::poly_operations::{
|
||||
poly_add, poly_divide_by_cyclo, poly_mul_equal_deg, poly_reduce, poly_scalar_mul,
|
||||
};
|
||||
|
||||
/// Circuit inputs for BFV encryption operations
|
||||
///
|
||||
/// # Type Parameters
|
||||
///
|
||||
/// * `N`: Degree of the cyclotomic polynomial `cyclo` of the polynomial ring R_q.
|
||||
/// * `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
|
||||
///
|
||||
/// * `pk0`: Public key 0 polynomial coefficients of degree N-1 [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term
|
||||
/// * `pk1`: Public key 1 polynomial coefficients of degree N-1 [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term
|
||||
/// * `m`: Plaintext polynomial of degree N-1 [a_N-1, a_N-2, ..., a_1, a_0] where a_0 is the constant term. Represents the message to be encrypted
|
||||
/// * `u`: Ephemeral key polynomial coefficients from the distribution ChiKey [a_N-1, a_N-2, ..., a_1, a_0]
|
||||
/// * `e0`: Error polynomial coefficients from the distribution ChiError [a_N-1, a_N-2, ..., a_1, a_0]
|
||||
/// * `e1`: Error polynomial coefficients from the distribution ChiError [a_N-1, a_N-2, ..., a_1, a_0]
|
||||
/// * `pk0`: Public key 0 polynomial coefficients of degree DEG-1 [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term
|
||||
/// * `pk1`: Public key 1 polynomial coefficients of degree DEG-1 [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term
|
||||
/// * `m`: Plaintext polynomial of degree DEG-1 [a_DEG-1, a_DEG-2, ..., a_1, a_0] where a_0 is the constant term. Represents the message to be encrypted
|
||||
/// * `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
|
||||
|
||||
/// # Assumptions (to be checked outside the circuit)
|
||||
///
|
||||
/// # Assumes that the following checks have been performed outside the circuit
|
||||
/// - `N` 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^N + 1)
|
||||
/// - `cyclo` must be the cyclotomic polynomial of degree `N` => x^N + 1
|
||||
/// * `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)
|
||||
|
||||
// N 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
|
||||
const N: u64 = 1024;
|
||||
const Q: u64 = (1 << 29) - 3;
|
||||
const T: u64 = 65537;
|
||||
// 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;
|
||||
const B: u64 = 18;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CircuitInput<const N: u64, const Q: u64, const T: u64, const B: u64> {
|
||||
pub pk0: Vec<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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 struct CircuitInput<const DEG: usize, const Q: u64, const T: u64, const B: u64> {
|
||||
pub pk0: Vec<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<u64>, // 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<F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
input: CircuitInput<N, Q, T, B>,
|
||||
input: CircuitInput<DEG, Q, T, B>,
|
||||
make_public: &mut Vec<AssignedValue<F>>,
|
||||
) {
|
||||
// assert that the input polynomials have the same degree and this is equal to N - 1
|
||||
assert_eq!(input.pk0.len(), N as usize);
|
||||
assert_eq!(input.pk1.len(), N as usize);
|
||||
assert_eq!(input.m.len(), N as usize);
|
||||
assert_eq!(input.u.len(), N as usize);
|
||||
assert_eq!(input.e0.len(), N as usize);
|
||||
assert_eq!(input.e1.len(), N as usize);
|
||||
assert_eq!(input.c0.len(), N as usize);
|
||||
assert_eq!(input.c1.len(), N as usize);
|
||||
// assert that the input polynomials have the same degree and this is equal to DEG - 1
|
||||
assert_eq!(input.pk0.len() - 1, DEG - 1);
|
||||
assert_eq!(input.pk1.len() - 1, DEG - 1);
|
||||
assert_eq!(input.m.len() - 1, DEG - 1);
|
||||
assert_eq!(input.u.len() - 1, DEG - 1);
|
||||
assert_eq!(input.e0.len() - 1, DEG - 1);
|
||||
assert_eq!(input.e1.len() - 1, DEG - 1);
|
||||
assert_eq!(input.c0.len() - 1, DEG - 1);
|
||||
assert_eq!(input.c1.len() - 1, DEG - 1);
|
||||
|
||||
let mut pk0 = vec![];
|
||||
let mut pk1 = vec![];
|
||||
@@ -92,8 +97,8 @@ fn bfv_encryption_circuit<F: ScalarField>(
|
||||
let mut e1 = vec![];
|
||||
|
||||
// Assign the input polynomials to the circuit
|
||||
// Using a for loop from 0 to N - 1 enforces that the assigned input polynomials have the same degree and this is equal to N - 1
|
||||
for i in 0..N as usize {
|
||||
// Using a for loop from 0 to DEG - 1 enforces that the assigned input polynomials have the same degree and this is equal to DEG - 1
|
||||
for i in 0..DEG {
|
||||
let pk0_val = F::from(input.pk0[i]);
|
||||
let pk1_val = F::from(input.pk1[i]);
|
||||
let u_val = F::from(input.u[i]);
|
||||
@@ -116,15 +121,16 @@ fn bfv_encryption_circuit<F: ScalarField>(
|
||||
e1.push(e1_assigned_value);
|
||||
}
|
||||
|
||||
assert!(pk0.len() == N as usize);
|
||||
assert!(pk1.len() == N as usize);
|
||||
assert!(u.len() == N as usize);
|
||||
assert!(m.len() == N as usize);
|
||||
assert!(e0.len() == N as usize);
|
||||
assert!(e1.len() == N as usize);
|
||||
assert!(pk0.len() - 1 == DEG - 1);
|
||||
assert!(pk1.len() - 1 == DEG - 1);
|
||||
assert!(u.len() - 1 == DEG - 1);
|
||||
assert!(m.len() - 1 == DEG - 1);
|
||||
assert!(e0.len() - 1 == DEG - 1);
|
||||
assert!(e1.len() - 1 == DEG - 1);
|
||||
|
||||
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"))
|
||||
@@ -133,145 +139,387 @@ fn bfv_encryption_circuit<F: ScalarField>(
|
||||
|
||||
let range = RangeChip::default(lookup_bits);
|
||||
|
||||
// TO DO: Assign cyclotomic polynomial `cyclo` to the circuit
|
||||
// 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
|
||||
// - the leading coefficient is 1
|
||||
// - the constant term is 1
|
||||
// - all the other coefficients are 0
|
||||
let mut cyclo = vec![];
|
||||
|
||||
/* 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 N - 1
|
||||
let leading_coefficient = F::from(1);
|
||||
let leading_coefficient_assigned_value = ctx.load_witness(leading_coefficient);
|
||||
cyclo.push(leading_coefficient_assigned_value);
|
||||
|
||||
for _i in 1..DEG {
|
||||
let cyclo_val = F::from(0);
|
||||
let cyclo_assigned_value = ctx.load_witness(cyclo_val);
|
||||
cyclo.push(cyclo_assigned_value);
|
||||
}
|
||||
|
||||
let constant_term = F::from(1);
|
||||
let constant_term_assigned_value = ctx.load_witness(constant_term);
|
||||
cyclo.push(constant_term_assigned_value);
|
||||
|
||||
assert!(cyclo.len() - 1 == DEG);
|
||||
|
||||
/* 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 N - 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
|
||||
*/
|
||||
|
||||
check_poly_from_distribution_chi_error::<{ N - 1 }, Q, B, F>(ctx, e0, &range);
|
||||
check_poly_from_distribution_chi_error::<{ N - 1 }, Q, B, F>(ctx, e1, &range);
|
||||
// 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 N - 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
|
||||
- The assignment for loop above guarantees that the degree of u is N - 1
|
||||
- `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::<{ N - 1 }, Q, F>(ctx, u.clone(), range.gate());
|
||||
check_poly_from_distribution_chi_key::<{ DEG - 1 }, Q, F>(ctx, u.clone(), range.gate());
|
||||
|
||||
/* Constraints on m
|
||||
- m must be a polynomial in the R_t ring => Coefficients must be in the [0, T) range and the degree of m must be N - 1
|
||||
/* 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:
|
||||
- Perform a range check on the coefficients of m to be in the [0, T) range
|
||||
- The assignment for loop above guarantees that the degree of m is N - 1
|
||||
- Perform a range check on the coefficients of m to be in the [0, T/2] OR [Q - T/2, Q - 1] range
|
||||
- The assignment for loop above guarantees that the degree of m is DEG - 1
|
||||
*/
|
||||
|
||||
// 1. COMPUTE C0
|
||||
// 1. COMPUTE C0 (c0 is the first ciphertext component)
|
||||
|
||||
// pk0 * u
|
||||
|
||||
// TO DO: Perform the polynomial multiplication between pk0 and u.
|
||||
// TO DO: Reduce the resulting polynomial by the cyclotomic polynomial of degree `N` => x^N + 1
|
||||
// TO DO: Further reduce the coefficients by modulo `Q`
|
||||
// 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 maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) = Q^2 - 2Q + 1.
|
||||
// Q needs to be chosen such that Q^2 - 2Q + 1 < p where p is the prime field of the circuit in order to avoid overflow during the multiplication.
|
||||
// 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
|
||||
|
||||
let pk0_u = poly_mul_equal_deg::<{ N - 1 }, F>(ctx, pk0, u, &range.gate());
|
||||
// Note: pk0_u is a polynomial in the R_q ring
|
||||
// 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 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-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
|
||||
|
||||
// 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() {
|
||||
pk0_u_trimmed.push(pk0_u[i]);
|
||||
}
|
||||
|
||||
// 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!
|
||||
|
||||
// m * delta
|
||||
|
||||
// TO DO: Perform the polynomial scalar multiplication between m and delta.
|
||||
// TO DO: Reduce the coefficients by modulo `Q`
|
||||
// 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 `N` => x^N + 1
|
||||
|
||||
// OVERFLOW ANALYSIS
|
||||
// The coefficients of m are in the range [0, T) 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) * (T-1) = QT - Q - T + 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.
|
||||
// Perform the polynomial scalar multiplication between m and delta.
|
||||
|
||||
// DEGREE ANALYSIS
|
||||
// let m_delta = poly_scalar_mul::<{ N - 1 }, F>(ctx, m, Constant(F::from(DELTA)), &gate);
|
||||
// Note: m_delta is a polynomial in the R_q ring
|
||||
// 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
|
||||
|
||||
// pk0_u + m_delta
|
||||
// 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 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.
|
||||
|
||||
// TO DO: Perform the polynomial addition between pk0_u and m_delta.
|
||||
// TO DO: 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 `N` => x^N + 1
|
||||
let m_delta =
|
||||
poly_scalar_mul::<{ DEG - 1 }, F>(ctx, m.clone(), Constant(F::from(DELTA)), range.gate());
|
||||
|
||||
// OVERFLOW ANALYSIS
|
||||
// The coefficients of pk0_u and m_delta are in the [0, Q) range according to the constraints set above.
|
||||
// The maximum value of the coffiecient of pk0_u_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.
|
||||
// m_delta is a polynomial of degree DEG - 1
|
||||
// Coefficients of m_delta are in the [0, (Q-1) * (Q/T)] range
|
||||
|
||||
// let pk0_u_plus_m_delta = poly_add::<N, F>(ctx, pk0_u, m_delta, &gate);
|
||||
// Note: pk0_u_plus_m_delta is a polynomial in the R_q ring
|
||||
// Reduce the coefficients of `m_delta` by modulo `Q`
|
||||
|
||||
// c0 = pk0_u_plus_m_delta + e0
|
||||
// get the number of bits needed to represent the value of (Q-1) * (Q/T)
|
||||
|
||||
// TO DO: Perform the polynomial addition between pk0_u_plus_m_delta and e0.
|
||||
// TO DO: 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 `N` => x^N + 1
|
||||
let binary_representation = format!("{:b}", ((Q - 1) * (Q / T)));
|
||||
let num_bits_2 = binary_representation.len();
|
||||
|
||||
// OVERFLOW ANALYSIS
|
||||
// The coefficients of pk0_u_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.
|
||||
// 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 c0 = poly_add::<N, F>(ctx, pk0_u_plus_m_delta, e0, &gate);
|
||||
// Note: c0 is a polynomial in the R_q ring
|
||||
let m_delta = poly_reduce::<{ DEG - 1 }, Q, F>(ctx, m_delta, &range, num_bits_2);
|
||||
|
||||
// 1. COMPUTE C1
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
// 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 `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.
|
||||
|
||||
// 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());
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
// c0 is a polynomial in the R_q ring!
|
||||
|
||||
// 1. COMPUTE C1 (c1 is the second ciphertext component)
|
||||
|
||||
// pk1 * u
|
||||
|
||||
// TO DO: Perform the polynomial multiplication between pk1 and u.
|
||||
// TO DO: Reduce the resulting polynomial by the cyclotomic polynomial of degree `N` => x^N + 1
|
||||
// TO DO: Further reduce the coefficients by modulo `Q`
|
||||
// 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 maximum value of the coffiecient of pk0_u is (Q-1) * (Q-1) = Q^2 - 2Q + 1.
|
||||
// If the previous condition (Q^2 - 2Q + 1 < p) is satisfied there is no risk of overflow during the multiplication.
|
||||
// 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
|
||||
|
||||
// let pk1_u = poly_mul::<{ N - 1 }, F>(ctx, pk1, u.clone(), &gate);
|
||||
// Note: pk1_u is a polynomial in the R_q ring
|
||||
// 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 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.
|
||||
|
||||
// TO DO: perform pk1_u + e1 to get c1
|
||||
let pk1_u = poly_mul_equal_deg::<{ DEG - 1 }, F>(ctx, pk1.clone(), u, range.gate());
|
||||
|
||||
// TO DO: Perform the polynomial addition between pk1_u and e1.
|
||||
// TO DO: 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 `N` => x^N + 1
|
||||
// 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
|
||||
|
||||
// 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.
|
||||
// Reduce the coefficients by modulo `Q`
|
||||
|
||||
// let c1 = poly_add::<N, F>(ctx, pk1_u, e1, &gate);
|
||||
// Note: c1 is a polynomial in the R_q ring
|
||||
// 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
|
||||
|
||||
// TO DO: Expose to the public the coefficients of c0 and c1
|
||||
// TO DO: Expose to the public pk0 and pk1
|
||||
// TO DO: Expose to the public `cyclo`
|
||||
let pk1_u = poly_reduce::<{ 2 * DEG - 2 }, Q, F>(ctx, pk1_u, &range, num_bits_1);
|
||||
|
||||
// TO DO: test that c0 and c1 computed inside the circuit are equal to the ciphertexts provided as input in the json file
|
||||
// pk1_u is a polynomial of degree (DEG - 1) * 2 = 2*DEG - 2
|
||||
// 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
|
||||
|
||||
// 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!
|
||||
|
||||
// c1 = pk1_u_trimmed + e1
|
||||
|
||||
// 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.
|
||||
|
||||
let c1 = poly_add::<{ DEG - 1 }, F>(ctx, pk1_u_trimmed, e1, range.gate());
|
||||
|
||||
// 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
|
||||
// Check outside the circuit that the remainder matches the expected one
|
||||
for i in 0..DEG {
|
||||
assert_eq!(*c0[i].value(), F::from(input.c0[i]));
|
||||
assert_eq!(*c1[i].value(), F::from(input.c1[i]));
|
||||
}
|
||||
|
||||
// Expose to the public the coefficients of c0 and c1
|
||||
for i in 0..DEG {
|
||||
make_public.push(c0[i]);
|
||||
}
|
||||
|
||||
for i in 0..DEG {
|
||||
make_public.push(c1[i]);
|
||||
}
|
||||
|
||||
// Expose to the public pk0 and pk1
|
||||
for i in 0..DEG {
|
||||
make_public.push(pk0[i]);
|
||||
}
|
||||
|
||||
for i in 0..DEG {
|
||||
make_public.push(pk1[i]);
|
||||
}
|
||||
|
||||
// Expose to the public `cyclo`
|
||||
for i in 0..DEG + 1 {
|
||||
make_public.push(cyclo[i]);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod poly_distribution;
|
||||
pub mod poly_operations;
|
||||
pub mod utils;
|
||||
|
||||
@@ -8,10 +8,12 @@ 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: u64,
|
||||
const DEG: usize,
|
||||
const Q: u64,
|
||||
const B: u64,
|
||||
F: ScalarField,
|
||||
@@ -21,7 +23,7 @@ pub fn check_poly_from_distribution_chi_error<
|
||||
range: &RangeChip<F>,
|
||||
) {
|
||||
// assert that the degree of the polynomial a is equal to DEG
|
||||
assert_eq!(a.len() - 1, DEG as usize);
|
||||
assert_eq!(a.len() - 1, DEG);
|
||||
|
||||
// The goal is to check that coeff is in the range [0, B] OR [Q-B, Q-1]
|
||||
// We split this check into two checks:
|
||||
@@ -29,33 +31,34 @@ 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
|
||||
let mut in_range_vec = Vec::with_capacity((DEG + 1) as usize);
|
||||
// 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
|
||||
let binary_representation = format!("{:b}", Q);
|
||||
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,15 +78,16 @@ 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
|
||||
pub fn check_poly_from_distribution_chi_key<const DEG: u64, const Q: u64, F: ScalarField>(
|
||||
///
|
||||
/// * 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<const DEG: usize, const Q: u64, F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
a: Vec<AssignedValue<F>>,
|
||||
gate: &GateChip<F>,
|
||||
) {
|
||||
// assert that the degree of the polynomial a is equal to DEG
|
||||
assert_eq!(a.len() - 1, DEG as usize);
|
||||
assert_eq!(a.len() - 1, DEG);
|
||||
|
||||
// In order to check that coeff is equal to either 0, 1 or q-1
|
||||
// The constraint that we want to enforce is:
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
use crate::chips::utils::{div_euclid, vec_assigned_to_vec_u64};
|
||||
use halo2_base::gates::GateChip;
|
||||
use halo2_base::gates::GateInstructions;
|
||||
use halo2_base::safe_types::RangeChip;
|
||||
use halo2_base::safe_types::RangeInstructions;
|
||||
use halo2_base::utils::ScalarField;
|
||||
use halo2_base::AssignedValue;
|
||||
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
|
||||
pub fn poly_add<const DEG: u64, F: ScalarField>(
|
||||
///
|
||||
/// * 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<const DEG: usize, F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
a: Vec<AssignedValue<F>>,
|
||||
b: Vec<AssignedValue<F>>,
|
||||
@@ -16,26 +21,28 @@ pub fn poly_add<const DEG: u64, F: ScalarField>(
|
||||
) -> Vec<AssignedValue<F>> {
|
||||
// assert that the input polynomials have the same degree and this is equal to DEG
|
||||
assert_eq!(a.len() - 1, b.len() - 1);
|
||||
assert_eq!(a.len() - 1, DEG as usize);
|
||||
assert_eq!(a.len() - 1, DEG);
|
||||
|
||||
let c: Vec<AssignedValue<F>> = a
|
||||
.iter()
|
||||
.zip(b.iter())
|
||||
.take(2 * (DEG as usize) - 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 as usize);
|
||||
assert_eq!(c.len() - 1, DEG);
|
||||
|
||||
c
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn poly_mul_equal_deg<const DEG: u64, F: ScalarField>(
|
||||
///
|
||||
/// * 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<const DEG: usize, F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
a: Vec<AssignedValue<F>>,
|
||||
b: Vec<AssignedValue<F>>,
|
||||
@@ -43,7 +50,7 @@ pub fn poly_mul_equal_deg<const DEG: u64, F: ScalarField>(
|
||||
) -> Vec<AssignedValue<F>> {
|
||||
// assert that the input polynomials have the same degree and this is equal to DEG
|
||||
assert_eq!(a.len() - 1, b.len() - 1);
|
||||
assert_eq!(a.len() - 1, DEG as usize);
|
||||
assert_eq!(a.len() - 1, DEG);
|
||||
|
||||
let mut c = vec![];
|
||||
|
||||
@@ -52,14 +59,14 @@ pub fn poly_mul_equal_deg<const DEG: u64, F: ScalarField>(
|
||||
|
||||
if i < (DEG + 1) {
|
||||
for a_idx in 0..=i {
|
||||
let a_coef = a[a_idx as usize];
|
||||
let b_coef = b[(i - a_idx) as usize];
|
||||
let a_coef = a[a_idx];
|
||||
let b_coef = b[(i - a_idx)];
|
||||
coefficient_accumaltor.push(gate.mul(ctx, a_coef, b_coef));
|
||||
}
|
||||
} else {
|
||||
for a_idx in (i - DEG)..=DEG {
|
||||
let a_coef = a[a_idx as usize];
|
||||
let b_coef = b[(i - a_idx) as usize];
|
||||
let a_coef = a[a_idx];
|
||||
let b_coef = b[(i - a_idx)];
|
||||
coefficient_accumaltor.push(gate.mul(ctx, a_coef, b_coef));
|
||||
}
|
||||
}
|
||||
@@ -72,14 +79,16 @@ pub fn poly_mul_equal_deg<const DEG: u64, F: ScalarField>(
|
||||
}
|
||||
|
||||
// assert that the product polynomial has degree 2*DEG
|
||||
assert_eq!(c.len() - 1, 2 * (DEG as usize));
|
||||
assert_eq!(c.len() - 1, 2 * DEG);
|
||||
|
||||
c
|
||||
}
|
||||
|
||||
/// 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<F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
a: Vec<AssignedValue<F>>,
|
||||
@@ -119,21 +128,243 @@ pub fn poly_mul_diff_deg<F: ScalarField>(
|
||||
}
|
||||
|
||||
/// 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 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
|
||||
pub fn poly_scalar_mul<const DEG: u64, F: ScalarField>(
|
||||
///
|
||||
/// * 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<const DEG: usize, F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
a: Vec<AssignedValue<F>>,
|
||||
b: QuantumCell<F>,
|
||||
gate: &GateChip<F>,
|
||||
) -> Vec<AssignedValue<F>> {
|
||||
// assert that the degree of the polynomial a is equal to DEG
|
||||
assert_eq!(a.len() - 1, DEG as usize);
|
||||
assert_eq!(a.len() - 1, DEG);
|
||||
|
||||
let c: Vec<AssignedValue<F>> = 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 as usize);
|
||||
assert_eq!(c.len() - 1, DEG);
|
||||
|
||||
c
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn poly_reduce<const DEG: usize, const Q: u64, F: ScalarField>(
|
||||
ctx: &mut Context<F>,
|
||||
input: Vec<AssignedValue<F>>,
|
||||
range: &RangeChip<F>,
|
||||
num_bits: usize,
|
||||
) -> Vec<AssignedValue<F>> {
|
||||
// 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]
|
||||
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);
|
||||
|
||||
rem_assigned
|
||||
}
|
||||
|
||||
/// 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 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,
|
||||
const Q: u64,
|
||||
F: ScalarField,
|
||||
>(
|
||||
ctx: &mut Context<F>,
|
||||
dividend: Vec<AssignedValue<F>>,
|
||||
divisor: Vec<AssignedValue<F>>,
|
||||
range: &RangeChip<F>,
|
||||
) -> Vec<AssignedValue<F>> {
|
||||
// Assert that degree of dividend polynomial is equal to the constant DEG_DVD
|
||||
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);
|
||||
|
||||
// long division operation performed outside the circuit
|
||||
// Need to convert the dividend and divisor into a vector of u64
|
||||
let dividend_to_u64 = vec_assigned_to_vec_u64(÷nd);
|
||||
let divisor_to_u64 = vec_assigned_to_vec_u64(&divisor);
|
||||
|
||||
let (quotient_to_u64, remainder_to_u64) =
|
||||
div_euclid::<DEG_DVD, DEG_DVS, Q>(÷nd_to_u64, &divisor_to_u64);
|
||||
|
||||
// After the division, the degree of the quotient should be equal to DEG_DVD - DEG_DVS
|
||||
assert_eq!(quotient_to_u64.len() - 1, DEG_DVD - DEG_DVS);
|
||||
|
||||
// Furthermore, the degree of the remainder must be strictly less than the degree of the divisor
|
||||
assert!(remainder_to_u64.len() - 1 < DEG_DVS);
|
||||
|
||||
// Pad the remainder with 0s to make its degree equal to DEG_DVS - 1
|
||||
let mut remainder_to_u64 = remainder_to_u64;
|
||||
while remainder_to_u64.len() - 1 < DEG_DVS - 1 {
|
||||
remainder_to_u64.push(0);
|
||||
}
|
||||
|
||||
// Now remainder must be of degree DEG_DVS - 1
|
||||
assert_eq!(remainder_to_u64.len() - 1, DEG_DVS - 1);
|
||||
|
||||
// Later we need to perform the operation remainder + prod where prod is of degree DEG_DVD
|
||||
// In order to perform the operation inside the circuit we need to pad the remainder with 0s at the beginning to make its degree equal to DEG_DVD
|
||||
let mut remainder_to_u64 = remainder_to_u64;
|
||||
while remainder_to_u64.len() - 1 < DEG_DVD {
|
||||
remainder_to_u64.insert(0, 0);
|
||||
}
|
||||
|
||||
// Now remainder must be of degree DEG_DVD
|
||||
assert_eq!(remainder_to_u64.len() - 1, DEG_DVD);
|
||||
|
||||
// Assign the quotient and remainder to the circuit
|
||||
let mut quotient = vec![];
|
||||
let mut remainder = vec![];
|
||||
|
||||
for i in 0..DEG_DVD - DEG_DVS + 1 {
|
||||
let val = F::from(quotient_to_u64[i]);
|
||||
let assigned_val = ctx.load_witness(val);
|
||||
quotient.push(assigned_val);
|
||||
}
|
||||
|
||||
for i in 0..DEG_DVD + 1 {
|
||||
let val = F::from(remainder_to_u64[i]);
|
||||
let assigned_val = ctx.load_witness(val);
|
||||
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.
|
||||
// Therefore, the coefficients of quotient have to be in the range [0, Q - 1]
|
||||
// Since the quotient is computed outside the circuit, we need to enforce this constraint
|
||||
for i in 0..(DEG_DVD - DEG_DVS + 1) {
|
||||
range.check_less_than_safe(ctx, quotient[i], Q);
|
||||
}
|
||||
|
||||
// Remainder is equal to dividend - (quotient * divisor).
|
||||
// The coefficients of dividend are in the range [0, Q - 1] by assumption.
|
||||
// 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 (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 {
|
||||
range.check_less_than_safe(ctx, remainder[i], Q);
|
||||
}
|
||||
|
||||
// check that quotient * divisor + remainder = dividend
|
||||
|
||||
// DEGREE ANALYSIS
|
||||
// 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_DVD
|
||||
// Quotient * divisor + rem is of degree DEG_DVD
|
||||
// Dividend is of degree DEG_DVD
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
// 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
|
||||
|
||||
// DEGREE ANALYSIS
|
||||
// Prod is of degree DEG_DVD
|
||||
// Remainder is of degree DEG_DVD
|
||||
// Prod + rem is of degree DEG_DVD
|
||||
|
||||
// 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::<DEG_DVD, F>(ctx, prod, remainder.clone(), range.gate());
|
||||
|
||||
// assert that the degree of sum is DEG_DVD
|
||||
assert_eq!(sum.len() - 1, DEG_DVD);
|
||||
|
||||
// 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 (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::<DEG_DVD, Q, F>(ctx, sum, range, num_bits);
|
||||
|
||||
// assert that the degree of sum_mod is DEG_DVD
|
||||
assert_eq!(sum_mod.len() - 1, DEG_DVD);
|
||||
|
||||
// Enforce that sum_mod = dividend
|
||||
for i in 0..=DEG_DVD {
|
||||
let bool = range.gate().is_equal(ctx, sum_mod[i], dividend[i]);
|
||||
range.gate().assert_is_const(ctx, &bool, &F::from(1))
|
||||
}
|
||||
|
||||
remainder
|
||||
}
|
||||
|
||||
106
src/chips/utils.rs
Normal file
106
src/chips/utils.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
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]
|
||||
/// * Assumes that coefficients of the dividend and divisor are u64 values
|
||||
pub fn div_euclid<const DEG_DVD: usize, const DEG_DVS: usize, const Q: u64>(
|
||||
dividend: &Vec<u64>,
|
||||
divisor: &Vec<u64>,
|
||||
) -> (Vec<u64>, Vec<u64>) {
|
||||
if divisor.is_empty() || divisor.iter().all(|&x| x == 0) {
|
||||
panic!("Cannot divide by a zero polynomial!");
|
||||
}
|
||||
if dividend.is_empty() || dividend.iter().all(|&x| x == 0) {
|
||||
let quotient = vec![0; DEG_DVD - DEG_DVS + 1];
|
||||
let remainder = vec![0; DEG_DVS];
|
||||
|
||||
// turn quotient and remainder into u64
|
||||
let quotient = quotient.iter().map(|&x| x as u64).collect::<Vec<u64>>();
|
||||
let remainder = remainder.iter().map(|&x| x as u64).collect::<Vec<u64>>();
|
||||
|
||||
return (quotient, remainder);
|
||||
}
|
||||
|
||||
// assert that the degree of the dividend is equal to DEG_DVD
|
||||
assert_eq!(dividend.len() - 1, DEG_DVD);
|
||||
|
||||
// assert that the degree of the divisor is equal to DEG_DVS
|
||||
assert_eq!(divisor.len() - 1, DEG_DVS);
|
||||
|
||||
// transform the dividend and divisor into a vector of i64
|
||||
let mut dividend = dividend.iter().map(|&x| x as i64).collect::<Vec<i64>>();
|
||||
let divisor = divisor.iter().map(|&x| x as i64).collect::<Vec<i64>>();
|
||||
|
||||
let mut quotient = Vec::new();
|
||||
let mut remainder = Vec::new();
|
||||
|
||||
while dividend.len() > divisor.len() - 1 {
|
||||
let leading_coefficient_ratio = dividend[0] / divisor[0];
|
||||
quotient.push(leading_coefficient_ratio);
|
||||
|
||||
for (i, coeff) in divisor.iter().enumerate() {
|
||||
let diff = dividend[i] - leading_coefficient_ratio * *coeff;
|
||||
dividend[i] = diff;
|
||||
}
|
||||
|
||||
dividend.remove(0);
|
||||
}
|
||||
|
||||
for coeff in ÷nd {
|
||||
remainder.push(*coeff);
|
||||
}
|
||||
|
||||
// Trim the leading zeros from quotient and remainder
|
||||
while !quotient.is_empty() && quotient[0] == 0 {
|
||||
quotient.remove(0);
|
||||
}
|
||||
|
||||
while !remainder.is_empty() && remainder[0] == 0 {
|
||||
remainder.remove(0);
|
||||
}
|
||||
|
||||
// Range over remainder. If any element is negative, add Q to it
|
||||
for coeff in &mut remainder {
|
||||
if *coeff < 0 {
|
||||
*coeff += Q as i64;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert remainder back to u64
|
||||
let remainder = remainder.iter().map(|&x| x as u64).collect::<Vec<u64>>();
|
||||
|
||||
// Convert quotient back to u64
|
||||
let quotient = quotient.iter().map(|&x| x as u64).collect::<Vec<u64>>();
|
||||
|
||||
// pad quotient with zeroes at the beginning to make its degree equal to DEG_DVD - DEG_DVS
|
||||
let mut quotient = quotient;
|
||||
while quotient.len() - 1 < DEG_DVD - DEG_DVS {
|
||||
quotient.insert(0, 0);
|
||||
}
|
||||
|
||||
(quotient, remainder)
|
||||
}
|
||||
|
||||
/// Convert a vector of AssignedValue to a vector of u64
|
||||
///
|
||||
/// * Assumes that each element of AssignedValue can be represented in 8 bytes
|
||||
pub fn vec_assigned_to_vec_u64<F: ScalarField>(vec: &Vec<AssignedValue<F>>) -> Vec<u64> {
|
||||
let mut vec_u64 = Vec::new();
|
||||
|
||||
for i in 0..vec.len() {
|
||||
let value_bytes_le = vec[i].value().to_bytes_le();
|
||||
|
||||
// slice value_to_bytes_le the first 8 bytes
|
||||
let value_8_bytes_le = &value_bytes_le[..8];
|
||||
let mut array_value_8_bytes_le = [0u8; 8];
|
||||
array_value_8_bytes_le.copy_from_slice(value_8_bytes_le);
|
||||
let num = u64::from_le_bytes(array_value_8_bytes_le);
|
||||
vec_u64.push(num);
|
||||
}
|
||||
vec_u64
|
||||
}
|
||||
Reference in New Issue
Block a user