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