arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/ecgfp5/src/lib.rs
ecgfp5/src/lib.rsRaw
  1use std::array;
  2
  3use num::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;
 14pub mod elgamal;
 15pub mod hashed_elgamal;
 16
 17// Message encoding constants
 18pub const BITS_PER_LIMB: usize = 32;
 19pub const TOTAL_BITS: usize = 5 * BITS_PER_LIMB;
 20
 21/// EcGFp5 secret key type
 22pub struct ECGFP5SecretKey(pub BigUint);
 23
 24impl ECGFP5SecretKey {
 25    /// EcGFp5 secret key constructor
 26    pub fn new(s: BigUint) -> Self {
 27        assert!(&s < &GROUP_ORDER);
 28        Self(s)
 29    }
 30
 31    /// Random EcGFp5 secret key generator
 32    pub fn rand() -> Self {
 33        Self(OsRng.gen_biguint_below(&GROUP_ORDER))
 34    }
 35
 36    /// Derives public key from secret key
 37    pub fn public_key(&self) -> Point {
 38        &self.0 * Point::generator()
 39    }
 40}
 41
 42/// Convert `TOTAL_BITS`-bit biguint to an EcGFp5 point by padding
 43/// with random bits. This is an adaptation of the probabilistic
 44/// algorithm described in §3 of Koblitz's *Elliptic Curve
 45/// Cryptosystems* (1987).
 46pub fn encode_binary(x: &BigUint) -> Point {
 47    let num_bits = x.bits() as usize;
 48    assert!(num_bits <= TOTAL_BITS);
 49
 50    let bs_bits = [x.to_radix_le(2), vec![0; TOTAL_BITS - num_bits]].concat();
 51    let bs_fields: Vec<F> = bs_bits
 52        .chunks(BITS_PER_LIMB)
 53        .map(|bits| {
 54            F::from_noncanonical_biguint(
 55                BigUint::from_radix_le(bits, 2).expect("Should be a valid bit decomposition"),
 56            )
 57        })
 58        .collect();
 59
 60    std::iter::repeat_with(|| {
 61        let ec_fields: [_; 5] = array::from_fn(|i| {
 62            let random_element: u64 = OsRng.gen_range(0..F::ORDER - bs_fields[i].0);
 63            F::from_canonical_u64(
 64                bs_fields[i].0 + ((random_element >> BITS_PER_LIMB) << BITS_PER_LIMB),
 65            )
 66        });
 67        QuinticExtension(ec_fields)
 68    })
 69    .find_map(|w| Point::decompress_into_subgroup(&w).ok())
 70    .expect("Should be a match!")
 71}
 72
 73/// Convert an EcGFp5 point to a `TOTAL_BITS`-bit biguint. This is the
 74/// inverse to `encode_binary`.
 75pub fn decode_binary(p: Point) -> BigUint {
 76    let msg_bits: Vec<_> = p
 77        .compress_from_subgroup()
 78        .unwrap()
 79        .0
 80        .into_iter()
 81        .flat_map(|f| {
 82            BigUint::from(f.0)
 83                .to_radix_le(2)
 84                .into_iter()
 85                .take(BITS_PER_LIMB)
 86                .collect::<Vec<_>>()
 87        })
 88        .collect();
 89    BigUint::from_bytes_le(
 90        &BigUint::from_radix_le(&msg_bits, 2)
 91            .expect("Should be a valid bit slice.")
 92            .to_radix_le(256),
 93    )
 94}
 95#[cfg(test)]
 96mod tests {
 97    use num::BigUint;
 98    use num_bigint::RandBigInt;
 99    use rand::rngs::OsRng;
100
101    use crate::{TOTAL_BITS, decode_binary, encode_binary};
102
103    #[test]
104    fn msg_encoding_roundtrip() {
105        (0..100).for_each(|_| {
106            let test_val = OsRng.gen_biguint_below(&BigUint::from(2u32).pow(TOTAL_BITS as u32));
107            let pt = encode_binary(&test_val);
108            let maybe_test_val = decode_binary(pt);
109            assert_eq!(test_val, maybe_test_val);
110        })
111    }
112}