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
193macro_rules! osrng {
194 () => {
195 &mut aead::OsRng
196 };
197}
198
199/// Returns a random Ristretto point, mainly for examples.
200///
201/// If you're immediately encrypting this point, consider
202/// using [PublicKey::encrypt_random] instead.
203pub fn random_point() -> RistrettoPoint {
204 RistrettoPoint::random(osrng!())
205}
206
207/// Returns a random scalar, mainly for examples.
208pub fn random_scalar() -> Scalar {
209 Scalar::random(osrng!())
210}
211
212/// Private key - load using [`PrivateKey::from_hex`] or generate with [`PrivateKey::random`].
213///
214/// Caches the associated [`PublicKey`], which means that loading a [`PrivateKey`] involves a base
215/// point multiplication.
216#[derive(Clone, PartialEq, Eq, Debug, zeroize::ZeroizeOnDrop)]
217pub struct PrivateKey {
218 /// underlying scalar
219 scalar: Scalar,
220
221 /// associated public key, stored for efficiency
222 #[zeroize(skip)]
223 public_key: PublicKey,
224}
225
226impl PrivateKey {
227 /// Returns reference to underlying scalar.
228 pub fn as_scalar(&self) -> &Scalar {
229 &self.scalar
230 }
231
232 pub fn random() -> Self {
233 random_scalar().into()
234 }
235
236 pub fn public_key(&self) -> &PublicKey {
237 &self.public_key
238 }
239
240 /// Computes the [PublicKey] associated with the product of two [PrivateKey]s given only one
241 /// private key.
242 pub fn scale(&self, pk: &PublicKey) -> PublicKey {
243 (self.scalar * pk.point).into()
244 }
245
246 /// Creates a Diffie-Hellman-type shared secret between this [`PrivateKey`] and the [`PublicKey`].
247 pub fn shared_secret(&self, pk: &PublicKey) -> SharedSecret {
248 SharedSecret {
249 inner: self.scale(pk).to_bytes(),
250 }
251 }
252}
253
254impl From<Scalar> for PrivateKey {
255 fn from(scalar: Scalar) -> Self {
256 PrivateKey {
257 scalar,
258 public_key: (&scalar * B).into(),
259 }
260 }
261}
262
263/// Public key - obtained using [`PublicKey::from_hex`] or [`PrivateKey::public_key`].
264#[derive(Clone, PartialEq, Eq, Debug)]
265pub struct PublicKey {
266 point: RistrettoPoint,
267 compressed: CompressedRistretto,
268}
269
270impl AsRef<[u8]> for PublicKey {
271 /// Returns a reference to the compressed encoding of this public key
272 fn as_ref(&self) -> &[u8] {
273 self.compressed.as_bytes().as_slice()
274 }
275}
276
277impl PublicKey {
278 /// Turns a 64 digit hex string into a [`PublicKey`].
279 ///
280 /// Returns `None` when the hex-encoding is invalid or when the hex-encoding does not encode a
281 /// valid Ristretto point.
282 pub fn from_hex(hexstr: &str) -> Option<Self> {
283 CompressedRistretto::from_hex(hexstr)?.try_into().ok()
284 }
285
286 /// The identity element, which encodes as 32 zero bytes.
287 pub fn zero() -> Self {
288 use curve25519_dalek::traits::Identity as _;
289 RistrettoPoint::identity().into()
290 }
291
292 /// Encrypts the given `plaintext` for this public key.
293 /// If the plaintext is a random point, consider using [Self::encrypt_random].
294 pub fn encrypt(&self, plaintext: RistrettoPoint) -> Triple {
295 self.encrypt_with_random(random_scalar(), plaintext)
296 }
297
298 /// Like [`Self::encrypt`], but you can specify the random scalar used - which you shouldn't
299 /// except to make deterministic tests.
300 pub fn encrypt_with_random(&self, r: Scalar, plaintext: RistrettoPoint) -> Triple {
301 Triple {
302 ek: &r * B,
303 ct: plaintext + r * self.point,
304 pk: self.point,
305 }
306 }
307
308 /// Effectively encrypts a random plaintext for this public key.
309 ///
310 /// Instead of picking random Ristretto point M and random scalar r and computing
311 /// `(rB, r * pk + M, self)`
312 /// we pick Ristretto points ek and ct randomly and return
313 /// `(ek, ct, sekf)`.
314 /// since this is more efficient, and yields the same distribution.
315 pub fn encrypt_random(&self) -> Triple {
316 Triple {
317 ek: random_point(),
318 ct: random_point(),
319 pk: self.point,
320 }
321 }
322}
323
324impl From<RistrettoPoint> for PublicKey {
325 fn from(point: RistrettoPoint) -> Self {
326 Self {
327 point,
328 compressed: point.compress(),
329 }
330 }
331}
332
333impl TryFrom<CompressedRistretto> for PublicKey {
334 type Error = ();
335
336 fn try_from(compressed: CompressedRistretto) -> Result<Self, Self::Error> {
337 Ok(Self {
338 point: compressed.decompress().ok_or(())?,
339 compressed,
340 })
341 }
342}
343
344/// Adds encoding and decoding methods to [`PrivateKey`], [`PublicKey`], [`Triple`], [`Scalar`]
345/// and [`RistrettoPoint`] which can all be represented as `[u8; N]`s for some `N`.
346///
347/// Not all arrays of the form `[u8; N]` may be a valid representation of the type of object in question, though.
348pub trait Encoding<const N: usize>
349where
350 Self: Sized,
351{
352 /// Decodes `Some(object)` from `bytes` if `bytes` encodes some `object` of type `Self`;
353 /// otherwise returns `None`.
354 fn from_bytes(bytes: [u8; N]) -> Option<Self>;
355
356 /// Encodes `self` as `[u8; N]`.
357 fn to_bytes(&self) -> [u8; N];
358
359 /// Like [Self::from_bytes], but reads `[u8; N]` from `slice`. Returns `None` if `slice.len()!=N`
360 /// or when the slice is not a valid encoding.
361 fn from_slice(slice: &[u8]) -> Option<Self> {
362 if slice.len() != N {
363 return None;
364 }
365
366 let mut buf = [0u8; N];
367 buf.copy_from_slice(slice);
368
369 Self::from_bytes(buf)
370 }
371
372 /// Copies the encoding of `self` into `slice`. Returns `None` when `slice.len()!=N`.
373 fn copy_to_slice(&self, slice: &mut [u8]) -> Option<()> {
374 if slice.len() != N {
375 return None;
376 }
377
378 slice.copy_from_slice(&self.to_bytes());
379
380 Some(())
381 }
382
383 /// Like [Self::from_bytes], but reads the `[u8; N]` from the 2*N-digit hex string `hex`.
384 /// The case of the hex digits is ignored.
385 fn from_hex(hex: &str) -> Option<Self> {
386 let hex: &[u8] = hex.as_bytes();
387
388 if hex.len() != 2 * N {
389 return None;
390 }
391
392 let mut buf = [0u8; N];
393
394 base16ct::mixed::decode(hex, &mut buf).ok()?;
395 Self::from_bytes(buf)
396 }
397
398 /// Returns the `2*N`-digit lower-case hex representation of `self`.
399 fn to_hex(&self) -> String {
400 base16ct::lower::encode_string(&self.to_bytes())
401 }
402
403 /// Loads object from the `N`-byte buffer pointed to by `ptr`.
404 ///
405 /// # Safety
406 /// The caller must make sure that `ptr` is properly alligned,
407 /// the `N`-byte buffer is readable, and isn't modified for the duration of the call.
408 ///
409 /// See the 'Safety' section of [core::slice::from_raw_parts] for more details.
410 unsafe fn from_ptr(ptr: *const u8) -> Option<Self> {
411 Self::from_slice(unsafe { core::slice::from_raw_parts(ptr, N) })
412 }
413
414 /// Writes the `N`-byte representation of this object to the memory location `ptr`.
415 ///
416 /// # Safety
417 /// The caller must make sure that `ptr` is properly alligned,
418 /// the `N`-byte buffer is writable, and isn't modified for the duration of the call.
419 ///
420 /// See the 'Safety' section of [core::slice::from_raw_parts_mut] for more details.
421 unsafe fn copy_to_ptr(self, ptr: *mut u8) {
422 self.copy_to_slice(unsafe { core::slice::from_raw_parts_mut(ptr, N) })
423 .unwrap()
424 // Note: `copy_to_slice` only fails when the provided slice has the incorrect size (not `N`)
425 // which is not the case here.
426 }
427}
428
429impl Encoding<32> for Scalar {
430 fn from_bytes(bytes: [u8; 32]) -> Option<Scalar> {
431 Scalar::from_canonical_bytes(bytes).into()
432 }
433
434 fn to_bytes(&self) -> [u8; 32] {
435 Scalar::to_bytes(self)
436 }
437}
438
439impl Encoding<32> for CompressedRistretto {
440 fn from_bytes(bytes: [u8; 32]) -> Option<CompressedRistretto> {
441 Some(CompressedRistretto(bytes))
442 }
443
444 fn to_bytes(&self) -> [u8; 32] {
445 self.to_bytes()
446 }
447}
448
449impl Encoding<32> for RistrettoPoint {
450 fn from_bytes(bytes: [u8; 32]) -> Option<RistrettoPoint> {
451 CompressedRistretto(bytes).decompress()
452 }
453
454 fn to_bytes(&self) -> [u8; 32] {
455 self.compress().to_bytes()
456 }
457}
458
459impl Encoding<32> for PrivateKey {
460 fn from_bytes(bytes: [u8; 32]) -> Option<PrivateKey> {
461 Scalar::from_bytes(bytes).map(PrivateKey::from)
462 }
463
464 fn to_bytes(&self) -> [u8; 32] {
465 self.scalar.to_bytes()
466 }
467}
468
469impl Encoding<32> for PublicKey {
470 fn from_bytes(bytes: [u8; 32]) -> Option<PublicKey> {
471 CompressedRistretto::from_bytes(bytes)?.try_into().ok()
472 }
473
474 fn to_bytes(&self) -> [u8; 32] {
475 self.compressed.to_bytes()
476 }
477}
478
479impl Encoding<96> for Triple {
480 fn from_bytes(bytes: [u8; 96]) -> Option<Triple> {
481 let ek: RistrettoPoint = RistrettoPoint::from_slice(&bytes[..32])?;
482 let ct: RistrettoPoint = RistrettoPoint::from_slice(&bytes[32..64])?;
483 let pk: RistrettoPoint = RistrettoPoint::from_slice(&bytes[64..])?;
484
485 Some(Triple { ek, ct, pk })
486 }
487
488 fn to_bytes(&self) -> [u8; 96] {
489 let mut result = [0u8; 96];
490
491 // Note: `copy_to_slice` only fails when the slice's size is not 32, which it won't below
492 self.ek.copy_to_slice(&mut result[..32]).unwrap();
493 self.ct.copy_to_slice(&mut result[32..64]).unwrap();
494 self.pk.copy_to_slice(&mut result[64..]).unwrap();
495
496 result
497 }
498}
499
500mod serde_impls {
501 use super::*;
502 use crate::misc::serde_ext;
503 use serde::de::Error as _;
504
505 /// Implements [`serde::Serialize`] and [`serde::Deserialize`] using [`serde_ext::ByteArray`] and hex
506 /// encoding
507 macro_rules! serde_impl {
508 { $type:ident, $n:literal } => {
509
510 impl<'de> serde::Deserialize<'de> for $type {
511 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
512 let byte_array : serde_ext::ByteArray<$n> =
513 serde_ext::bytes_wrapper::B16::<serde_ext::ByteArray<$n>>::deserialize(d)?.into_inner();
514 $type::from_bytes(byte_array.into()).ok_or_else(|| D::Error::custom(concat!("invalid ", stringify!($type))))
515 }
516 }
517
518 impl<'de> serde::Serialize for $type {
519 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
520 let byte_array = serde_ext::ByteArray::<$n>::from(self.to_bytes());
521 serde_ext::bytes_wrapper::B16::<serde_ext::ByteArray<$n>>::from(byte_array)
522 .serialize(s)
523 }
524 }
525 }
526 }
527
528 serde_impl! { PrivateKey, 32 }
529 serde_impl! { PublicKey, 32 }
530 serde_impl! { Triple, 96 }
531}
532
533/// Shared secret created by combining a [`PrivateKey`] with a [`PublicKey`], which, although it is
534/// basically the encoding of a [`RistrettoPoint`], is given a separate interface to limit its
535/// usage.
536#[derive(Clone, Debug, zeroize::ZeroizeOnDrop)]
537pub struct SharedSecret {
538 inner: [u8; 32],
539}
540
541impl crate::common::secret::DigestibleSecret for SharedSecret {
542 fn as_bytes(&self) -> &[u8] {
543 &self.inner
544 }
545}
546
547impl crate::common::secret::DigestibleSecret for PrivateKey {
548 fn as_bytes(&self) -> &[u8] {
549 self.scalar.as_bytes().as_slice()
550 }
551}
552
553///// Application binary interface
554//pub mod abi {
555// use super::*;
556//
557// /// Decrypts the given `ciphertext` using the given `private_key` and stores the result in
558// /// `plaintext`.
559// ///
560// /// * `plaintext` - pointer to a writable 32-byte buffer
561// /// * `ciperhtext` - pointer to a 96-byte buffer holding the result of [Triple::to_bytes]
562// /// * `private_key` - pointer to a 32-byte buffer holding the result of [Scalar::to_bytes]
563// ///
564// /// # Safety
565// /// The caller must make sure the pointers are aligned, point to valid memory regions,
566// /// are readable, and plaintext is writable, and are not otherwise modified.
567// ///
568// /// For more details, see [core::slice::from_raw_parts] and [core::slice::from_raw_parts_mut].
569// #[unsafe(no_mangle)]
570// pub unsafe extern "C" fn decrypt(
571// plaintext: *mut u8,
572// ciphertext: *const u8,
573// private_key: *const u8,
574// ) -> DecryptResult {
575// let pk = match unsafe { PrivateKey::from_ptr(private_key) } {
576// Some(pk) => pk,
577// None => return DecryptResult::InvalidPrivateKey,
578// };
579//
580// let ct = match unsafe { Triple::from_ptr(ciphertext) } {
581// Some(ct) => ct,
582// None => return DecryptResult::InvalidTriple,
583// };
584//
585// let pt = match ct.decrypt_and_check_pk(&pk) {
586// Some(pt) => pt,
587// None => return DecryptResult::WrongPublicKey,
588// };
589//
590// unsafe { pt.copy_to_ptr(plaintext) }
591//
592// DecryptResult::Ok
593// }
594//
595// /// Result of [decrypt].
596// #[repr(u8)]
597// pub enum DecryptResult {
598// Ok = 1,
599// WrongPublicKey = 2,
600// InvalidTriple = 3,
601// InvalidPrivateKey = 4,
602// }
603//}