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