generate prime number + comments

This commit is contained in:
sam.hadow committed 2026-10-19 12:11:08 +02:00
1 parent 9346355406
commit 057ca33b6b
4 files changed
+168 -29

No files matched your search

+2 -2
View File
@@ -256,8 +256,8 @@ mod tests {
// c += c0.clone(); // c += c0.clone();
c *= c1.clone(); c *= c1.clone();
let encrypted_z = encrypt_and_evaluate(&c, &bk, kappa*2); let encrypted_z = encrypt_and_evaluate(&c, &bk, kappa);
c = bootstrap(&c, &encrypted_z, &bk.enc_s, &pk, kappa*2); c = bootstrap(&c, &encrypted_z, &bk.enc_s, &pk, kappa);
let m_boot = decrypt_bit(&c, &sk.p); let m_boot = decrypt_bit(&c, &sk.p);
assert_eq!( assert_eq!(
+10 -8
View File
@@ -1,14 +1,14 @@
use crate::utils::{generate_random_integer, generate_random_odd_integer}; use crate::utils::{generate_random_odd_integer, generate_random_prime};
use rug::{Assign, Integer}; use rug::{Assign, Integer};
pub fn encrypt_bit(bit: u8, key: &Integer, gamma: u32, rho: u32) -> Integer { pub fn encrypt_bit(bit: u8, key: &Integer, gamma: u32, rho: u32) -> Integer {
assert!(bit == 0 || bit == 1, "Only bits (0 or 1) are allowed"); assert!(bit == 0 || bit == 1, "Only bits (0 or 1) are allowed");
let q: Integer = generate_random_integer(gamma); let q: Integer = generate_random_odd_integer(gamma);
// noise // noise
let p_div_4: Integer = key.clone() / 4; let p_div_4: Integer = key.clone() / 4;
let r = std::iter::repeat_with(|| generate_random_integer(rho)) let r = std::iter::repeat_with(|| generate_random_odd_integer(rho))
.find(|r| r.clone() * 2 < p_div_4) .find(|r| r.clone() * 2 < p_div_4)
.unwrap(); .unwrap();
@@ -24,8 +24,9 @@ pub fn decrypt_bit(encrypted: &Integer, key: &Integer) -> u8 {
bit.to_u8().unwrap() bit.to_u8().unwrap()
} }
/// Secret key: a random eta-bit *prime* (Fermat-tested, see `utils::generate_random_prime`).
pub fn generate_secret_key(eta: u32) -> Integer { pub fn generate_secret_key(eta: u32) -> Integer {
generate_random_odd_integer(eta) generate_random_prime(eta)
} }
#[cfg(test)] #[cfg(test)]
@@ -36,14 +37,15 @@ mod tests {
fn test_secret_key_generation_properties() { fn test_secret_key_generation_properties() {
let eta = 1024; let eta = 1024;
let key = generate_secret_key(eta); let key = generate_secret_key(eta);
assert!(key.significant_bits() <= eta); assert_eq!(key.significant_bits(), eta);
assert!(key.significant_bits() >= eta - 1);
assert!(key.is_odd()); assert!(key.is_odd());
// independent primality cross-check (GMP Miller-Rabin)
assert_ne!(key.is_probably_prime(30), rug::integer::IsPrime::No);
} }
#[test] #[test]
fn test_encrypt_decrypt_bit() { fn test_encrypt_decrypt_bit() {
let eta = 10000; let eta = 1024;
let key = generate_secret_key(eta); let key = generate_secret_key(eta);
let bit = 0; let bit = 0;
@@ -59,7 +61,7 @@ mod tests {
#[test] #[test]
fn test_multiple_encryptions_are_different() { fn test_multiple_encryptions_are_different() {
let eta = 10000; let eta = 1024;
let key = generate_secret_key(eta); let key = generate_secret_key(eta);
let bit = 1; let bit = 1;
+28 -15
View File
@@ -13,22 +13,31 @@ pub struct PrivateKey {
pub p: Integer, pub p: Integer,
} }
/// Generates a key pair.
/// gamma: bit-length of the q_i, eta: bit-length of the (prime) secret p,
/// rho: noise bit-length, theta: total number of public elements (x_0 included, >= 2).
///
/// x_0 = q_0 * p exactly (no noise) and is the largest element; the other x_i = q_i*p + 2*r_i.
/// Reducing a ciphertext modulo x_0 therefore never changes its noise, which is what makes products of ciphertexts (and the bootstrapping circuit) reducible. With a noisy x_0, reducing a product would add k*r_0 to the noise with k ~ x_0 and break decryption.
pub fn generate_keys(gamma: u32, eta: u32, rho: u32, theta: usize) -> (PrivateKey, PublicKey) { pub fn generate_keys(gamma: u32, eta: u32, rho: u32, theta: usize) -> (PrivateKey, PublicKey) {
assert!(theta >= 2, "need at least x_0 and one more public element");
let p: Integer = generate_secret_key(eta); let p: Integer = generate_secret_key(eta);
let mut xs: Vec<Integer> = Vec::with_capacity(theta); // noisy elements x_1 .. x_{theta-1}
for _ in 0..theta { let mut xs: Vec<Integer> = (1..theta)
let xi = encrypt_bit(0u8, &p, gamma, rho); .map(|_| encrypt_bit(0u8, &p, gamma, rho))
xs.push(xi); .collect();
}
xs.sort_by(|a, b| b.cmp(a));
while xs[0].is_even() { // q_0 = (largest q_i) + 2 is odd and strictly above every other q_i, so that
xs.remove(0); // x_i < (q_i + 1) * p <= x_0 for all i.
let xi = encrypt_bit(0u8, &p, gamma, rho); let q_max = xs
xs.push(xi); .iter()
.map(|x| Integer::from(x / &p))
.max()
.expect("theta >= 2");
let q0 = q_max + 2u32;
xs.push(q0 * &p);
xs.sort_by(|a, b| b.cmp(a)); xs.sort_by(|a, b| b.cmp(a));
}
(PrivateKey { p }, PublicKey { xs }) (PrivateKey { p }, PublicKey { xs })
} }
@@ -65,22 +74,26 @@ pub fn encrypt_bit_asym(m: u8, pk: &PublicKey, rho: u32) -> Integer {
mod tests { mod tests {
use super::*; use super::*;
use crate::decrypt_bit; use crate::decrypt_bit;
use rug::integer::IsPrime;
#[test] #[test]
fn test_key_generation_properties() { fn test_key_generation_properties() {
let eta: u32 = 10000; let eta: u32 = 1024;
let theta: usize = 5; let theta: usize = 5;
let rho: u32 = 100; let rho: u32 = 100;
let (sk, pk) = generate_keys(rho, eta, rho, theta); let (sk, pk) = generate_keys(rho, eta, rho, theta);
assert_eq!(sk.p.significant_bits(), eta); assert_eq!(sk.p.significant_bits(), eta);
assert!(sk.p.is_odd()); assert!(sk.p.is_odd());
assert_ne!(sk.p.is_probably_prime(30), IsPrime::No);
assert_eq!(pk.xs.len(), theta); assert_eq!(pk.xs.len(), theta);
assert!(pk.xs[0].is_odd()); assert!(pk.xs[0].is_odd());
// x_0 is an exact multiple of p
assert!(pk.xs[0].is_divisible(&sk.p));
for xi in pk.xs.iter().skip(1) { for xi in pk.xs.iter().skip(1) {
assert!(pk.xs[0] >= *xi); assert!(pk.xs[0] > *xi);
} }
} }
@@ -88,11 +101,11 @@ mod tests {
fn test_encrypt_decrypt_bit() { fn test_encrypt_decrypt_bit() {
use rayon::prelude::*; use rayon::prelude::*;
let eta: u32 = 10000; let eta: u32 = 1024;
let theta: usize = 50; let theta: usize = 50;
let rho: u32 = 100; let rho: u32 = 100;
(0..=127).into_par_iter().for_each(|_| { (0..=31).into_par_iter().for_each(|_| {
let (sk, pk) = generate_keys(rho, eta, rho, theta); let (sk, pk) = generate_keys(rho, eta, rho, theta);
for &m in &[0u8, 1u8] { for &m in &[0u8, 1u8] {
let c = encrypt_bit_asym(m, &pk, rho); let c = encrypt_bit_asym(m, &pk, rho);
+128 -4
View File
@@ -1,9 +1,15 @@
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::Rng; use rand::Rng;
use rand::SeedableRng; use rand::SeedableRng;
use rug::rand::RandState; use rug::integer::Order;
use rug::Integer; use rug::Integer;
/// Number of random bases used by the Fermat test when generating primes.
pub const FERMAT_ROUNDS: u32 = 10;
/// Trial-division bound used to discard most composites before the (expensive) Fermat test.
const SIEVE_BOUND: u32 = 5000;
pub fn generate_random_odd_integer(num_bits: u32) -> Integer { pub fn generate_random_odd_integer(num_bits: u32) -> Integer {
let mut rng = StdRng::from_os_rng(); let mut rng = StdRng::from_os_rng();
let mut x = Integer::from(1); let mut x = Integer::from(1);
@@ -26,10 +32,128 @@ pub fn generate_random_integer(num_bits: u32) -> Integer {
x x
} }
/// Uniform random `finish - start` bit integer, shifted left by `start`.
pub fn generate_random_integer_range(start: u32, finish: u32) -> Integer { pub fn generate_random_integer_range(start: u32, finish: u32) -> Integer {
assert!(finish > start, "finish must be greater than start"); assert!(finish > start, "finish must be greater than start");
let k = finish - start; let k = finish - start;
let mut rand = RandState::new(); let mut rng = StdRng::from_os_rng();
let i = Integer::from(Integer::random_bits(k, &mut rand)) << start; let mut buf = vec![0u8; k.div_ceil(8) as usize];
i rng.fill(&mut buf[..]);
let x = Integer::from_digits(&buf, Order::Lsf).keep_bits(k);
x << start
}
/// Primes below `SIEVE_BOUND`.
fn small_primes() -> Vec<u32> {
let mut is_composite = vec![false; SIEVE_BOUND as usize + 1];
let mut primes = Vec::new();
for i in 2..=SIEVE_BOUND as usize {
if !is_composite[i] {
primes.push(i as u32);
let mut j = i * i;
while j <= SIEVE_BOUND as usize {
is_composite[j] = true;
j += i;
}
}
}
primes
}
/// Fermat primality test with `rounds` random bases.
///
/// `n` is declared (probably) prime iff `a^(n-1) = 1 (mod n)` for every random base `a` in `[2, n-2]`. Small numbers are settled exactly by trial division first.
/// (Carmichael numbers fool every coprime base, but they are rare among random large candidates.)
pub fn is_probable_prime_fermat(n: &Integer, rounds: u32) -> bool {
if *n < 2 {
return false;
}
for p in small_primes() {
if *n == p {
return true;
}
if n.is_divisible_u(p) {
return false;
}
}
// n > SIEVE_BOUND and has no small factor
let n_minus_1 = Integer::from(n - 1u32);
let range = Integer::from(n - 3u32); // bases in [2, n-2]
for _ in 0..rounds.max(1) {
let a = generate_random_integer_range(0, n.significant_bits() + 1) % &range + 2u32;
match a.pow_mod(&n_minus_1, n) {
Ok(r) if r == 1 => {}
_ => return false,
}
}
true
}
/// Random `num_bits`-bit prime (top bit set), found by rejection sampling + Fermat test.
pub fn generate_random_prime(num_bits: u32) -> Integer {
assert!(num_bits >= 2, "a prime needs at least 2 bits");
loop {
let candidate = generate_random_odd_integer(num_bits);
if is_probable_prime_fermat(&candidate, FERMAT_ROUNDS) {
return candidate;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rug::integer::IsPrime;
#[test]
fn test_range_generator_is_random() {
let a = generate_random_integer_range(0, 128);
let b = generate_random_integer_range(0, 128);
assert_ne!(a, b);
for _ in 0..50 {
let x = generate_random_integer_range(3, 40);
assert!(x < (Integer::from(1) << 40u32));
assert!(x.is_divisible_2pow(3));
}
}
#[test]
fn test_fermat_small_numbers() {
let primes = [2u32, 3, 5, 7, 11, 4999, 5003, 7919, 104729];
for p in primes {
assert!(is_probable_prime_fermat(&Integer::from(p), 5), "{p}");
}
let composites = [0u32, 1, 4, 9, 561, 1105, 1729, 4999 * 4999, 5003 * 5009];
for c in composites {
assert!(!is_probable_prime_fermat(&Integer::from(c), 5), "{c}");
}
}
#[test]
fn test_fermat_large_numbers() {
// Mersenne prime 2^127 - 1 and 2^521 - 1
for e in [127u32, 521] {
let m = (Integer::from(1) << e) - 1u32;
assert!(is_probable_prime_fermat(&m, 10));
}
// product of two large primes
let p = generate_random_prime(128);
let q = generate_random_prime(128);
assert!(!is_probable_prime_fermat(&(p * q), 10));
// 2^127 + 1 is divisible by 3
let c = (Integer::from(1) << 127u32) + 1u32;
assert!(!is_probable_prime_fermat(&c, 10));
}
#[test]
fn test_generate_random_prime() {
for bits in [16u32, 64, 256, 512] {
let p = generate_random_prime(bits);
assert_eq!(p.significant_bits(), bits);
assert!(p.is_odd());
// independent cross-check with GMP's Miller-Rabin
assert_ne!(p.is_probably_prime(30), IsPrime::No);
}
}
} }