Simple interface for ElGamal and Homomorphic-ElGamal cryptosystems.
use curv::arithmetic::traits::Modulo;
use curv::arithmetic::traits::Samplable;
use curv::BigInt;
use elgamal::{
rfc7919_groups::SupportedGroups, ElGamal, ElGamalKeyPair, ElGamalPP, ElGamalPrivateKey,
ElGamalPublicKey,ExponentElGamal,ElGamalCiphertext,
};
fn main() {
// choose suitable field parameter, https://tools.ietf.org/html/rfc7919
let group_id = SupportedGroups::FFDHE2048;
let alice_pp = ElGamalPP::generate_from_rfc7919(group_id);
// create a public, secret keypair
let alice_key_pair = ElGamalKeyPair::generate(&alice_pp);
// basic en/decryption roundtrip
let message = BigInt::from(13);
let cipher = ElGamal::encrypt(&message, &alice_key_pair.pk).unwrap();
let message_tag = ElGamal::decrypt(&cipher, &alice_key_pair.sk).unwrap();
println!("basic encryption: message: {}, decrypted: {}", message, message_tag);
// homomorphic multiplication
let factor_1 = BigInt::from(13);
let factor_2 = BigInt::from(9);
let cipher = ElGamal::encrypt(&factor_1, &alice_key_pair.pk).unwrap();
let constant_cipher = ElGamal::encrypt(&factor_2, &alice_key_pair.pk).unwrap();
// homomorphic multiplication in cipher space
let product_cipher = ElGamal::mul(&cipher, &constant_cipher).unwrap();
// decrypt homomorphic product
let product_tag = ElGamal::decrypt(&product_cipher, &alice_key_pair.sk).unwrap();
println!(" factor1: {} * factor 2: {} = {}; decrypted homomorphic product: {}", factor_1, factor_2, &factor_1 * &factor_2, product_tag);
// homomorphic (pow) addition
// note to self: we now have (g^r, g^m * h^r) instead of (g^r, m * h^r)
// data set:
let data = vec![BigInt::from(1),BigInt::from(10), BigInt::from(100)];
let randomness = vec![BigInt::sample_below(&alice_pp.q), BigInt::sample_below(&alice_pp.q), BigInt::sample_below(&alice_pp.q)];
// encrypt each data point
let mut ciphers: Vec<ElGamalCiphertext> = Vec::new();
for (idx, number) in data.iter().enumerate() {
let c = ExponentElGamal::encrypt_from_predefined_randomness(&number, &alice_key_pair.pk, &randomness[idx]).unwrap();
ciphers.push(c);
}
// finally, we add the data
let n = ciphers.len();
let mut addition_cipher: ElGamalCiphertext = ExponentElGamal::add(&ciphers[0], &ciphers[1]).unwrap();
for idx in 2..n {
addition_cipher = ExponentElGamal::add(&ciphers[idx], &addition_cipher).unwrap();
}
// and now we decrypt and due to the exponentiation we end up with g^m
let c_tag = ExponentElGamal::decrypt_exp(&addition_cipher, &alice_key_pair.sk).unwrap();
// and we're super inefficiently brute-forcing g^i mod p to validate the raw sum
for i in 0..1_000_000 {
let res = BigInt::mod_pow(&alice_key_pair.pk.pp.g, &BigInt::from(i), &alice_key_pair.pk.pp.p);
if res.eq(&c_tag) {
println!("result: {}", i);
break;
}
}
}
Several tests are included:
cargo test --lib
Please note that the test for generate_safe
is not part of the default test run due to the potentially long runtime. To run the expensive tests:
cargo test --lib -- --ignored
Benchmarks are also included:
cargo bench
The benchmarks are created bycriterion.rs and the default reports include pretty cool plots, which are best with gnuplot
installed, e.g., brew install gnuplot
. The benchmark reports can found in ../target/criterion/report
and open index.html
should do.
To run the benches without plots, or with any of the other criterion.rs options, use
cargo bench --bench elgamal_benches -- --noplot
See benches/examples
for a full results set.
Feel free to reach out or join ZenGo X Telegram for discussions on code and research.