mirror of
https://github.com/enricobottazzi/zk-fhe.git
synced 2026-01-09 05:08:04 -05:00
Merge branch 'master' into pr/3
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod distribution;
|
||||
pub mod poly_distribution;
|
||||
pub mod poly_operations;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user