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}