mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-08 22:28:01 -05:00
chore(zk): add a test of a proof with invalid noise in zk
This commit is contained in:
committed by
Nicolas Sarlin
parent
87dbfdcd5e
commit
68cfd1008a
@@ -136,6 +136,8 @@ impl<G: Curve> GroupElements<G> {
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum ProofSanityCheckMode {
|
||||
Panic,
|
||||
#[cfg(test)]
|
||||
Ignore,
|
||||
}
|
||||
|
||||
/// Check the preconditions of the pke proof before computing it. Panic if one of the conditions
|
||||
@@ -368,6 +370,7 @@ mod test {
|
||||
}
|
||||
|
||||
/// A randomly generated testcase of pke encryption
|
||||
#[derive(Clone)]
|
||||
pub(super) struct PkeTestcase {
|
||||
pub(super) a: Vec<i64>,
|
||||
pub(super) e1: Vec<i64>,
|
||||
@@ -376,7 +379,7 @@ mod test {
|
||||
pub(super) m: Vec<i64>,
|
||||
pub(super) b: Vec<i64>,
|
||||
pub(super) metadata: [u8; METADATA_LEN],
|
||||
s: Vec<i64>,
|
||||
pub(super) s: Vec<i64>,
|
||||
}
|
||||
|
||||
impl PkeTestcase {
|
||||
@@ -435,7 +438,7 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt using compact pke
|
||||
/// Encrypt using compact pke, the encryption is validated by doing a decryption
|
||||
pub(super) fn encrypt(&self, params: PkeTestParameters) -> PkeTestCiphertext {
|
||||
let PkeTestParameters {
|
||||
d,
|
||||
@@ -446,6 +449,47 @@ mod test {
|
||||
msbs_zero_padding_bit_count: _msbs_zero_padding_bit_count,
|
||||
} = params;
|
||||
|
||||
let ct = self.encrypt_unchecked(params);
|
||||
|
||||
// Check decryption
|
||||
let mut m_decrypted = vec![0i64; k];
|
||||
for (i, decrypted) in m_decrypted.iter_mut().enumerate() {
|
||||
let mut dot = 0i128;
|
||||
for j in 0..d {
|
||||
let c = if i + j < d {
|
||||
ct.c1[d - j - i - 1]
|
||||
} else {
|
||||
ct.c1[2 * d - j - i - 1].wrapping_neg()
|
||||
};
|
||||
|
||||
dot += self.s[d - j - 1] as i128 * c as i128;
|
||||
}
|
||||
|
||||
let q = if q == 0 { 1i128 << 64 } else { q as i128 };
|
||||
let val = ((ct.c2[i] as i128).wrapping_sub(dot)) * t as i128;
|
||||
let div = val.div_euclid(q);
|
||||
let rem = val.rem_euclid(q);
|
||||
let result = div as i64 + (rem > (q / 2)) as i64;
|
||||
let result = result.rem_euclid(params.t as i64);
|
||||
*decrypted = result;
|
||||
}
|
||||
|
||||
assert_eq!(self.m, m_decrypted);
|
||||
|
||||
ct
|
||||
}
|
||||
|
||||
/// Encrypt using compact pke, without checking that the decryption is correct
|
||||
pub(super) fn encrypt_unchecked(&self, params: PkeTestParameters) -> PkeTestCiphertext {
|
||||
let PkeTestParameters {
|
||||
d,
|
||||
k,
|
||||
B: _B,
|
||||
q,
|
||||
t,
|
||||
msbs_zero_padding_bit_count: _msbs_zero_padding_bit_count,
|
||||
} = params;
|
||||
|
||||
let delta = {
|
||||
let q = if q == 0 { 1i128 << 64 } else { q as i128 };
|
||||
// delta takes the encoding with the padding bit
|
||||
@@ -477,35 +521,17 @@ mod test {
|
||||
.wrapping_add((delta * self.m[i] as u64) as i64);
|
||||
}
|
||||
|
||||
// Check decryption
|
||||
let mut m_roundtrip = vec![0i64; k];
|
||||
for i in 0..k {
|
||||
let mut dot = 0i128;
|
||||
for j in 0..d {
|
||||
let c = if i + j < d {
|
||||
c1[d - j - i - 1]
|
||||
} else {
|
||||
c1[2 * d - j - i - 1].wrapping_neg()
|
||||
};
|
||||
|
||||
dot += self.s[d - j - 1] as i128 * c as i128;
|
||||
}
|
||||
|
||||
let q = if q == 0 { 1i128 << 64 } else { q as i128 };
|
||||
let val = ((c2[i] as i128).wrapping_sub(dot)) * t as i128;
|
||||
let div = val.div_euclid(q);
|
||||
let rem = val.rem_euclid(q);
|
||||
let result = div as i64 + (rem > (q / 2)) as i64;
|
||||
let result = result.rem_euclid(params.t as i64);
|
||||
m_roundtrip[i] = result;
|
||||
}
|
||||
|
||||
assert_eq!(self.m, m_roundtrip);
|
||||
|
||||
PkeTestCiphertext { c1, c2 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Expected result of the verification for a test
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub(super) enum VerificationResult {
|
||||
Accept,
|
||||
Reject,
|
||||
}
|
||||
|
||||
/// Return a point with coordinates (x, y) that is randomly chosen and not on the curve
|
||||
pub(super) fn point_not_on_curve<Config: short_weierstrass::SWCurveConfig>(
|
||||
rng: &mut StdRng,
|
||||
|
||||
@@ -1272,6 +1272,7 @@ mod tests {
|
||||
msbs_zero_padding_bit_count: 1,
|
||||
};
|
||||
|
||||
/// Test that the proof is rejected if we use a different value between encryption and proof
|
||||
#[test]
|
||||
fn test_pke() {
|
||||
let PkeTestParameters {
|
||||
@@ -1396,6 +1397,209 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn prove_and_verify<G: Curve>(
|
||||
testcase: &PkeTestcase,
|
||||
crs: &PublicParams<G>,
|
||||
load: ComputeLoad,
|
||||
rng: &mut StdRng,
|
||||
) -> VerificationResult {
|
||||
let ct = testcase.encrypt_unchecked(PKEV1_TEST_PARAMS);
|
||||
|
||||
let (public_commit, private_commit) = commit(
|
||||
testcase.a.clone(),
|
||||
testcase.b.clone(),
|
||||
ct.c1.clone(),
|
||||
ct.c2.clone(),
|
||||
testcase.r.clone(),
|
||||
testcase.e1.clone(),
|
||||
testcase.m.clone(),
|
||||
testcase.e2.clone(),
|
||||
crs,
|
||||
rng,
|
||||
);
|
||||
|
||||
let proof = prove_impl(
|
||||
(crs, &public_commit),
|
||||
&private_commit,
|
||||
&testcase.metadata,
|
||||
load,
|
||||
rng,
|
||||
ProofSanityCheckMode::Ignore,
|
||||
);
|
||||
|
||||
if verify(&proof, (crs, &public_commit), &testcase.metadata).is_ok() {
|
||||
VerificationResult::Accept
|
||||
} else {
|
||||
VerificationResult::Reject
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_prove_and_verify<G: Curve>(
|
||||
testcase: &PkeTestcase,
|
||||
testcase_name: &str,
|
||||
crs: &PublicParams<G>,
|
||||
rng: &mut StdRng,
|
||||
expected_result: VerificationResult,
|
||||
) {
|
||||
for load in [ComputeLoad::Proof, ComputeLoad::Verify] {
|
||||
assert_eq!(
|
||||
prove_and_verify(testcase, crs, load, rng),
|
||||
expected_result,
|
||||
"Testcase {testcase_name} failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that the proof is rejected if we use a noise outside of the bounds
|
||||
#[test]
|
||||
fn test_pke_bad_noise() {
|
||||
let PkeTestParameters {
|
||||
d,
|
||||
k,
|
||||
B,
|
||||
q,
|
||||
t,
|
||||
msbs_zero_padding_bit_count,
|
||||
} = PKEV1_TEST_PARAMS;
|
||||
|
||||
let rng = &mut StdRng::seed_from_u64(0);
|
||||
|
||||
let testcase = PkeTestcase::gen(rng, PKEV1_TEST_PARAMS);
|
||||
|
||||
type Curve = curve_api::Bls12_446;
|
||||
|
||||
// A CRS where the number of slots = the number of messages to encrypt
|
||||
let crs = crs_gen::<Curve>(d, k, B, q, t, msbs_zero_padding_bit_count, rng);
|
||||
|
||||
// A CRS where the number of slots is bigger than the number of messages to encrypt
|
||||
let big_crs_k = k + 1 + (rng.gen::<usize>() % (d - k));
|
||||
let crs_bigger_k =
|
||||
crs_gen::<Curve>(d, big_crs_k, B, q, t, msbs_zero_padding_bit_count, rng);
|
||||
|
||||
// ==== Generate test noise vectors with random coeffs and one completely out of bounds ===
|
||||
let mut testcase_bad_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
// Generate a value between B + 1 and i64::MAX to make sure that it is out of bounds
|
||||
let bad_term = (rng.gen::<u64>() % (i64::MAX as u64 - (B + 1))) + (B + 1);
|
||||
let bad_term = bad_term as i64;
|
||||
|
||||
testcase_bad_e1.e1[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
let mut testcase_bad_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_bad_e2.e2[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
// ==== Generate test noise vectors with random coeffs and one just around the bound ===
|
||||
|
||||
// Check slightly out of bound noise
|
||||
let bad_term = (B + 1) as i64;
|
||||
|
||||
let mut testcase_after_bound_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
|
||||
testcase_after_bound_e1.e1[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
let mut testcase_after_bound_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_after_bound_e2.e2[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
// Check noise right on the bound
|
||||
let bad_term = B as i64;
|
||||
|
||||
let mut testcase_on_bound_positive_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
|
||||
testcase_on_bound_positive_e1.e1[bad_idx] = bad_term;
|
||||
|
||||
let mut testcase_on_bound_positive_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_on_bound_positive_e2.e2[bad_idx] = bad_term;
|
||||
|
||||
let mut testcase_on_bound_negative_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
|
||||
testcase_on_bound_negative_e1.e1[bad_idx] = -bad_term;
|
||||
|
||||
let mut testcase_on_bound_negative_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_on_bound_negative_e2.e2[bad_idx] = -bad_term;
|
||||
|
||||
// Check just before the limit
|
||||
let bad_term = (B - 1) as i64;
|
||||
|
||||
let mut testcase_before_bound_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
|
||||
testcase_before_bound_e1.e1[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
let mut testcase_before_bound_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_before_bound_e2.e2[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
for (testcase, name, expected_result) in [
|
||||
(
|
||||
testcase_bad_e1,
|
||||
stringify!(testcase_bad_e1),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
(
|
||||
testcase_bad_e2,
|
||||
stringify!(testcase_bad_e2),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
(
|
||||
testcase_after_bound_e1,
|
||||
stringify!(testcase_after_bound_e1),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
(
|
||||
testcase_after_bound_e2,
|
||||
stringify!(testcase_after_bound_e2),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
// Upper bound is refused and lower bound is accepted
|
||||
(
|
||||
testcase_on_bound_positive_e1,
|
||||
stringify!(testcase_on_bound_positive_e1),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
(
|
||||
testcase_on_bound_positive_e2,
|
||||
stringify!(testcase_on_bound_positive_e2),
|
||||
VerificationResult::Reject,
|
||||
),
|
||||
(
|
||||
testcase_on_bound_negative_e1,
|
||||
stringify!(testcase_on_bound_negative_e1),
|
||||
VerificationResult::Accept,
|
||||
),
|
||||
(
|
||||
testcase_on_bound_negative_e2,
|
||||
stringify!(testcase_on_bound_negative_e2),
|
||||
VerificationResult::Accept,
|
||||
),
|
||||
(
|
||||
testcase_before_bound_e1,
|
||||
stringify!(testcase_before_bound_e1),
|
||||
VerificationResult::Accept,
|
||||
),
|
||||
(
|
||||
testcase_before_bound_e2,
|
||||
stringify!(testcase_before_bound_e2),
|
||||
VerificationResult::Accept,
|
||||
),
|
||||
] {
|
||||
assert_prove_and_verify(&testcase, name, &crs, rng, expected_result);
|
||||
assert_prove_and_verify(&testcase, name, &crs_bigger_k, rng, expected_result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that the proof is rejected if we don't have the padding bit set to 0
|
||||
#[test]
|
||||
fn test_pke_w_padding_fail_verify() {
|
||||
let PkeTestParameters {
|
||||
@@ -1472,6 +1676,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test compression of proofs
|
||||
#[test]
|
||||
fn test_proof_compression() {
|
||||
let PkeTestParameters {
|
||||
@@ -1524,6 +1729,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the `is_usable` method, that checks the correctness of the EC points in the proof
|
||||
#[test]
|
||||
fn test_proof_usable() {
|
||||
let PkeTestParameters {
|
||||
|
||||
@@ -506,7 +506,7 @@ pub fn compute_crs_params(
|
||||
};
|
||||
|
||||
if bound_type == Bound::GHL {
|
||||
B_bound_squared /= 10000;
|
||||
B_bound_squared = B_bound_squared.div_ceil(10000);
|
||||
}
|
||||
|
||||
// Formula is round_up(1 + B_bound.ilog2()).
|
||||
@@ -2422,6 +2422,7 @@ mod tests {
|
||||
msbs_zero_padding_bit_count: 1,
|
||||
};
|
||||
|
||||
/// Test that the proof is rejected if we use a different value between encryption and proof
|
||||
#[test]
|
||||
fn test_pke() {
|
||||
let PkeTestParameters {
|
||||
@@ -2545,6 +2546,326 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn prove_and_verify<G: Curve>(
|
||||
testcase: &PkeTestcase,
|
||||
crs: &PublicParams<G>,
|
||||
load: ComputeLoad,
|
||||
rng: &mut StdRng,
|
||||
) -> VerificationResult {
|
||||
let ct = testcase.encrypt_unchecked(PKEV2_TEST_PARAMS);
|
||||
|
||||
let (public_commit, private_commit) = commit(
|
||||
testcase.a.clone(),
|
||||
testcase.b.clone(),
|
||||
ct.c1.clone(),
|
||||
ct.c2.clone(),
|
||||
testcase.r.clone(),
|
||||
testcase.e1.clone(),
|
||||
testcase.m.clone(),
|
||||
testcase.e2.clone(),
|
||||
crs,
|
||||
rng,
|
||||
);
|
||||
|
||||
let proof = prove_impl(
|
||||
(crs, &public_commit),
|
||||
&private_commit,
|
||||
&testcase.metadata,
|
||||
load,
|
||||
rng,
|
||||
ProofSanityCheckMode::Ignore,
|
||||
);
|
||||
|
||||
if verify(&proof, (crs, &public_commit), &testcase.metadata).is_ok() {
|
||||
VerificationResult::Accept
|
||||
} else {
|
||||
VerificationResult::Reject
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_prove_and_verify<G: Curve>(
|
||||
testcase: &PkeTestcase,
|
||||
testcase_name: &str,
|
||||
crs: &PublicParams<G>,
|
||||
rng: &mut StdRng,
|
||||
expected_result: VerificationResult,
|
||||
) {
|
||||
for load in [ComputeLoad::Proof, ComputeLoad::Verify] {
|
||||
assert_eq!(
|
||||
prove_and_verify(testcase, crs, load, rng),
|
||||
expected_result,
|
||||
"Testcase {testcase_name} failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum BoundTestSlackMode {
|
||||
/// Generate test noise vectors with all coeffs at 0 except one
|
||||
// Here ||e||inf == ||e||2 so the slack is the biggest, since B is multiplied by
|
||||
// sqrt(d+k) anyways
|
||||
Max,
|
||||
/// Generate test noise vectors with random coeffs and one just around the bound
|
||||
// Here the slack should be "average"
|
||||
Avg,
|
||||
/// Generate test noise vectors with all coeffs equals to B except one at +/-1
|
||||
// Here the slack should be minimal since ||e||_2 = sqrt(d+k)*||e||_inf, which is exactly
|
||||
// what we are proving.
|
||||
Min,
|
||||
}
|
||||
|
||||
impl Display for BoundTestSlackMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BoundTestSlackMode::Min => write!(f, "min_slack"),
|
||||
BoundTestSlackMode::Avg => write!(f, "avg_slack"),
|
||||
BoundTestSlackMode::Max => write!(f, "max_slack"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TestedCoeffOffsetType {
|
||||
/// Noise term is after the bound, the proof should be refused
|
||||
After,
|
||||
/// Noise term is right on the bound, the proof should be accepted
|
||||
On,
|
||||
/// Noise term is before the bound, the proof should be accepted
|
||||
Before,
|
||||
}
|
||||
|
||||
impl Display for TestedCoeffOffsetType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TestedCoeffOffsetType::After => write!(f, "after_bound"),
|
||||
TestedCoeffOffsetType::On => write!(f, "on_bound"),
|
||||
TestedCoeffOffsetType::Before => write!(f, "before_bound"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestedCoeffOffsetType {
|
||||
fn offset(self) -> i64 {
|
||||
match self {
|
||||
TestedCoeffOffsetType::After => 1,
|
||||
TestedCoeffOffsetType::On => 0,
|
||||
TestedCoeffOffsetType::Before => -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_result(self) -> VerificationResult {
|
||||
match self {
|
||||
TestedCoeffOffsetType::After => VerificationResult::Reject,
|
||||
TestedCoeffOffsetType::On => VerificationResult::Accept,
|
||||
TestedCoeffOffsetType::Before => VerificationResult::Accept,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TestedCoeffType {
|
||||
E1,
|
||||
E2,
|
||||
}
|
||||
|
||||
impl Display for TestedCoeffType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TestedCoeffType::E1 => write!(f, "e1"),
|
||||
TestedCoeffType::E2 => write!(f, "e2"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PkeBoundTestcase {
|
||||
name: String,
|
||||
testcase: PkeTestcase,
|
||||
expected_result: VerificationResult,
|
||||
}
|
||||
|
||||
impl PkeBoundTestcase {
|
||||
fn new(
|
||||
ref_testcase: &PkeTestcase,
|
||||
B: u64,
|
||||
slack_mode: BoundTestSlackMode,
|
||||
offset_type: TestedCoeffOffsetType,
|
||||
coeff_type: TestedCoeffType,
|
||||
rng: &mut StdRng,
|
||||
) -> Self {
|
||||
let mut testcase = ref_testcase.clone();
|
||||
|
||||
let d = testcase.e1.len();
|
||||
let k = testcase.e2.len();
|
||||
|
||||
// Select a random index for the tested term
|
||||
let tested_idx = match coeff_type {
|
||||
TestedCoeffType::E1 => rng.gen::<usize>() % d,
|
||||
TestedCoeffType::E2 => rng.gen::<usize>() % k,
|
||||
};
|
||||
|
||||
// Initialize the "good" terms of the error, that are not above the bound
|
||||
match slack_mode {
|
||||
BoundTestSlackMode::Max => {
|
||||
// In this mode, all the terms are 0 except the tested one
|
||||
testcase.e1 = vec![0; d];
|
||||
testcase.e2 = vec![0; k];
|
||||
}
|
||||
BoundTestSlackMode::Avg => {
|
||||
// In this mode we keep the original random vector
|
||||
}
|
||||
BoundTestSlackMode::Min => {
|
||||
// In this mode all the terms are exactly at the bound
|
||||
let good_term = B as i64;
|
||||
testcase.e1 = (0..d)
|
||||
.map(|_| if rng.gen() { good_term } else { -good_term })
|
||||
.collect();
|
||||
testcase.e2 = (0..k)
|
||||
.map(|_| if rng.gen() { good_term } else { -good_term })
|
||||
.collect();
|
||||
}
|
||||
};
|
||||
|
||||
let B_with_slack_squared = inf_norm_bound_to_euclidean_squared(B, d + k);
|
||||
let B_with_slack = isqrt(B_with_slack_squared) as u64;
|
||||
|
||||
let bound = match slack_mode {
|
||||
// The slack is maximal, any term above B+slack should be refused
|
||||
BoundTestSlackMode::Max => B_with_slack as i64,
|
||||
// The actual accepted bound depends on the content of the test vector
|
||||
BoundTestSlackMode::Avg => {
|
||||
let e_sqr_norm = testcase
|
||||
.e1
|
||||
.iter()
|
||||
.chain(&testcase.e2)
|
||||
.map(|x| sqr(x.unsigned_abs() as u128))
|
||||
.sum::<u128>();
|
||||
|
||||
let orig_value = match coeff_type {
|
||||
TestedCoeffType::E1 => testcase.e1[tested_idx],
|
||||
TestedCoeffType::E2 => testcase.e2[tested_idx],
|
||||
};
|
||||
|
||||
let bound_squared =
|
||||
B_with_slack_squared - (e_sqr_norm - sqr(orig_value as u128));
|
||||
isqrt(bound_squared) as i64
|
||||
}
|
||||
// There is no slack effect, any term above B should be refused
|
||||
BoundTestSlackMode::Min => B as i64,
|
||||
};
|
||||
|
||||
let tested_term = bound + offset_type.offset();
|
||||
|
||||
match coeff_type {
|
||||
TestedCoeffType::E1 => testcase.e1[tested_idx] = tested_term,
|
||||
TestedCoeffType::E2 => testcase.e2[tested_idx] = tested_term,
|
||||
};
|
||||
|
||||
Self {
|
||||
name: format!("test_{slack_mode}_{offset_type}_{coeff_type}"),
|
||||
testcase,
|
||||
expected_result: offset_type.expected_result(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that the proof is rejected if we use a noise outside of the bounds, taking the slack
|
||||
/// into account
|
||||
#[test]
|
||||
fn test_pke_bad_noise() {
|
||||
let PkeTestParameters {
|
||||
d,
|
||||
k,
|
||||
B,
|
||||
q,
|
||||
t,
|
||||
msbs_zero_padding_bit_count,
|
||||
} = PKEV2_TEST_PARAMS;
|
||||
|
||||
let rng = &mut StdRng::seed_from_u64(0);
|
||||
|
||||
let testcase = PkeTestcase::gen(rng, PKEV2_TEST_PARAMS);
|
||||
|
||||
type Curve = curve_api::Bls12_446;
|
||||
|
||||
let crs = crs_gen::<Curve>(d, k, B, q, t, msbs_zero_padding_bit_count, rng);
|
||||
let crs_max_k = crs_gen::<Curve>(d, d, B, q, t, msbs_zero_padding_bit_count, rng);
|
||||
|
||||
let B_with_slack_squared = inf_norm_bound_to_euclidean_squared(B, d + k);
|
||||
let B_with_slack_upper = isqrt(B_with_slack_squared) as u64 + 1;
|
||||
|
||||
// Generate test noise vectors with random coeffs and one completely out of bounds
|
||||
|
||||
let mut testcases = Vec::new();
|
||||
let mut testcase_bad_e1 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % d;
|
||||
let bad_term =
|
||||
(rng.gen::<u64>() % (i64::MAX as u64 - B_with_slack_upper)) + B_with_slack_upper;
|
||||
let bad_term = bad_term as i64;
|
||||
|
||||
testcase_bad_e1.e1[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
testcases.push(PkeBoundTestcase {
|
||||
name: "testcase_bad_e1".to_string(),
|
||||
testcase: testcase_bad_e1,
|
||||
expected_result: VerificationResult::Reject,
|
||||
});
|
||||
|
||||
let mut testcase_bad_e2 = testcase.clone();
|
||||
let bad_idx = rng.gen::<usize>() % k;
|
||||
|
||||
testcase_bad_e2.e2[bad_idx] = if rng.gen() { bad_term } else { -bad_term };
|
||||
|
||||
testcases.push(PkeBoundTestcase {
|
||||
name: "testcase_bad_e2".to_string(),
|
||||
testcase: testcase_bad_e2,
|
||||
expected_result: VerificationResult::Reject,
|
||||
});
|
||||
|
||||
// Generate test vectors with a noise term right around the bound
|
||||
|
||||
testcases.extend(
|
||||
itertools::iproduct!(
|
||||
[
|
||||
BoundTestSlackMode::Min,
|
||||
BoundTestSlackMode::Avg,
|
||||
BoundTestSlackMode::Max
|
||||
],
|
||||
[
|
||||
TestedCoeffOffsetType::Before,
|
||||
TestedCoeffOffsetType::On,
|
||||
TestedCoeffOffsetType::After
|
||||
],
|
||||
[TestedCoeffType::E1, TestedCoeffType::E2]
|
||||
)
|
||||
.map(|(slack_mode, offset_type, coeff_type)| {
|
||||
PkeBoundTestcase::new(&testcase, B, slack_mode, offset_type, coeff_type, rng)
|
||||
}),
|
||||
);
|
||||
|
||||
for PkeBoundTestcase {
|
||||
name,
|
||||
testcase,
|
||||
expected_result,
|
||||
} in testcases
|
||||
{
|
||||
assert_prove_and_verify(
|
||||
&testcase,
|
||||
&format!("{name}_crs"),
|
||||
&crs,
|
||||
rng,
|
||||
expected_result,
|
||||
);
|
||||
assert_prove_and_verify(
|
||||
&testcase,
|
||||
&format!("{name}_crs_max_k"),
|
||||
&crs_max_k,
|
||||
rng,
|
||||
expected_result,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that the proof is rejected if we don't have the padding bit set to 0
|
||||
#[test]
|
||||
fn test_pke_w_padding_fail_verify() {
|
||||
let PkeTestParameters {
|
||||
@@ -2621,6 +2942,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test compression of proofs
|
||||
#[test]
|
||||
fn test_proof_compression() {
|
||||
let PkeTestParameters {
|
||||
@@ -2673,6 +2995,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the `is_usable` method, that checks the correctness of the EC points in the proof
|
||||
#[test]
|
||||
fn test_proof_usable() {
|
||||
let PkeTestParameters {
|
||||
|
||||
Reference in New Issue
Block a user