arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/ecgfp5/src/elgamal.rs
ecgfp5/src/elgamal.rsRaw
  1use std::array;
  2
  3use num::bigint::BigUint;
  4use num_bigint::RandBigInt;
  5use plonky2::field::{
  6    extension::quintic::QuinticExtension,
  7    goldilocks_field::GoldilocksField as F,
  8    types::{Field, Field64},
  9};
 10use pod2::backends::plonky2::primitives::ec::curve::{GROUP_ORDER, Point};
 11use rand::{Rng, rngs::OsRng};
 12
 13pub mod circuit;
 14
 15// Message encoding constants
 16pub const BITS_PER_LIMB: usize = 32;
 17pub const TOTAL_BITS: usize = 5 * BITS_PER_LIMB;
 18
 19pub struct ECGFP5SecretKey(pub BigUint);
 20
 21impl ECGFP5SecretKey {
 22    pub fn new(s: BigUint) -> Self {
 23        assert!(&s < &GROUP_ORDER);
 24        Self(s)
 25    }
 26
 27    pub fn rand() -> Self {
 28        Self(OsRng.gen_biguint_below(&GROUP_ORDER))
 29    }
 30
 31    pub fn public_key(&self) -> Point {
 32        &self.0 * Point::generator()
 33    }
 34}
 35
 36pub fn elgamal_encrypt(pk: Point, nonce: &BigUint, msg: Point) -> (Point, Point) {
 37    assert!(nonce < &GROUP_ORDER);
 38    let nonce_times_gen = nonce * Point::generator();
 39    let nonce_times_pk = nonce * pk;
 40    (nonce_times_gen, msg + nonce_times_pk)
 41}
 42
 43pub fn elgamal_decrypt(sk: ECGFP5SecretKey, ct: (Point, Point)) -> Point {
 44    let nonce_times_pk = &sk.0 * ct.0;
 45    ct.1 + nonce_times_pk.inverse()
 46}
 47
 48/// Convert `TOTAL_BITS`-bit biguint to a point by padding with random bits.
 49pub fn biguint_to_point(x: &BigUint) -> Point {
 50    let num_bits = x.bits() as usize;
 51    assert!(num_bits <= TOTAL_BITS);
 52
 53    let bs_bits = [x.to_radix_le(2), vec![0; TOTAL_BITS - num_bits]].concat();
 54    let bs_fields: Vec<F> = bs_bits
 55        .chunks(BITS_PER_LIMB)
 56        .map(|bits| {
 57            F::from_noncanonical_biguint(
 58                BigUint::from_radix_le(bits, 2).expect("Should be a valid bit decomposition"),
 59            )
 60        })
 61        .collect();
 62
 63    std::iter::repeat_with(|| {
 64        let ec_fields: [_; 5] = array::from_fn(|i| {
 65            let random_element: u64 = OsRng.gen_range(0..F::ORDER - bs_fields[i].0);
 66            F::from_canonical_u64(
 67                bs_fields[i].0 + ((random_element >> BITS_PER_LIMB) << BITS_PER_LIMB),
 68            )
 69        });
 70        QuinticExtension(ec_fields)
 71    })
 72    .find_map(|w| Point::decompress_into_subgroup(&w).ok())
 73    .expect("Should be a match!")
 74}
 75
 76pub fn point_to_biguint(p: Point) -> BigUint {
 77    let msg_bits: Vec<_> = p
 78        .compress_from_subgroup()
 79        .unwrap()
 80        .0
 81        .into_iter()
 82        .flat_map(|f| {
 83            BigUint::from(f.0)
 84                .to_radix_le(2)
 85                .into_iter()
 86                .take(BITS_PER_LIMB)
 87                .collect::<Vec<_>>()
 88        })
 89        .collect();
 90    BigUint::from_bytes_le(
 91        &BigUint::from_radix_le(&msg_bits, 2)
 92            .expect("Should be a valid bit slice.")
 93            .to_radix_le(256),
 94    )
 95}
 96
 97#[cfg(test)]
 98mod tests {
 99    use num::BigUint;
100    use num_bigint::RandBigInt;
101    use pod2::backends::plonky2::primitives::ec::curve::{GROUP_ORDER, Point};
102    use rand::rngs::OsRng;
103
104    use crate::elgamal::{
105        ECGFP5SecretKey, TOTAL_BITS, biguint_to_point, elgamal_decrypt, elgamal_encrypt,
106        point_to_biguint,
107    };
108
109    #[test]
110    fn msg_encoding_roundtrip() {
111        (0..100).for_each(|_| {
112            let test_val = OsRng.gen_biguint_below(&BigUint::from(2u32).pow(TOTAL_BITS as u32));
113            let pt = biguint_to_point(&test_val);
114            let maybe_test_val = point_to_biguint(pt);
115            assert_eq!(test_val, maybe_test_val);
116        })
117    }
118
119    #[test]
120    fn round_trip_encryption() {
121        (0..100).for_each(|_| {
122            let sk = ECGFP5SecretKey::rand();
123            let pk = sk.public_key();
124
125            let msg = Point::new_rand_from_subgroup();
126            let nonce = OsRng.gen_biguint_below(&GROUP_ORDER);
127
128            let encrypted_msg = elgamal_encrypt(pk, &nonce, msg);
129            let decrypted_msg = elgamal_decrypt(sk, encrypted_msg);
130
131            assert_eq!(msg, decrypted_msg);
132        })
133    }
134}