elastic_elgamal/
decryption.rs

1//! Verifiable decryption.
2
3use elliptic_curve::rand_core::{CryptoRng, RngCore};
4use merlin::Transcript;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "serde")]
9use crate::serde::ElementHelper;
10use crate::{
11    Ciphertext, DiscreteLogTable, Keypair, PublicKey, VerificationError,
12    alloc::{Vec, vec},
13    group::Group,
14    proofs::{LogEqualityProof, TranscriptForGroup},
15};
16
17/// Verifiable decryption for a certain [`Ciphertext`] in the ElGamal encryption scheme.
18/// Usable both for standalone proofs and in threshold encryption.
19///
20/// # Construction
21///
22/// Decryption is represented by a single group element – the result of combining
23/// a [`SecretKey`](crate::SecretKey) scalar `x` with the random element of the ciphertext `R`
24/// (i.e., `D = [x]R`, the Diffie – Hellman construction).
25/// This element can retrieved using [`Self::as_element()`] and applied to a ciphertext using
26/// [`Self::decrypt()`] or [`Self::decrypt_to_element()`].
27///
28/// The decryption can be proven with the help of a standard [`LogEqualityProof`]. Indeed,
29/// to prove the validity of decryption, it is sufficient to prove `dlog_R(D) = dlog_G(K)`,
30/// where `G` is the conventional group generator and `K = [x]G` is the public key for encryption.
31///
32/// # Examples
33///
34/// `VerifiableDecryption` can be used either within the threshold encryption scheme provided by
35/// the [`sharing`](crate::sharing) module, or independently (for example, if another approach
36/// to secret sharing is used, or if the encryption key is not shared at all).
37/// An example of standalone usage is outlined below:
38///
39/// ```
40/// # use elastic_elgamal::{
41/// #     group::Ristretto, CandidateDecryption, VerifiableDecryption, Keypair, DiscreteLogTable,
42/// # };
43/// # use merlin::Transcript;
44/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
45/// let mut rng = rand::rng();
46/// let keys = Keypair::<Ristretto>::generate(&mut rng);
47/// // Suppose the `keys` holder wants to prove decryption
48/// // of the following ciphertext:
49/// let ciphertext = keys.public().encrypt(42_u64, &mut rng);
50/// let (decryption, proof) = VerifiableDecryption::new(
51///     ciphertext,
52///     &keys,
53///     &mut Transcript::new(b"decryption"),
54///     &mut rng,
55/// );
56///
57/// // This proof can then be universally verified:
58/// let candidate_decryption = CandidateDecryption::from(decryption);
59/// let decryption = candidate_decryption.verify(
60///     ciphertext,
61///     keys.public(),
62///     &proof,
63///     &mut Transcript::new(b"decryption"),
64/// )?;
65/// assert_eq!(
66///     decryption.decrypt(ciphertext, &DiscreteLogTable::new(0..50)),
67///     Some(42)
68/// );
69/// # Ok(())
70/// # }
71/// ```
72#[derive(Debug, Clone, Copy)]
73#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
74#[cfg_attr(feature = "serde", serde(bound = ""))]
75pub struct VerifiableDecryption<G: Group> {
76    #[cfg_attr(feature = "serde", serde(with = "ElementHelper::<G>"))]
77    dh_element: G::Element,
78}
79
80impl<G: Group> VerifiableDecryption<G> {
81    pub(crate) fn from_element(dh_element: G::Element) -> Self {
82        Self { dh_element }
83    }
84
85    /// Creates a decryption for the specified `ciphertext` under `keys` together with
86    /// a zero-knowledge proof of validity.
87    ///
88    /// See [`CandidateDecryption::verify()`] for the verification counterpart.
89    pub fn new<R: CryptoRng + RngCore>(
90        ciphertext: Ciphertext<G>,
91        keys: &Keypair<G>,
92        transcript: &mut Transcript,
93        rng: &mut R,
94    ) -> (Self, LogEqualityProof<G>) {
95        // All inputs except from `ciphertext.blinded_element` are committed in the `proof`,
96        // and it is not necessary to commit in order to allow iteratively recomputing
97        // the ciphertext.
98        transcript.start_proof(b"decryption_with_custom_key");
99
100        let dh_element = ciphertext.random_element * keys.secret().expose_scalar();
101        let proof = LogEqualityProof::new(
102            &PublicKey::from_element(ciphertext.random_element),
103            keys.secret(),
104            (keys.public().as_element(), dh_element),
105            transcript,
106            rng,
107        );
108
109        (Self { dh_element }, proof)
110    }
111
112    /// Returns the group element encapsulated in this decryption.
113    pub fn as_element(&self) -> &G::Element {
114        &self.dh_element
115    }
116
117    /// Serializes this decryption into bytes.
118    pub fn to_bytes(self) -> Vec<u8> {
119        let mut bytes = vec![0_u8; G::ELEMENT_SIZE];
120        G::serialize_element(&self.dh_element, &mut bytes);
121        bytes
122    }
123
124    /// Decrypts the provided ciphertext and returns the produced group element.
125    ///
126    /// As the ciphertext does not include a MAC or another way to assert integrity,
127    /// this operation cannot fail. If the ciphertext is not produced properly (e.g., it targets
128    /// another receiver), the returned group element will be garbage.
129    pub fn decrypt_to_element(&self, encrypted: Ciphertext<G>) -> G::Element {
130        encrypted.blinded_element - self.dh_element
131    }
132
133    /// Decrypts the provided ciphertext and returns the original encrypted value.
134    ///
135    /// `lookup_table` is used to find encrypted values based on the original decrypted
136    /// group element. That is, it must contain all valid plaintext values. If the value
137    /// is not in the table, this method will return `None`.
138    pub fn decrypt(
139        &self,
140        encrypted: Ciphertext<G>,
141        lookup_table: &DiscreteLogTable<G>,
142    ) -> Option<u64> {
143        lookup_table.get(&self.decrypt_to_element(encrypted))
144    }
145}
146
147/// Candidate for a [`VerifiableDecryption`] that is not yet verified. This presentation should be
148/// used for decryption data retrieved from an untrusted source.
149///
150/// Decryption data can be verified using [`Self::verify()`]. The threshold encryption scheme
151/// implemented in the [`sharing`](crate::sharing) module has its own verification procedure
152/// in [`PublicKeySet`].
153///
154/// [`PublicKeySet`]: crate::sharing::PublicKeySet
155///
156/// # Examples
157///
158/// See [`VerifiableDecryption`] for an example of usage.
159#[derive(Debug, Clone, Copy)]
160#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
161#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
162pub struct CandidateDecryption<G: Group> {
163    inner: VerifiableDecryption<G>,
164}
165
166impl<G: Group> CandidateDecryption<G> {
167    /// Deserializes decryption data from `bytes`. Returns `None` if the data is malformed.
168    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
169        if bytes.len() == G::ELEMENT_SIZE {
170            let dh_element = G::deserialize_element(bytes)?;
171            Some(Self {
172                inner: VerifiableDecryption { dh_element },
173            })
174        } else {
175            None
176        }
177    }
178
179    pub(super) fn dh_element(self) -> G::Element {
180        self.inner.dh_element
181    }
182
183    /// Verifies this as decryption for `ciphertext` under `key` using the provided
184    /// zero-knowledge `proof`.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if `proof` does not verify.
189    pub fn verify(
190        self,
191        ciphertext: Ciphertext<G>,
192        key: &PublicKey<G>,
193        proof: &LogEqualityProof<G>,
194        transcript: &mut Transcript,
195    ) -> Result<VerifiableDecryption<G>, VerificationError> {
196        transcript.start_proof(b"decryption_with_custom_key");
197
198        let dh_element = self.dh_element();
199        proof.verify(
200            &PublicKey::from_element(ciphertext.random_element),
201            (key.as_element(), dh_element),
202            transcript,
203        )?;
204        Ok(self.inner)
205    }
206
207    /// Converts this candidate decryption into a [`VerifiableDecryption`]
208    /// **without** verifying it.
209    /// This is only semantically correct if the data was verified in some other way.
210    pub fn into_unchecked(self) -> VerifiableDecryption<G> {
211        self.inner
212    }
213}
214
215impl<G: Group> From<VerifiableDecryption<G>> for CandidateDecryption<G> {
216    fn from(decryption: VerifiableDecryption<G>) -> Self {
217        Self { inner: decryption }
218    }
219}