noah_crypto/basic/
elgamal.rs

1use noah_algebra::ristretto::RistrettoPoint;
2use noah_algebra::{
3    hash::{Hash, Hasher},
4    prelude::*,
5};
6
7#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
8/// The ElGamal encryption key/public key.
9pub struct ElGamalEncKey<G>(pub G);
10
11#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
12/// The ElGamal decryption key/secret key.
13pub struct ElGamalDecKey<S>(pub(crate) S);
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16/// An ElGamal ciphertext.
17pub struct ElGamalCiphertext<G> {
18    /// `e1` = `r * G`
19    pub e1: G,
20    /// `e2` = `m * G + r * pk`
21    pub e2: G,
22}
23
24impl Hash for ElGamalEncKey<RistrettoPoint> {
25    fn hash<H: Hasher>(&self, state: &mut H) {
26        self.0.to_compressed_bytes().as_slice().hash(state);
27    }
28}
29
30impl NoahFromToBytes for ElGamalCiphertext<RistrettoPoint> {
31    fn noah_to_bytes(&self) -> Vec<u8> {
32        let mut v = vec![];
33        v.extend_from_slice(self.e1.to_compressed_bytes().as_slice());
34        v.extend_from_slice(self.e2.to_compressed_bytes().as_slice());
35        v
36    }
37    fn noah_from_bytes(bytes: &[u8]) -> Result<Self> {
38        let e1 = RistrettoPoint::from_compressed_bytes(&bytes[0..RistrettoPoint::COMPRESSED_LEN])
39            .c(d!(NoahError::DeserializationError))?;
40        let e2 = RistrettoPoint::from_compressed_bytes(&bytes[RistrettoPoint::COMPRESSED_LEN..])
41            .c(d!(NoahError::DeserializationError))?;
42        Ok(ElGamalCiphertext { e1, e2 })
43    }
44}
45
46/// Return an ElGamal key pair as `(sk, pk = sk * G)`
47pub fn elgamal_key_gen<R: CryptoRng + RngCore, G: Group>(
48    prng: &mut R,
49) -> (ElGamalDecKey<G::ScalarType>, ElGamalEncKey<G>) {
50    let base = G::get_base();
51    let secret_key = ElGamalDecKey(G::ScalarType::random(prng));
52    let public_key = ElGamalEncKey(base.mul(&secret_key.0));
53    (secret_key, public_key)
54}
55
56/// Return an ElGamal ciphertext pair as `(r * G, m * G + r * pk)`, where `G` is a base point on the curve
57pub fn elgamal_encrypt<G: Group>(
58    m: &G::ScalarType,
59    r: &G::ScalarType,
60    pub_key: &ElGamalEncKey<G>,
61) -> ElGamalCiphertext<G> {
62    let base = G::get_base();
63    let e1 = base.mul(r);
64    let e2 = base.mul(m).add(&(pub_key.0).mul(r));
65
66    ElGamalCiphertext::<G> { e1, e2 }
67}
68
69/// Verify that the ElGamal ciphertext encrypts m by checking `ctext.e2 - ctext.e1 * sk = m * G`
70pub fn elgamal_verify<G: Group>(
71    m: &G::ScalarType,
72    ctext: &ElGamalCiphertext<G>,
73    sec_key: &ElGamalDecKey<G::ScalarType>,
74) -> Result<()> {
75    let base = G::get_base();
76    if base.mul(m).add(&ctext.e1.mul(&sec_key.0)) == ctext.e2 {
77        Ok(())
78    } else {
79        Err(eg!(NoahError::ElGamalVerificationError))
80    }
81}
82
83/// Perform a partial decryption for the ElGamal ciphertext that returns `m * G`
84pub fn elgamal_partial_decrypt<G: Group>(
85    ctext: &ElGamalCiphertext<G>,
86    sec_key: &ElGamalDecKey<G::ScalarType>,
87) -> G {
88    ctext.e2.sub(&ctext.e1.mul(&sec_key.0))
89}
90
91#[cfg(test)]
92mod elgamal_test {
93    use noah_algebra::bls12_381::BLSGt;
94    use noah_algebra::bls12_381::BLSG1;
95    use noah_algebra::bls12_381::BLSG2;
96    use noah_algebra::prelude::*;
97    use noah_algebra::ristretto::RistrettoPoint;
98
99    fn verification<G: Group>() {
100        let mut prng = test_rng();
101
102        let (secret_key, public_key) = super::elgamal_key_gen::<_, G>(&mut prng);
103
104        let m = G::ScalarType::from(100u32);
105        let r = G::ScalarType::random(&mut prng);
106        let ctext = super::elgamal_encrypt::<G>(&m, &r, &public_key);
107        pnk!(super::elgamal_verify::<G>(&m, &ctext, &secret_key));
108
109        let wrong_m = G::ScalarType::from(99u32);
110        let err = super::elgamal_verify(&wrong_m, &ctext, &secret_key)
111            .err()
112            .unwrap();
113        msg_eq!(NoahError::ElGamalVerificationError, err);
114    }
115
116    fn decryption<G: Group>() {
117        let mut prng = test_rng();
118        let (secret_key, public_key) = super::elgamal_key_gen::<_, G>(&mut prng);
119
120        let mu32 = 100u32;
121        let m = G::ScalarType::from(mu32);
122        let r = G::ScalarType::random(&mut prng);
123        let ctext = super::elgamal_encrypt(&m, &r, &public_key);
124        pnk!(super::elgamal_verify(&m, &ctext, &secret_key));
125
126        let m = G::ScalarType::from(u64::MAX);
127        let ctext = super::elgamal_encrypt(&m, &r, &public_key);
128        pnk!(super::elgamal_verify(&m, &ctext, &secret_key));
129    }
130
131    #[test]
132    fn verify() {
133        verification::<RistrettoPoint>();
134        verification::<BLSG1>();
135        verification::<BLSG2>();
136        verification::<BLSGt>();
137    }
138
139    #[test]
140    fn decrypt() {
141        decryption::<RistrettoPoint>();
142        decryption::<BLSG1>();
143        decryption::<BLSG2>();
144        decryption::<BLSGt>();
145    }
146}