Merge branch 'master' into pr/3

This commit is contained in:
Enrico Bottazzi
2023-10-05 08:35:15 +02:00
4 changed files with 119 additions and 56 deletions

View File

@@ -12,10 +12,10 @@ use halo2_base::QuantumCell::Constant;
use halo2_scaffold::scaffold::cmd::Cli;
use halo2_scaffold::scaffold::run;
use serde::{Deserialize, Serialize};
use zk_fhe::chips::distribution::{
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, poly_scalar_mul};
use zk_fhe::chips::poly_operations::{poly_add, poly_mul_equal_deg, poly_scalar_mul};
/// Circuit inputs for BFV encryption operations
///
@@ -29,12 +29,12 @@ use zk_fhe::chips::poly_operations::{poly_add, poly_mul, poly_scalar_mul};
///
/// # Fields
///
/// * `pk0`: Public key 0 polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i
/// * `pk1`: Public key 1 polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i
/// * `m`: Plaintext polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Represents the message to be encrypted
/// * `u`: Ephemeral key polynomial coefficients from the distribution ChiKey [a_0, a_1, ..., a_N-1]
/// * `e0`: Error polynomial coefficients from the distribution ChiError [a_0, a_1, ..., a_N-1]
/// * `e1`: Error polynomial coefficients from the distribution ChiError [a_0, a_1, ..., a_N-1]
/// * `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]
///
/// # Assumes that the following checks have been performed outside the circuit
@@ -59,14 +59,14 @@ 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_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Should live in R_q (to be checked outside the circuit)
pub pk1: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Should live in R_q (to be checked outside the circuit)
pub m: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Should in R_t (checked inside the circuit)
pub u: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Lives in R_q (checked inside the circuit)
pub e0: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Lives in R_q (checked inside the circuit)
pub e1: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. Lives in R_q (checked inside the circuit)
pub c0: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. It is compared to the ciphertext c0 generated by the circuit
pub c1: Vec<u64>, // polynomial coefficients [a_0, a_1, ..., a_N-1] where a_i is the coefficient of x^i. It is compared to the ciphertext c1 generated by the circuit
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
}
fn bfv_encryption_circuit<F: ScalarField>(
@@ -162,15 +162,13 @@ fn bfv_encryption_circuit<F: ScalarField>(
- The assignment for loop above guarantees that the degree of u is N - 1
*/
check_poly_from_distribution_chi_key::<{ N - 1 }, Q, F>(ctx, u, range.gate());
check_poly_from_distribution_chi_key::<{ N - 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
Approach:
- `reduce_poly_mod` takes as input a polynomial and a modulus and returns the polynomial reduced by the modulus.
The constraint set is that the input polynomial is equal to the output polynomial meaning that the coefficients of the input polynomial
were already in the [0, T) range
- 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
*/
@@ -188,7 +186,7 @@ fn bfv_encryption_circuit<F: ScalarField>(
// 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.
// let pk0_u = poly_mul::<{ N - 1 }, F>(ctx, pk0, u.clone(), &gate);
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
// m * delta

View File

@@ -1,2 +1,2 @@
pub mod distribution;
pub mod poly_distribution;
pub mod poly_operations;

View File

@@ -7,11 +7,11 @@ use halo2_base::AssignedValue;
use halo2_base::Context;
use halo2_base::QuantumCell::Constant;
/// Enforce that polynomial a of degree N is sampled from the distribution chi error
/// 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]
/// N is the degree of the polynomial
/// DEG is the degree of the polynomial
pub fn check_poly_from_distribution_chi_error<
const N: u64,
const DEG: u64,
const Q: u64,
const B: u64,
F: ScalarField,
@@ -20,8 +20,8 @@ pub fn check_poly_from_distribution_chi_error<
a: Vec<AssignedValue<F>>,
range: &RangeChip<F>,
) {
// assert that the degree of the polynomial a is equal to N
assert_eq!(a.len() - 1, N as usize);
// assert that the degree of the polynomial a is equal to DEG
assert_eq!(a.len() - 1, DEG as usize);
// 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:
@@ -30,19 +30,33 @@ pub fn check_poly_from_distribution_chi_error<
// 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((N + 1) as usize);
let mut in_range_vec = Vec::with_capacity((DEG + 1) as usize);
// get the number of bits required to represent the modulus q
let q_bits = (Q as f64).log2().ceil() as usize;
// 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);
// 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
// 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
// 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
// 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
.gate
@@ -60,16 +74,16 @@ pub fn check_poly_from_distribution_chi_error<
}
}
/// Enforce that polynomial a of degree N is sampled from the distribution chi key
/// 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].
/// N is the degree of the polynomial
pub fn check_poly_from_distribution_chi_key<const N: u64, const Q: u64, F: ScalarField>(
/// DEG is the degree of the polynomial
pub fn check_poly_from_distribution_chi_key<const DEG: u64, 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 N
assert_eq!(a.len() - 1, N as usize);
// assert that the degree of the polynomial a is equal to DEG
assert_eq!(a.len() - 1, DEG as usize);
// In order to check that coeff is equal to either 0, 1 or q-1
// The constraint that we want to enforce is:
@@ -96,4 +110,4 @@ pub fn check_poly_from_distribution_chi_key<const N: u64, const Q: u64, F: Scala
let bool = gate.is_zero(ctx, factor_1_2_3);
gate.assert_is_const(ctx, &bool, &F::from(1));
}
}
}

View File

@@ -9,52 +9,58 @@ use halo2_base::QuantumCell;
use zk_fhe::chips::utils::div_euclid;
/// Build the sum of the polynomials a and b as sum of the coefficients
/// N is the degree of the polynomials
pub fn poly_add<const N: 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
pub fn poly_add<const DEG: u64, F: ScalarField>(
ctx: &mut Context<F>,
a: Vec<AssignedValue<F>>,
b: Vec<AssignedValue<F>>,
gate: &GateChip<F>,
) -> Vec<AssignedValue<F>> {
// assert that the input polynomials have the same degree and this is equal to N
// 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, N as usize);
assert_eq!(a.len() - 1, DEG as usize);
let c = a
let c: Vec<AssignedValue<F>> = a
.iter()
.zip(b.iter())
.take(2 * (N as usize) - 1)
.take(2 * (DEG as usize) - 1)
.map(|(&a, &b)| gate.add(ctx, a, b))
.collect();
// assert that the sum polynomial has degree DEG
assert_eq!(c.len() - 1, DEG as usize);
c
}
/// Build the product of the polynomials a and b as dot product of the coefficients of a and b
/// N is the degree of the polynomials
pub fn poly_mul<const N: 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
pub fn poly_mul_equal_deg<const DEG: u64, F: ScalarField>(
ctx: &mut Context<F>,
a: Vec<AssignedValue<F>>,
b: Vec<AssignedValue<F>>,
gate: &GateChip<F>,
) -> Vec<AssignedValue<F>> {
// assert that the input polynomials have the same degree and this is equal to N
// 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, N as usize);
assert_eq!(a.len() - 1, DEG as usize);
let mut c = vec![];
for i in 0..(2 * N + 1) {
for i in 0..(2 * DEG + 1) {
let mut coefficient_accumaltor = vec![];
if i < (N + 1) {
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];
coefficient_accumaltor.push(gate.mul(ctx, a_coef, b_coef));
}
} else {
for a_idx in (i - N)..=N {
for a_idx in (i - DEG)..=DEG {
let a_coef = a[a_idx as usize];
let b_coef = b[(i - a_idx) as usize];
coefficient_accumaltor.push(gate.mul(ctx, a_coef, b_coef));
@@ -68,24 +74,69 @@ pub fn poly_mul<const N: u64, F: ScalarField>(
c.push(c_val);
}
// assert that the product polynomial has degree 2N
assert_eq!(c.len() - 1, 2 * (N as usize));
// assert that the product polynomial has degree 2*DEG
assert_eq!(c.len() - 1, 2 * (DEG as usize));
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
pub fn poly_mul_diff_deg<F: ScalarField>(
ctx: &mut Context<F>,
a: Vec<AssignedValue<F>>,
b: Vec<AssignedValue<F>>,
gate: &GateChip<F>,
) -> Vec<AssignedValue<F>> {
let a_deg = a.len() - 1;
let b_deg = b.len() - 1;
let c_deg = a_deg + b_deg;
let mut c = vec![];
for i in 0..=c_deg {
let mut coefficient_accumaltor = vec![];
for j in 0..=i {
if j <= a_deg && (i - j) <= b_deg {
let a_coef = a[j];
let b_coef = b[i - j];
// Update the accumulator
coefficient_accumaltor.push(gate.mul(ctx, a_coef, b_coef));
}
}
let c_val = coefficient_accumaltor
.iter()
.fold(ctx.load_witness(F::zero()), |acc, x| gate.add(ctx, acc, *x));
c.push(c_val);
}
// assert that the product polynomial has degree c_deg
assert_eq!(c.len() - 1, c_deg);
c
}
/// Build the scalar multiplication of the polynomials a and the scalar k as scalar multiplication of the coefficients of a and k
/// N is the degree of the polynomial
pub fn poly_scalar_mul<const N: u64, F: ScalarField>(
/// 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>(
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 N
assert_eq!(a.len() - 1, N as usize);
// assert that the degree of the polynomial a is equal to DEG
assert_eq!(a.len() - 1, DEG as usize);
let c = a.iter().map(|&a| gate.mul(ctx, a, b)).collect();
let c: Vec<AssignedValue<F>> = a.iter().map(|&a| gate.mul(ctx, a, b)).collect();
// assert that the product polynomial has degree DEG
assert_eq!(c.len() - 1, DEG as usize);
c
}