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}