pubhubs/common/elgamal.rs
1//! The ElGamal cryptosystem, as used in PEP
2
3use curve25519_dalek::{
4 constants::RISTRETTO_BASEPOINT_TABLE as B,
5 ristretto::{CompressedRistretto, RistrettoPoint},
6 scalar::Scalar,
7};
8
9/// ElGamal ciphertext - the result of [`PublicKey::encrypt`].
10///
11/// The associated public key is remembered to allow rerandomization, but this public key is
12/// not authenticated in any way. This means that anyone intercepting a triple may
13/// modify the public key without detection (but this does not cause the
14/// triple to be decryptable to the same plaintext by another public key.)
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Triple {
17 /// Ephemeral key
18 ek: RistrettoPoint,
19
20 /// Ciphertext,
21 ct: RistrettoPoint,
22
23 /// Public key
24 pk: RistrettoPoint,
25}
26
27impl Triple {
28 /// Decrypts the triple using the given private key `sk`. If the triple was encrypted
29 /// for a different private key, the result is a random point.
30 pub fn decrypt(self, sk: &PrivateKey) -> RistrettoPoint {
31 self.ct - sk.scalar * self.ek
32 }
33
34 /// Decrypts the triple using the given private key `sk` if the triple claims to be encrypted
35 /// for the associated public key; returns `None` otherwise.
36 ///
37 /// **Warning** This function can't check whether the triple's public key `pk` has been
38 /// tampered with.
39 ///
40 /// While tampering cannot be prevented, the plaintext of a triple with spoofed `pk` can be
41 /// garbled, using [Self::rerandomize].
42 ///
43 pub fn decrypt_and_check_pk(self, sk: &PrivateKey) -> Option<RistrettoPoint> {
44 if self.pk == B * &sk.scalar {
45 Some(self.decrypt(sk))
46 } else {
47 None
48 }
49 }
50
51 /// Changes the public key of this triple, likely resulting in garbage down the road.
52 ///
53 /// Used for demonstration purposes.
54 pub fn spoof_pk(self, pk: PublicKey) -> Triple {
55 Triple {
56 ek: self.ek,
57 ct: self.ct,
58 pk: pk.point,
59 }
60 }
61
62 /// Changes the appearance of the ciphertext, but leaves the plaintext and the target
63 /// public key unaltered. If the public key was spoofed, the plaintext is garbled.
64 /// ```
65 /// use pubhubs::common::elgamal::{PrivateKey, random_point, random_scalar};
66 /// use curve25519_dalek::{
67 /// ristretto::RistrettoPoint,
68 /// constants::RISTRETTO_BASEPOINT_TABLE as B,
69 /// };
70 ///
71 /// let M = random_point();
72 /// let sk = PrivateKey::random();
73 /// let pk = sk.public_key();
74 ///
75 /// let r1 = random_scalar();
76 /// let r2 = random_scalar();
77 ///
78 /// // Rerandomization leaves the plaintext unchanged:
79 /// let trip = pk.encrypt_with_random(r1, M).rerandomize_with_random(r2);
80 /// assert_eq!(trip, pk.encrypt_with_random(r1+r2,M));
81 ///
82 /// // But if the public key was spoofed, the plaintext is garbled:
83 /// let sk2 = PrivateKey::random();
84 /// let pk2 = sk2.public_key().clone();
85 /// let trip = pk.encrypt_with_random(r1, M).spoof_pk(pk2).rerandomize_with_random(r2);
86 ///
87 /// assert_eq!(trip.clone().decrypt_and_check_pk(&sk2),
88 /// Some(M + B * &(r1 * (sk.as_scalar()-sk2.as_scalar()))));
89 ///
90 /// // Indeed, if sk =/= sk2, then r1(sk - sk2)B will be some random unknowable Ristretto
91 /// // point, because r1 should be a random scalar that has been thrown away.
92 /// ```
93 pub fn rerandomize(self) -> Triple {
94 self.rerandomize_with_random(random_scalar())
95 }
96
97 /// Like [Self::rerandomize], but you can specify the random scalar used -
98 /// which you shouldn't except to make deterministic tests.
99 pub fn rerandomize_with_random(self, r: Scalar) -> Triple {
100 Triple {
101 ek: self.ek + &r * B,
102 ct: self.ct + r * self.pk,
103 pk: self.pk,
104 }
105 }
106
107 /// Like [rsk] but taking the parameters `s` and `k` thusly: `rsk_with_s(s).and_k(k)`.
108 pub fn rsk_with_s(self, s: &Scalar) -> rsk::WithS<'_> {
109 rsk::WithS { t: self, s }
110 }
111
112 /// Changes the given ciphertext according to the `params` provided:
113 ///
114 /// - Multiplies the underlying plaintext by `params.s()`;
115 /// - Multiplies the target public/private key by `params.k()`;
116 /// - Rerandomizes the ciphertext using the scalar `params.r()`.
117 ///
118 /// If the public key `self.pk` was spoofed, the resulting plaintext is garbled,
119 /// provided the scalar `params.r()` was random.
120 ///
121 /// If you only need to specify `s` and `k`, use `triple.rsk_with_s(s).and_k(k)` instead.
122 pub fn rsk(self, params: impl rsk::Params) -> Triple {
123 let r: Scalar = params.r();
124 let kpk = self.pk * params.k();
125
126 Triple {
127 ek: params.s_over_k() * self.ek + &r * B,
128 ct: params.s() * self.ct + r * kpk,
129 pk: kpk,
130 }
131 }
132}
133
134/// Utilities for [Triple::rsk]
135pub mod rsk {
136 use super::*;
137
138 /// Implementation of the [Params] trait given the parameters `s` and `k`.
139 pub struct SAndK<'s, 'k> {
140 s: &'s Scalar,
141 k: &'k Scalar,
142 }
143
144 impl Params for SAndK<'_, '_> {
145 fn s(&self) -> &Scalar {
146 self.s
147 }
148
149 fn k(&self) -> &Scalar {
150 self.k
151 }
152 }
153
154 /// The result of [Triple::rsk_with_s]. You should call [WithS::and_k] on it.
155 pub struct WithS<'a> {
156 pub(crate) t: Triple,
157 pub(crate) s: &'a Scalar,
158 }
159
160 impl WithS<'_> {
161 pub fn and_k(self, k: &Scalar) -> Triple {
162 self.t.rsk(SAndK { s: self.s, k })
163 }
164 }
165
166 /// Utilities for the [Triple::rsk] operation.
167 pub trait Params {
168 /// Multiply the encrypted plaintext ristretto point by this scalar.
169 fn s(&self) -> &Scalar;
170
171 /// Multiply the target public/private key by this scalar.
172 fn k(&self) -> &Scalar;
173
174 /// Returns `1/k`.
175 fn k_inv(&self) -> Scalar {
176 self.k().invert()
177 }
178
179 /// Returns `s/k`.
180 fn s_over_k(&self) -> Scalar {
181 self.s() * self.k_inv()
182 }
183
184 /// Returns the scalar used for rerandomisation.
185 ///
186 /// **Warning:** only override this method for the purpose of making deterministic test.
187 fn r(&self) -> Scalar {
188 random_scalar()
189 }
190 }
191}
192
193/// Returns a random Ristretto point, mainly for examples.
194///
195/// If you're immediately encrypting this point, consider
196/// using [PublicKey::encrypt_random] instead.
197pub fn random_point() -> RistrettoPoint {
198 // NOTE: what `RistrettoPoint::random` does, but without needing a rand_core v0.6 RNG.
199 RistrettoPoint::from_uniform_bytes(&crate::misc::crypto::random_64_bytes())
200}
201
202/// Returns a random scalar, mainly for examples.
203pub fn random_scalar() -> Scalar {
204 // NOTE: what `Scalar::random` does, but without needing a rand_core v0.6 RNG.
205 Scalar::from_bytes_mod_order_wide(&crate::misc::crypto::random_64_bytes())
206}
207
208/// Private key - load using [`PrivateKey::from_hex`] or generate with [`PrivateKey::random`].
209///
210/// Caches the associated [`PublicKey`], which means that loading a [`PrivateKey`] involves a base
211/// point multiplication.
212#[derive(Clone, PartialEq, Eq, Debug, zeroize::ZeroizeOnDrop)]
213pub struct PrivateKey {
214 /// underlying scalar
215 scalar: Scalar,
216
217 /// associated public key, stored for efficiency
218 #[zeroize(skip)]
219 public_key: PublicKey,
220}
221
222impl PrivateKey {
223 /// Returns reference to underlying scalar.
224 pub fn as_scalar(&self) -> &Scalar {
225 &self.scalar
226 }
227
228 pub fn random() -> Self {
229 random_scalar().into()
230 }
231
232 pub fn public_key(&self) -> &PublicKey {
233 &self.public_key
234 }
235
236 /// Computes the [PublicKey] associated with the product of two [PrivateKey]s given only one
237 /// private key.
238 pub fn scale(&self, pk: &PublicKey) -> PublicKey {
239 (self.scalar * pk.point).into()
240 }
241
242 /// Creates a Diffie-Hellman-type shared secret between this [`PrivateKey`] and the [`PublicKey`].
243 pub fn shared_secret(&self, pk: &PublicKey) -> SharedSecret {
244 SharedSecret {
245 inner: self.scale(pk).to_bytes(),
246 }
247 }
248}
249
250impl From<Scalar> for PrivateKey {
251 fn from(scalar: Scalar) -> Self {
252 PrivateKey {
253 scalar,
254 public_key: (&scalar * B).into(),
255 }
256 }
257}
258
259/// Public key - obtained using [`PublicKey::from_hex`] or [`PrivateKey::public_key`].
260#[derive(Clone, PartialEq, Eq, Debug)]
261pub struct PublicKey {
262 point: RistrettoPoint,
263 compressed: CompressedRistretto,
264}
265
266impl AsRef<[u8]> for PublicKey {
267 /// Returns a reference to the compressed encoding of this public key
268 fn as_ref(&self) -> &[u8] {
269 self.compressed.as_bytes().as_slice()
270 }
271}
272
273impl PublicKey {
274 /// Turns a 64 digit hex string into a [`PublicKey`].
275 ///
276 /// Returns `None` when the hex-encoding is invalid or when the hex-encoding does not encode a
277 /// valid Ristretto point.
278 pub fn from_hex(hexstr: &str) -> Option<Self> {
279 CompressedRistretto::from_hex(hexstr)?.try_into().ok()
280 }
281
282 /// The identity element, which encodes as 32 zero bytes.
283 pub fn zero() -> Self {
284 use curve25519_dalek::traits::Identity as _;
285 RistrettoPoint::identity().into()
286 }
287
288 /// Encrypts the given `plaintext` for this public key.
289 /// If the plaintext is a random point, consider using [Self::encrypt_random].
290 pub fn encrypt(&self, plaintext: RistrettoPoint) -> Triple {
291 self.encrypt_with_random(random_scalar(), plaintext)
292 }
293
294 /// Like [`Self::encrypt`], but you can specify the random scalar used - which you shouldn't
295 /// except to make deterministic tests.
296 pub fn encrypt_with_random(&self, r: Scalar, plaintext: RistrettoPoint) -> Triple {
297 Triple {
298 ek: &r * B,
299 ct: plaintext + r * self.point,
300 pk: self.point,
301 }
302 }
303
304 /// Effectively encrypts a random plaintext for this public key.
305 ///
306 /// Instead of picking random Ristretto point M and random scalar r and computing
307 /// `(rB, r * pk + M, self)`
308 /// we pick Ristretto points ek and ct randomly and return
309 /// `(ek, ct, sekf)`.
310 /// since this is more efficient, and yields the same distribution.
311 pub fn encrypt_random(&self) -> Triple {
312 Triple {
313 ek: random_point(),
314 ct: random_point(),
315 pk: self.point,
316 }
317 }
318}
319
320impl From<RistrettoPoint> for PublicKey {
321 fn from(point: RistrettoPoint) -> Self {
322 Self {
323 point,
324 compressed: point.compress(),
325 }
326 }
327}
328
329impl TryFrom<CompressedRistretto> for PublicKey {
330 type Error = ();
331
332 fn try_from(compressed: CompressedRistretto) -> Result<Self, Self::Error> {
333 Ok(Self {
334 point: compressed.decompress().ok_or(())?,
335 compressed,
336 })
337 }
338}
339
340/// Adds encoding and decoding methods to [`PrivateKey`], [`PublicKey`], [`Triple`], [`Scalar`]
341/// and [`RistrettoPoint`] which can all be represented as `[u8; N]`s for some `N`.
342///
343/// Not all arrays of the form `[u8; N]` may be a valid representation of the type of object in question, though.
344pub trait Encoding<const N: usize>
345where
346 Self: Sized,
347{
348 /// Decodes `Some(object)` from `bytes` if `bytes` encodes some `object` of type `Self`;
349 /// otherwise returns `None`.
350 fn from_bytes(bytes: [u8; N]) -> Option<Self>;
351
352 /// Encodes `self` as `[u8; N]`.
353 fn to_bytes(&self) -> [u8; N];
354
355 /// Like [Self::from_bytes], but reads `[u8; N]` from `slice`. Returns `None` if `slice.len()!=N`
356 /// or when the slice is not a valid encoding.
357 fn from_slice(slice: &[u8]) -> Option<Self> {
358 if slice.len() != N {
359 return None;
360 }
361
362 let mut buf = [0u8; N];
363 buf.copy_from_slice(slice);
364
365 Self::from_bytes(buf)
366 }
367
368 /// Copies the encoding of `self` into `slice`. Returns `None` when `slice.len()!=N`.
369 fn copy_to_slice(&self, slice: &mut [u8]) -> Option<()> {
370 if slice.len() != N {
371 return None;
372 }
373
374 slice.copy_from_slice(&self.to_bytes());
375
376 Some(())
377 }
378
379 /// Like [Self::from_bytes], but reads the `[u8; N]` from the 2*N-digit hex string `hex`.
380 /// The case of the hex digits is ignored.
381 fn from_hex(hex: &str) -> Option<Self> {
382 let hex: &[u8] = hex.as_bytes();
383
384 if hex.len() != 2 * N {
385 return None;
386 }
387
388 let mut buf = [0u8; N];
389
390 base16ct::mixed::decode(hex, &mut buf).ok()?;
391 Self::from_bytes(buf)
392 }
393
394 /// Returns the `2*N`-digit lower-case hex representation of `self`.
395 fn to_hex(&self) -> String {
396 base16ct::lower::encode_string(&self.to_bytes())
397 }
398
399 /// Loads object from the `N`-byte buffer pointed to by `ptr`.
400 ///
401 /// # Safety
402 /// The caller must make sure that `ptr` is properly alligned,
403 /// the `N`-byte buffer is readable, and isn't modified for the duration of the call.
404 ///
405 /// See the 'Safety' section of [core::slice::from_raw_parts] for more details.
406 unsafe fn from_ptr(ptr: *const u8) -> Option<Self> {
407 Self::from_slice(unsafe { core::slice::from_raw_parts(ptr, N) })
408 }
409
410 /// Writes the `N`-byte representation of this object to the memory location `ptr`.
411 ///
412 /// # Safety
413 /// The caller must make sure that `ptr` is properly alligned,
414 /// the `N`-byte buffer is writable, and isn't modified for the duration of the call.
415 ///
416 /// See the 'Safety' section of [core::slice::from_raw_parts_mut] for more details.
417 unsafe fn copy_to_ptr(self, ptr: *mut u8) {
418 self.copy_to_slice(unsafe { core::slice::from_raw_parts_mut(ptr, N) })
419 .unwrap()
420 // Note: `copy_to_slice` only fails when the provided slice has the incorrect size (not `N`)
421 // which is not the case here.
422 }
423}
424
425impl Encoding<32> for Scalar {
426 fn from_bytes(bytes: [u8; 32]) -> Option<Scalar> {
427 Scalar::from_canonical_bytes(bytes).into()
428 }
429
430 fn to_bytes(&self) -> [u8; 32] {
431 Scalar::to_bytes(self)
432 }
433}
434
435impl Encoding<32> for CompressedRistretto {
436 fn from_bytes(bytes: [u8; 32]) -> Option<CompressedRistretto> {
437 Some(CompressedRistretto(bytes))
438 }
439
440 fn to_bytes(&self) -> [u8; 32] {
441 self.to_bytes()
442 }
443}
444
445impl Encoding<32> for RistrettoPoint {
446 fn from_bytes(bytes: [u8; 32]) -> Option<RistrettoPoint> {
447 CompressedRistretto(bytes).decompress()
448 }
449
450 fn to_bytes(&self) -> [u8; 32] {
451 self.compress().to_bytes()
452 }
453}
454
455impl Encoding<32> for PrivateKey {
456 fn from_bytes(bytes: [u8; 32]) -> Option<PrivateKey> {
457 Scalar::from_bytes(bytes).map(PrivateKey::from)
458 }
459
460 fn to_bytes(&self) -> [u8; 32] {
461 self.scalar.to_bytes()
462 }
463}
464
465impl Encoding<32> for PublicKey {
466 fn from_bytes(bytes: [u8; 32]) -> Option<PublicKey> {
467 CompressedRistretto::from_bytes(bytes)?.try_into().ok()
468 }
469
470 fn to_bytes(&self) -> [u8; 32] {
471 self.compressed.to_bytes()
472 }
473}
474
475impl Encoding<96> for Triple {
476 fn from_bytes(bytes: [u8; 96]) -> Option<Triple> {
477 let ek: RistrettoPoint = RistrettoPoint::from_slice(&bytes[..32])?;
478 let ct: RistrettoPoint = RistrettoPoint::from_slice(&bytes[32..64])?;
479 let pk: RistrettoPoint = RistrettoPoint::from_slice(&bytes[64..])?;
480
481 Some(Triple { ek, ct, pk })
482 }
483
484 fn to_bytes(&self) -> [u8; 96] {
485 let mut result = [0u8; 96];
486
487 // Note: `copy_to_slice` only fails when the slice's size is not 32, which it won't below
488 self.ek.copy_to_slice(&mut result[..32]).unwrap();
489 self.ct.copy_to_slice(&mut result[32..64]).unwrap();
490 self.pk.copy_to_slice(&mut result[64..]).unwrap();
491
492 result
493 }
494}
495
496mod serde_impls {
497 use super::*;
498 use crate::misc::serde_ext;
499 use serde::de::Error as _;
500
501 /// Implements [`serde::Serialize`] and [`serde::Deserialize`] using [`serde_ext::ByteArray`] and hex
502 /// encoding
503 macro_rules! serde_impl {
504 { $type:ident, $n:literal } => {
505
506 impl<'de> serde::Deserialize<'de> for $type {
507 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
508 let byte_array : serde_ext::ByteArray<$n> =
509 serde_ext::bytes_wrapper::B16::<serde_ext::ByteArray<$n>>::deserialize(d)?.into_inner();
510 $type::from_bytes(byte_array.into()).ok_or_else(|| D::Error::custom(concat!("invalid ", stringify!($type))))
511 }
512 }
513
514 impl<'de> serde::Serialize for $type {
515 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
516 let byte_array = serde_ext::ByteArray::<$n>::from(self.to_bytes());
517 serde_ext::bytes_wrapper::B16::<serde_ext::ByteArray<$n>>::from(byte_array)
518 .serialize(s)
519 }
520 }
521 }
522 }
523
524 serde_impl! { PrivateKey, 32 }
525 serde_impl! { PublicKey, 32 }
526 serde_impl! { Triple, 96 }
527}
528
529/// Shared secret created by combining a [`PrivateKey`] with a [`PublicKey`], which, although it is
530/// basically the encoding of a [`RistrettoPoint`], is given a separate interface to limit its
531/// usage.
532#[derive(Clone, Debug, zeroize::ZeroizeOnDrop)]
533pub struct SharedSecret {
534 inner: [u8; 32],
535}
536
537impl crate::common::secret::DigestibleSecret for SharedSecret {
538 fn as_bytes(&self) -> &[u8] {
539 &self.inner
540 }
541}
542
543impl crate::common::secret::DigestibleSecret for PrivateKey {
544 fn as_bytes(&self) -> &[u8] {
545 self.scalar.as_bytes().as_slice()
546 }
547}
548
549///// Application binary interface
550//pub mod abi {
551// use super::*;
552//
553// /// Decrypts the given `ciphertext` using the given `private_key` and stores the result in
554// /// `plaintext`.
555// ///
556// /// * `plaintext` - pointer to a writable 32-byte buffer
557// /// * `ciperhtext` - pointer to a 96-byte buffer holding the result of [Triple::to_bytes]
558// /// * `private_key` - pointer to a 32-byte buffer holding the result of [Scalar::to_bytes]
559// ///
560// /// # Safety
561// /// The caller must make sure the pointers are aligned, point to valid memory regions,
562// /// are readable, and plaintext is writable, and are not otherwise modified.
563// ///
564// /// For more details, see [core::slice::from_raw_parts] and [core::slice::from_raw_parts_mut].
565// #[unsafe(no_mangle)]
566// pub unsafe extern "C" fn decrypt(
567// plaintext: *mut u8,
568// ciphertext: *const u8,
569// private_key: *const u8,
570// ) -> DecryptResult {
571// let pk = match unsafe { PrivateKey::from_ptr(private_key) } {
572// Some(pk) => pk,
573// None => return DecryptResult::InvalidPrivateKey,
574// };
575//
576// let ct = match unsafe { Triple::from_ptr(ciphertext) } {
577// Some(ct) => ct,
578// None => return DecryptResult::InvalidTriple,
579// };
580//
581// let pt = match ct.decrypt_and_check_pk(&pk) {
582// Some(pt) => pt,
583// None => return DecryptResult::WrongPublicKey,
584// };
585//
586// unsafe { pt.copy_to_ptr(plaintext) }
587//
588// DecryptResult::Ok
589// }
590//
591// /// Result of [decrypt].
592// #[repr(u8)]
593// pub enum DecryptResult {
594// Ok = 1,
595// WrongPublicKey = 2,
596// InvalidTriple = 3,
597// InvalidPrivateKey = 4,
598// }
599//}