concordium_base/elgamal/
mod.rs

1//! Implementation of elgamal public key encryption and decryption over a Curve.
2
3mod cipher;
4mod errors;
5mod message;
6mod public;
7mod secret;
8
9pub use self::{cipher::*, message::*, public::*, secret::*};
10
11use crate::curve_arithmetic::{Curve, Field, PrimeField, Value};
12use rand::*;
13
14/// Possible chunk sizes in bits.
15#[derive(Clone, Copy, PartialEq, Eq)]
16pub enum ChunkSize {
17    One,
18    Two,
19    Four,
20    Eight,
21    Sixteen,
22    ThirtyTwo,
23    SixtyFour,
24}
25
26impl From<ChunkSize> for u8 {
27    fn from(c: ChunkSize) -> Self {
28        use ChunkSize::*;
29        match c {
30            One => 1,
31            Two => 2,
32            Four => 4,
33            Eight => 8,
34            Sixteen => 16,
35            ThirtyTwo => 32,
36            SixtyFour => 64,
37        }
38    }
39}
40
41impl ChunkSize {
42    /// Compute the "mask" from chunk size. The mask can be used
43    /// to obtain the lowest (least significant) bits of a `u64` value.
44    ///
45    /// Concoretely, if ChunkSize is n, then this function returns
46    /// a u64 value whose n least significant bits are 1, and all other bits are
47    /// 0.
48    pub fn mask(self) -> u64 {
49        use ChunkSize::*;
50        match self {
51            One => 1,
52            Two => 0b11,
53            Four => 0b1111,
54            Eight => (1 << 8) - 1,
55            Sixteen => (1 << 16) - 1,
56            ThirtyTwo => (1 << 32) - 1,
57            SixtyFour => !0,
58        }
59    }
60
61    /// Return chunks as little-endian limbs.
62    pub fn u64_to_chunks(self, x: u64) -> Vec<u64> {
63        let mask = self.mask();
64        let size = u8::from(self);
65        let n = 64 / usize::from(size);
66        let mut out = Vec::with_capacity(n);
67        let mut tmp = x;
68        for _ in 0..n {
69            out.push(tmp & mask);
70            tmp >>= size;
71        }
72        out
73    }
74
75    /// Reconstruct from little endian limbs, given chunk size.
76    pub fn chunks_to_u64(self, xs: impl IntoIterator<Item = u64>) -> u64 {
77        let size = u8::from(self);
78        let mut factor = 0;
79        let mut out = 0;
80        for x in xs {
81            out += x << factor;
82            factor += size;
83        }
84        out
85    }
86}
87
88/// Transform a scalar into as many chunks as necessary.
89/// The chunks are returned in little-endian order.
90pub fn value_to_chunks<C: Curve>(val: &C::Scalar, chunk_size: ChunkSize) -> Vec<Value<C>> {
91    // u64 chunks as little-endian limbs
92    let size = usize::from(u8::from(chunk_size));
93    let n = C::SCALAR_LENGTH / size;
94    let mut out = Vec::with_capacity(n);
95    let u64_chunks = val.into_repr();
96    for chunk in u64_chunks {
97        out.extend(
98            chunk_size
99                .u64_to_chunks(chunk)
100                .iter()
101                .map(|&x| Value::new(C::scalar_from_u64(x))),
102        );
103    }
104    out
105}
106
107/// NB: This function does not ensure there is no overflow.
108/// It assumes that the chunks are reasonable and at most 64 bits.
109///
110/// The chunks are assumed to be in little-endian order.
111pub fn chunks_to_value<C: Curve>(chunks: &[Value<C>], chunk_size: ChunkSize) -> Value<C> {
112    // 2^64
113    let mul = {
114        let mut factor = C::scalar_from_u64(1);
115        factor.add_assign(&C::scalar_from_u64(!0));
116        factor
117    };
118    let mut factor = C::Scalar::one();
119    let mut ret = C::Scalar::zero();
120    for chunk_section in chunks.chunks(64 / usize::from(u8::from(chunk_size))) {
121        // get the u64 encoded in this chunk section
122        let v = chunk_size.chunks_to_u64(chunk_section.iter().map(|chunk| {
123            let repr = chunk.into_repr();
124            repr[0]
125        }));
126        let mut val = C::scalar_from_u64(v);
127        val.mul_assign(&factor);
128        ret.add_assign(&val);
129        factor.mul_assign(&mul);
130    }
131    Value::new(ret)
132}
133
134/// Wrapper around `encrypt_in_chunks_given_generator` that uses the generator
135/// that is part of the public key.
136pub fn encrypt_in_chunks<C: Curve, R: Rng>(
137    pk: &PublicKey<C>,
138    val: &Value<C>,
139    chunk_size: ChunkSize,
140    csprng: &mut R,
141) -> Vec<(Cipher<C>, Randomness<C>)> {
142    encrypt_in_chunks_given_generator(pk, val, chunk_size, &pk.generator, csprng)
143}
144
145pub fn encrypt_in_chunks_given_generator<C: Curve, R: Rng>(
146    pk: &PublicKey<C>,
147    val: &Value<C>,
148    chunk_size: ChunkSize,
149    generator: &C,
150    csprng: &mut R,
151) -> Vec<(Cipher<C>, Randomness<C>)> {
152    let chunks = value_to_chunks::<C>(val, chunk_size);
153    pk.encrypt_exponent_vec_given_generator(&chunks, generator, csprng)
154}
155
156/// Encrypt a single `u64` value in chunks in the exponent of the given
157/// generator.
158pub fn encrypt_u64_in_chunks_given_generator<C: Curve, R: Rng>(
159    pk: &PublicKey<C>,
160    val: u64,
161    chunk_size: ChunkSize,
162    generator: &C,
163    csprng: &mut R,
164) -> Vec<(Cipher<C>, Randomness<C>)> {
165    let chunks = chunk_size
166        .u64_to_chunks(val)
167        .into_iter()
168        .map(Value::from)
169        .collect::<Vec<_>>();
170    pk.encrypt_exponent_vec_given_generator(&chunks, generator, csprng)
171}
172
173/// Wrapper around `decrypt_from_chunks_given_generator` that uses the generator
174/// that is part of the key.
175pub fn decrypt_from_chunks<C: Curve>(
176    sk: &SecretKey<C>,
177    cipher: &[Cipher<C>],
178    m: u64,
179    chunk_size: ChunkSize,
180) -> Value<C> {
181    decrypt_from_chunks_given_generator(sk, cipher, &sk.generator, m, chunk_size)
182}
183
184pub fn decrypt_from_chunks_given_generator<C: Curve>(
185    sk: &SecretKey<C>,
186    cipher: &[Cipher<C>],
187    generator: &C,
188    m: u64,
189    chunk_size: ChunkSize,
190) -> Value<C> {
191    let bsgs = BabyStepGiantStep::new(generator, m);
192    decrypt_from_chunks_given_table(sk, cipher, &bsgs, chunk_size)
193}
194
195pub fn decrypt_from_chunks_given_table<C: Curve>(
196    sk: &SecretKey<C>,
197    ciphers: &[Cipher<C>],
198    table: &BabyStepGiantStep<C>,
199    chunk_size: ChunkSize,
200) -> Value<C> {
201    let scalars = ciphers
202        .iter()
203        .map(|cipher| Value::from(sk.decrypt_exponent(cipher, table)))
204        .collect::<Vec<_>>();
205    chunks_to_value::<C>(&scalars, chunk_size)
206}
207
208#[cfg(test)]
209mod tests {
210    use crate::curve_arithmetic::arkworks_instances::ArkGroup;
211
212    use super::*;
213    use ark_bls12_381::{G1Projective, G2Projective};
214    use rand::{rngs::ThreadRng, Rng};
215
216    // This is a generic helper function that tests encryption/decryption in chunks.
217    // It is parameterized by a curve, and the intention is that concrete tests are
218    // going to use explicit curve instances.
219    fn test_encrypt_decrypt_success_generic<C: Curve>() {
220        let mut csprng = thread_rng();
221        for _i in 1..10 {
222            let sk: SecretKey<C> = SecretKey::generate_all(&mut csprng);
223            let pk = PublicKey::from(&sk);
224            let m = Message::generate(&mut csprng);
225            let c = pk.encrypt(&mut csprng, &m);
226            let mm = sk.decrypt(&c);
227            assert_eq!(m, mm);
228
229            // encrypting again gives a different ciphertext (very likely at least)
230            let canother = pk.encrypt(&mut csprng, &m);
231            assert_ne!(c, canother);
232        }
233    }
234
235    #[test]
236    fn encrypt_decrypt_success_g1() {
237        test_encrypt_decrypt_success_generic::<ArkGroup<G1Projective>>()
238    }
239
240    #[test]
241    fn encrypt_decrypt_success_g2() {
242        test_encrypt_decrypt_success_generic::<ArkGroup<G2Projective>>()
243    }
244
245    // This is a generic helper function that tests encryption/decryption in chunks.
246    // It is parameterized by a curve, and the intention is that concrete tests are
247    // going to use explicit curve instances.
248    fn test_encrypt_decrypt_exponent_success_generic<C: Curve>() {
249        let mut csprng = thread_rng();
250        let sk: SecretKey<C> = SecretKey::generate_all(&mut csprng);
251        let pk = PublicKey::from(&sk);
252        for _i in 1..10 {
253            let n = csprng.gen_range(0..1000);
254            let mut e = <C as Curve>::Scalar::zero();
255            let one_scalar = Value::<C>::new(<C as Curve>::Scalar::one());
256            for _ in 0..n {
257                e.add_assign(&one_scalar);
258            }
259            let c = pk.encrypt_exponent(&mut csprng, &Value::new(e));
260            let e2 = sk.decrypt_exponent_slow(&c);
261            let e = Value::new(e);
262            assert_eq!(e, e2);
263        }
264    }
265
266    #[test]
267    fn encrypt_decrypt_exponent_success_g1() {
268        test_encrypt_decrypt_exponent_success_generic::<ArkGroup<G1Projective>>()
269    }
270
271    #[test]
272    fn encrypt_decrypt_exponent_success_g2() {
273        test_encrypt_decrypt_exponent_success_generic::<ArkGroup<G2Projective>>()
274    }
275
276    // This is a generic helper function that tests encryption/decryption in chunks.
277    // It is parameterized by a curve, and the intention is that concrete tests are
278    // going to use explicit curve instances.
279    fn test_chunking_generic<C: Curve>() {
280        let mut csprng = thread_rng();
281        use ChunkSize::*;
282        let possible_chunk_sizes = [One, Two, Four, Eight, Sixteen, ThirtyTwo];
283        // let possible_chunk_sizes = [32];
284
285        for _i in 1..10 {
286            let scalar = Value::<C>::generate(&mut csprng);
287            let chunk_size_index: usize = csprng.gen_range(0..possible_chunk_sizes.len());
288            let chunk_size = possible_chunk_sizes[chunk_size_index];
289            let chunks = value_to_chunks::<C>(&scalar, chunk_size);
290            let retrieved_scalar = chunks_to_value::<C>(&chunks, chunk_size);
291            // assert!(true);
292            assert_eq!(scalar, retrieved_scalar);
293        }
294    }
295
296    #[test]
297    fn chunking_test_g1() {
298        test_chunking_generic::<ArkGroup<G1Projective>>()
299    }
300
301    // This is a generic helper function that tests encryption/decryption in chunks.
302    // It is parameterized by a curve, and the intention is that concrete tests are
303    // going to use explicit curve instances.
304    fn test_chunked_encrypt_decrypt_generic<C: Curve>() {
305        let mut csprng = thread_rng();
306        let sk = SecretKey::<C>::generate_all(&mut csprng);
307        let pk = PublicKey::<C>::from(&sk);
308        // let possible_chunk_sizes = [1, 2, 4];
309        let possible_chunk_sizes = [ChunkSize::Two];
310
311        for _i in 1..2 {
312            let scalar = Value::<C>::generate(&mut csprng);
313            let chunk_size_index: usize = csprng.gen_range(0..possible_chunk_sizes.len());
314            let chunk_size = possible_chunk_sizes[chunk_size_index];
315            let m = 1 << (u8::from(chunk_size) - 1);
316            let cipher_pairs =
317                encrypt_in_chunks::<C, ThreadRng>(&pk, &scalar, chunk_size, &mut csprng);
318            let cipher = cipher_pairs.into_iter().map(|(x, _)| x).collect::<Vec<_>>();
319            let retrieved_scalar = decrypt_from_chunks::<C>(&sk, &cipher, m, chunk_size);
320            assert_eq!(
321                scalar, retrieved_scalar,
322                "Encrypted and retrieved scalars differ."
323            );
324        }
325    }
326
327    #[test]
328    fn chunked_encrypt_decrypt_test_g1() {
329        test_chunked_encrypt_decrypt_generic::<ArkGroup<G1Projective>>()
330    }
331}