protocol/elgamal.rs
1//! Elliptic-curve ElGamal over group elements.
2//!
3//! This module deliberately works on *points as messages* instead of trying to
4//! encode arbitrary byte strings into curve points. That keeps the abstraction
5//! compact and lets it directly reuse the existing `ec` layer.
6
7use ec::point_ops::PointAdd;
8
9use crate::scalar::SecretScalar;
10
11/// ElGamal ciphertext consisting of a pair $(c_1, c_2)$.
12///
13/// The ciphertext is formed as:
14///
15/// $$
16/// c_1 = rG, \quad c_2 = M + r \mathsf{PK}
17/// $$
18///
19/// where:
20/// - $r$ is a random
21/// - $G$ is the generator of the group
22/// - $PK$ is the recipient’s public key
23/// - $M$ is the plaintext message point
24///
25/// This struct stores the two ciphertext components.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Ciphertext<P> {
28 /// The ephemeral public component $c_1 = r G$.
29 pub ephemeral_public: P,
30
31 /// The blinded message component $c_2 = M + r \mathsf{PK}$.
32 pub blinded_message: P,
33}
34
35/// Stateless EC-ElGamal helper.
36pub struct EcElGamal;
37
38impl EcElGamal {
39 /// Encrypt a point `message` using a recipient public key and an
40 /// application-provided ephemeral scalar.
41 pub fn encrypt<P, const LIMBS: usize>(
42 base_point: &P,
43 recipient_public: &P,
44 message: &P,
45 ephemeral_secret: &SecretScalar<LIMBS>,
46 curve: &P::Curve,
47 ) -> Option<Ciphertext<P>>
48 where
49 P: PointAdd,
50 {
51 if recipient_public.is_identity() {
52 return None;
53 }
54
55 let ephemeral_public = base_point.scalar_mul(ephemeral_secret.as_limbs(), curve);
56 let shared = recipient_public.scalar_mul(ephemeral_secret.as_limbs(), curve);
57 let blinded_message = message.add(&shared, curve);
58
59 Some(Ciphertext {
60 ephemeral_public,
61 blinded_message,
62 })
63 }
64
65 /// Decrypt an ElGamal ciphertext back to the original point message.
66 pub fn decrypt<P, const LIMBS: usize>(
67 recipient_secret: &SecretScalar<LIMBS>,
68 ciphertext: &Ciphertext<P>,
69 curve: &P::Curve,
70 ) -> P
71 where
72 P: PointAdd,
73 {
74 let shared = ciphertext
75 .ephemeral_public
76 .scalar_mul(recipient_secret.as_limbs(), curve);
77 ciphertext.blinded_message.add(&shared.negate(curve), curve)
78 }
79}