Skip to main content

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}