arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/ecgfp5/src/hashed_elgamal.rs
ecgfp5/src/hashed_elgamal.rsRaw
 1use std::array;
 2
 3use num::bigint::BigUint;
 4use plonky2::{
 5    field::goldilocks_field::GoldilocksField as F,
 6    hash::{hashing::hash_n_to_m_no_pad, poseidon::PoseidonPermutation},
 7};
 8use pod2::backends::plonky2::primitives::ec::curve::{GROUP_ORDER, Point};
 9
10use crate::ECGFP5SecretKey;
11
12pub mod circuit;
13
14/// A variant of 'hashed ElGamal' with addition in place of
15/// XORing. Assumes a message of 5 field elements and uses the
16/// Poseidon hash function outputting 5 field elements.
17pub fn hashed_elgamal_encrypt(pk: Point, nonce: &BigUint, msg: [F; 5]) -> (Point, [F; 5]) {
18    assert!(nonce < &GROUP_ORDER);
19    let nonce_times_gen = nonce * Point::generator();
20    let nonce_times_pk = nonce * pk;
21    let h = hash_n_to_m_no_pad::<F, PoseidonPermutation<_>>(&nonce_times_pk.as_fields(), 5);
22    (nonce_times_gen, array::from_fn(|i| msg[i] + h[i]))
23}
24
25/// Inverse to `hashed_elgamal_encrypt`.
26pub fn hashed_elgamal_decrypt(sk: ECGFP5SecretKey, ct: (Point, [F; 5])) -> [F; 5] {
27    let nonce_times_pk = &sk.0 * ct.0;
28    let h = hash_n_to_m_no_pad::<F, PoseidonPermutation<_>>(&nonce_times_pk.as_fields(), 5);
29    array::from_fn(|i| ct.1[i] - h[i])
30}
31
32#[cfg(test)]
33mod tests {
34    use num_bigint::RandBigInt;
35    use plonky2::field::{
36        goldilocks_field::GoldilocksField as F,
37        types::{Field, Field64},
38    };
39    use pod2::backends::plonky2::primitives::ec::curve::GROUP_ORDER;
40    use rand::{Rng, rngs::OsRng};
41
42    use crate::{
43        ECGFP5SecretKey,
44        hashed_elgamal::{hashed_elgamal_decrypt, hashed_elgamal_encrypt},
45    };
46
47    #[test]
48    fn round_trip_encryption() {
49        (0..100).for_each(|_| {
50            let sk = ECGFP5SecretKey::rand();
51            let pk = sk.public_key();
52
53            let msg = std::array::from_fn(|_| F::from_canonical_u64(OsRng.gen_range(0..F::ORDER)));
54            let nonce = OsRng.gen_biguint_below(&GROUP_ORDER);
55
56            let encrypted_msg = hashed_elgamal_encrypt(pk, &nonce, msg);
57            let decrypted_msg = hashed_elgamal_decrypt(sk, encrypted_msg);
58
59            assert_eq!(msg, decrypted_msg);
60        })
61    }
62}