use num_bigint_dig::{BigUint, RandBigInt, RandPrime};
use num_traits::{One, Zero};
use rand::{rngs::StdRng, SeedableRng};
#[derive(Debug, Clone)]
pub struct ElGamalParams {
pub p: BigUint,
pub g: BigUint,
}
#[derive(Debug, Clone)]
pub struct ElGamalPublicKey {
pub p: BigUint,
pub g: BigUint,
pub y: BigUint,
}
#[derive(Debug)]
pub struct ElGamalPrivateKey {
pub p: BigUint,
pub g: BigUint,
pub x: BigUint,
}
#[derive(Debug)]
pub struct ElGamalKeyPair {
pub public: ElGamalPublicKey,
pub private: ElGamalPrivateKey,
}
#[derive(Debug, Clone)]
pub struct ElGamalCiphertext {
pub c1: BigUint,
pub c2: BigUint,
}
pub struct ElGamalParamsConfig {
pub prime_bits: usize,
pub seed: Option<u64>,
}
pub struct ElGamalKeyGenConfig {
pub seed: Option<u64>,
}
pub struct ElGamalEncryptConfig {
pub seed: Option<u64>,
}
impl ElGamalParams {
pub fn generate(config: &ElGamalParamsConfig) -> Self {
let mut rng = match config.seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::from_entropy(),
};
let p = rng.gen_prime(config.prime_bits);
let g = BigUint::from(2_u64);
ElGamalParams { p, g }
}
pub fn generate_keypair(&self, config: &ElGamalKeyGenConfig) -> ElGamalKeyPair {
let mut rng = match config.seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::from_entropy(),
};
let bitlen = self.p.bits();
let mut x = BigUint::zero();
while x.is_zero() {
x = rng.gen_biguint(bitlen);
if x >= self.p.clone() {
x = &x % (&self.p - BigUint::one());
}
}
let y = self.g.modpow(&x, &self.p);
let public = ElGamalPublicKey {
p: self.p.clone(),
g: self.g.clone(),
y,
};
let private = ElGamalPrivateKey {
p: self.p.clone(),
g: self.g.clone(),
x,
};
ElGamalKeyPair { public, private }
}
}
pub fn elgamal_encrypt(
public_key: &ElGamalPublicKey,
message: &BigUint,
config: &ElGamalEncryptConfig,
) -> ElGamalCiphertext {
let mut rng = match config.seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::from_entropy(),
};
let bitlen = public_key.p.bits();
let mut k = BigUint::zero();
while k.is_zero() {
k = rng.gen_biguint(bitlen);
if k >= public_key.p.clone() {
k = &k % (&public_key.p - BigUint::one());
}
}
let c1 = public_key.g.modpow(&k, &public_key.p);
let yk = public_key.y.modpow(&k, &public_key.p);
let c2 = (message * &yk) % &public_key.p;
ElGamalCiphertext { c1, c2 }
}
pub fn elgamal_decrypt(private_key: &ElGamalPrivateKey, ciphertext: &ElGamalCiphertext) -> BigUint {
let s = ciphertext.c1.modpow(&private_key.x, &private_key.p);
let s_inv = s.modpow(
&(private_key.p.clone() - BigUint::one() - BigUint::one()),
&private_key.p,
);
(&ciphertext.c2 * &s_inv) % &private_key.p
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_toy_elgamal() {
let params_cfg = ElGamalParamsConfig {
prime_bits: 256,
seed: Some(42),
};
let params = ElGamalParams::generate(¶ms_cfg);
let key_cfg = ElGamalKeyGenConfig { seed: Some(100) };
let keypair = params.generate_keypair(&key_cfg);
let message = BigUint::from(123456789_u64);
assert!(message < keypair.public.p, "Message must be < p");
let enc_cfg = ElGamalEncryptConfig { seed: Some(200) };
let ciphertext = elgamal_encrypt(&keypair.public, &message, &enc_cfg);
let recovered = elgamal_decrypt(&keypair.private, &ciphertext);
assert_eq!(recovered, message, "ElGamal encryption/decryption mismatch");
}
}