1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! # Elgamal Public Key Encryption
//!
//! This document represents an executable specification of the Elgamal Public Key Encryption scheme.
//!
//! At the basis of the encryption scheme is a prime order group $`\mathbb{G}`$ with the same interface as defined in [draft-oprf].
#![warn(missing_docs)]

use hacspec_lib::Randomness;
use p256::{p256_point_mul, p256_point_mul_base, point_add, random_scalar, P256Point, P256Scalar};

use std::ops::Neg;

/// Domain separator for scalar sampling during key generation.
const DST_KEYGEN: &[u8] = b"ElGamal-KeyGeneration";
/// Domain separator for scalar sampling during encryption.
const DST_ENCRYPT: &[u8] = b"ElGamal-Encryption";
/// Domain separator for scalar sampling during ciphertext rerandomization.
const DST_RERANDOMIZE: &[u8] = b"ElGamal-Rerandomize";

/// An Elgamal encryption of a message `M` under public key `PK` is a pair of group elements `(c0, c1)` where
/// * `c0` is the group generator multiplied by a randomizer `r`
/// * `c1` is the result of multiplying the  target public encryption key the same randomizer `r` and adding the result to the message `M` that is to be encrypted.
pub type Ciphertext = (P256Point, P256Point);

/// An Elgamal plaintext is a member of the base group.
pub type Plaintext = P256Point;

#[derive(Debug)]
/// Any of the given algorithms may fail if the given arguments result in invalid group operations.
pub enum Error {
    /// Wrapper around Errors given by the underlying group implementation.
    CurveError,
    /// Wrapper around rejection sampling Errors
    SamplingError,
}

impl From<p256::Error> for Error {
    fn from(_value: p256::Error) -> Self {
        Self::CurveError
    }
}

/// An Elgamal private decryption key is a scalar $`dk`$ of base group $`\mathcal{G}`$.
pub type DecryptionKey = P256Scalar;
/// An Elgamal encryption key is a group element in $`\mathcal{G}$.
pub type EncryptionKey = P256Point;

/// To generate a pair of encryption and decryption keys, first the decryption key is drawn uniformly at random from the set of scalars.
/// Then the corresponding encryption key is generated by scalar multiplication of the group generator with the decryption key.
pub fn generate_keys(randomness: &mut Randomness) -> Result<(DecryptionKey, EncryptionKey), Error> {
    let dec_key = random_scalar(randomness, DST_KEYGEN)?;
    let enc_key = p256_point_mul_base(dec_key)?;

    Ok((dec_key, enc_key))
}

/// To encrypt a `message` (a group element) under encryption key `ek`, the encryption key randomized by scalar muliplication with `randomizer`. The result is used as a blinding element by adding it to the message using the group operation.
/// To allow for decryption, the ciphertext also includes as an auxillary component the result of scalar multiplication of the generator by the `randomizer` that was used in the blinding.
///

pub fn encrypt(
    enc_key: P256Point,
    message: Plaintext,
    randomness: &mut Randomness,
) -> Result<Ciphertext, Error> {
    let randomizer = random_scalar(randomness, DST_ENCRYPT)?;

    let blinded_message = p256::point_add(message, p256::p256_point_mul(randomizer, enc_key)?)?;
    let auxillary = p256::p256_point_mul_base(randomizer)?;
    Ok((auxillary, blinded_message))
}

/// To decrypt an Elgamal ciphertext, the holder of the decryption key can multiply the auxillary component of the ciphertext by the decryption key, thus reproducing the blinding element that was calculated during encryption. Adding the negation of this blinding to the blinded message component of the ciphertext recovers the encrypted message.
pub fn decrypt(dec_key: P256Scalar, ctx: Ciphertext) -> Result<Plaintext, Error> {
    let blinding: P256Point = p256::p256_point_mul(dec_key, ctx.0)?;

    p256::point_add(ctx.1, blinding.neg()).map_err(|e| e.into())
}

/// Given the correct public encryption key, it is possible to rerandomize Elgamal ciphertexts without changing the message that is encrypted.
/// To do so, the encryption is multiplied by the fresh randomizer and the result is added to the blinded message component of the ciphertext.
///
/// The auxillary element of the ciphertext is similarly updated by adding the result of multiplying the generator by the fresh randomizer.
pub fn rerandomize(
    enc_key: P256Point,
    ctx: Ciphertext,
    randomness: &mut Randomness,
) -> Result<Ciphertext, Error> {
    let randomizer = random_scalar(randomness, DST_RERANDOMIZE)?;
    let updated_blinding = p256_point_mul(randomizer, enc_key)?;
    let updated_blinded_message = point_add(updated_blinding, ctx.1)?;

    let updated_auxillary = point_add(p256_point_mul_base(randomizer)?, ctx.0)?;
    Ok((updated_auxillary, updated_blinded_message))
}

/// Elgamal ciphertexts under the same encryption key enjoy an additive homomorphism,
///  i.e. if `c1 = Enc(ek, m1)` and `c2 = Enc(pk, m2)`, then we can define an addition operation `+` such that
/// `Dec(dk, c1 + c2) = m1 + m2`.
///
/// The addition is defined as componentwise application of the group operation on the pairs of group elements that make up ciphertexts.
pub fn add_ciphertexts(lhs: Ciphertext, rhs: Ciphertext) -> Result<Ciphertext, Error> {
    let sum_blinded_messages = p256::point_add(lhs.0, rhs.0)?;
    let sum_auxillaries = p256::point_add(lhs.1, rhs.1)?;

    Ok((sum_blinded_messages, sum_auxillaries))
}

/// Since addition as described above is defined via component-wise application of the group operation, repeated addition of a ciphertext with itself
/// can be expressed via componentwise scalar multiplication of the ciphertext.
pub fn scalar_mul_ciphertext(scalar: P256Scalar, ctx: Ciphertext) -> Result<Ciphertext, Error> {
    let multiple_blinded_message = p256_point_mul(scalar, ctx.0)?;
    let multiple_auxillary = p256_point_mul(scalar, ctx.1)?;

    Ok((multiple_blinded_message, multiple_auxillary))
}

#[cfg(test)]
mod test {
    use super::*;
    use p256::random_scalar;
    use rand::RngCore;

    #[test]
    fn test_correctness() {
        let msg = random_element().unwrap();
        let mut uniform_bytes = vec![0u8; 32];
        rand::thread_rng().fill_bytes(&mut uniform_bytes);
        let mut uniform_bytes = Randomness::new(uniform_bytes);
        let (dk, ek) = generate_keys(&mut uniform_bytes).unwrap();

        let ctx = encrypt(ek, msg, &mut Randomness::new(vec![0xab; 32])).unwrap();
        let decryption = decrypt(dk, ctx).unwrap();

        debug_assert_eq!(msg, decryption);
    }

    #[test]
    fn test_rerandomize() {
        let msg = random_element().unwrap();

        let mut uniform_bytes = vec![0u8; 32];
        rand::thread_rng().fill_bytes(&mut uniform_bytes);
        let mut uniform_bytes = Randomness::new(uniform_bytes);
        let (dk, ek) = generate_keys(&mut uniform_bytes).unwrap();

        let ctx = encrypt(ek, msg, &mut Randomness::new(vec![0xab; 32])).unwrap();
        let rctx = rerandomize(ek, ctx, &mut Randomness::new(vec![0xab; 32])).unwrap();
        let decryption = decrypt(dk, rctx).unwrap();

        debug_assert_eq!(msg, decryption);
        assert_ne!(ctx, rctx);
    }

    fn random_element() -> Result<P256Point, Error> {
        let rand = random_scalar(&mut Randomness::new(vec![0xab; 32]), b"ElGamal-Test").unwrap();
        let res = p256_point_mul_base(rand)?;

        Ok(res)
    }
}