elastic_elgamal/proofs/log_equality.rs
1//! [`LogEqualityProof`] and related logic.
2
3use merlin::Transcript;
4use rand_core::{CryptoRng, RngCore};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "serde")]
9use crate::serde::ScalarHelper;
10use crate::{
11 alloc::{vec, Vec},
12 group::Group,
13 proofs::{TranscriptForGroup, VerificationError},
14 PublicKey, SecretKey,
15};
16
17/// Zero-knowledge proof of equality of two discrete logarithms in different bases,
18/// aka Chaum–Pedersen protocol.
19///
20/// # Construction
21///
22/// This proof is a result of the [Fiat–Shamir transform][fst] applied to a standard
23/// ZKP of equality of the two discrete logs in different bases.
24///
25/// - Public parameters of the proof are the two bases `G` and `K` in a prime-order group
26/// in which discrete log problem is believed to be hard.
27/// - Prover and verifier both know group elements `R` and `B`, which presumably have
28/// the same discrete log in bases `G` and `K` respectively.
29/// - Prover additionally knows the discrete log in question: `r = dlog_G(R) = dlog_K(B)`.
30///
31/// The interactive proof is specified as a sigma protocol (see, e.g., [this course])
32/// as follows:
33///
34/// 1. **Commitment:** The prover generates random scalar `x`. The prover sends to the verifier
35/// `X_G = [x]G` and `X_K = [x]K`.
36/// 2. **Challenge:** The verifier sends to the prover random scalar `c`.
37/// 3. **Response:** The prover computes scalar `s = x + cr` and sends it to the verifier.
38///
39/// Verification equations are:
40///
41/// ```text
42/// [s]G ?= X_G + [c]R;
43/// [s]K ?= X_K + [c]B.
44/// ```
45///
46/// In the non-interactive version of the proof, challenge `c` is derived from `hash(M)`,
47/// where `hash()` is a cryptographically secure hash function, and `M` is an optional message
48/// verified together with the proof (cf. public-key digital signatures). If `M` is set, we
49/// use a proof as a *signature of knowledge*. This allows to tie the proof to the context,
50/// so it cannot be (re)used in other contexts.
51///
52/// To reduce the size of the proof, we use the trick underpinning ring signature constructions.
53/// Namely, we represent the proof as `(c, s)`; during verification, we restore `X_G`, `X_K`
54/// from the original verification equations above.
55///
56/// # Implementation details
57///
58/// - The proof is serialized as 2 scalars: `(c, s)`.
59/// - Proof generation is constant-time. Verification is **not** constant-time.
60/// - Challenge `c` is derived using [`Transcript`] API.
61///
62/// # Examples
63///
64/// ```
65/// # use elastic_elgamal::{group::Ristretto, Keypair, SecretKey, LogEqualityProof};
66/// # use merlin::Transcript;
67/// # use rand::thread_rng;
68/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
69/// let mut rng = thread_rng();
70/// let (log_base, _) =
71/// Keypair::<Ristretto>::generate(&mut rng).into_tuple();
72/// let (power_g, discrete_log) =
73/// Keypair::<Ristretto>::generate(&mut rng).into_tuple();
74/// let power_k = log_base.as_element() * discrete_log.expose_scalar();
75///
76/// let proof = LogEqualityProof::new(
77/// &log_base,
78/// &discrete_log,
79/// (power_g.as_element(), power_k),
80/// &mut Transcript::new(b"custom_proof"),
81/// &mut rng,
82/// );
83/// proof.verify(
84/// &log_base,
85/// (power_g.as_element(), power_k),
86/// &mut Transcript::new(b"custom_proof"),
87/// )?;
88/// # Ok(())
89/// # }
90/// ```
91///
92/// [fst]: https://en.wikipedia.org/wiki/Fiat%E2%80%93Shamir_heuristic
93/// [this course]: http://www.cs.au.dk/~ivan/Sigma.pdf
94#[derive(Debug, Clone, Copy)]
95#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96#[cfg_attr(feature = "serde", serde(bound = ""))]
97pub struct LogEqualityProof<G: Group> {
98 #[cfg_attr(feature = "serde", serde(with = "ScalarHelper::<G>"))]
99 challenge: G::Scalar,
100 #[cfg_attr(feature = "serde", serde(with = "ScalarHelper::<G>"))]
101 response: G::Scalar,
102}
103
104impl<G: Group> LogEqualityProof<G> {
105 /// Creates a new proof.
106 ///
107 /// # Parameters
108 ///
109 /// - `log_base` is the second discrete log base (`K` in the notation above). The first
110 /// log base is always the [`Group`] generator.
111 /// - `secret` is the discrete log (`r` in the notation above).
112 /// - `powers` are `[r]G` and `[r]K`, respectively. It is **not** checked whether `r`
113 /// is a discrete log of these powers; if this is not the case, the constructed proof
114 /// will not [`verify`](Self::verify()).
115 pub fn new<R: CryptoRng + RngCore>(
116 log_base: &PublicKey<G>,
117 secret: &SecretKey<G>,
118 powers: (G::Element, G::Element),
119 transcript: &mut Transcript,
120 rng: &mut R,
121 ) -> Self {
122 transcript.start_proof(b"log_eq");
123 transcript.append_element_bytes(b"K", log_base.as_bytes());
124 transcript.append_element::<G>(b"[r]G", &powers.0);
125 transcript.append_element::<G>(b"[r]K", &powers.1);
126
127 let random_scalar = SecretKey::<G>::generate(rng);
128 transcript.append_element::<G>(b"[x]G", &G::mul_generator(random_scalar.expose_scalar()));
129 transcript.append_element::<G>(
130 b"[x]K",
131 &(log_base.as_element() * random_scalar.expose_scalar()),
132 );
133 let challenge = transcript.challenge_scalar::<G>(b"c");
134 let response = challenge * secret.expose_scalar() + random_scalar.expose_scalar();
135
136 Self {
137 challenge,
138 response,
139 }
140 }
141
142 /// Verifies this proof.
143 ///
144 /// # Parameters
145 ///
146 /// - `log_base` is the second discrete log base (`K` in the notation above). The first
147 /// log base is always the [`Group`] generator.
148 /// - `powers` are group elements presumably equal to `[r]G` and `[r]K` respectively,
149 /// where `r` is a secret scalar.
150 ///
151 /// # Errors
152 ///
153 /// Returns an error if this proof does not verify.
154 pub fn verify(
155 &self,
156 log_base: &PublicKey<G>,
157 powers: (G::Element, G::Element),
158 transcript: &mut Transcript,
159 ) -> Result<(), VerificationError> {
160 let commitments = (
161 G::vartime_double_mul_generator(&-self.challenge, powers.0, &self.response),
162 G::vartime_multi_mul(
163 &[-self.challenge, self.response],
164 [powers.1, log_base.as_element()],
165 ),
166 );
167
168 transcript.start_proof(b"log_eq");
169 transcript.append_element_bytes(b"K", log_base.as_bytes());
170 transcript.append_element::<G>(b"[r]G", &powers.0);
171 transcript.append_element::<G>(b"[r]K", &powers.1);
172 transcript.append_element::<G>(b"[x]G", &commitments.0);
173 transcript.append_element::<G>(b"[x]K", &commitments.1);
174 let expected_challenge = transcript.challenge_scalar::<G>(b"c");
175
176 if expected_challenge == self.challenge {
177 Ok(())
178 } else {
179 Err(VerificationError::ChallengeMismatch)
180 }
181 }
182
183 /// Serializes this proof into bytes. As described [above](#implementation-details),
184 /// the is serialized as 2 scalars: `(c, s)`, i.e., challenge and response.
185 pub fn to_bytes(self) -> Vec<u8> {
186 let mut bytes = vec![0_u8; 2 * G::SCALAR_SIZE];
187 G::serialize_scalar(&self.challenge, &mut bytes[..G::SCALAR_SIZE]);
188 G::serialize_scalar(&self.response, &mut bytes[G::SCALAR_SIZE..]);
189 bytes
190 }
191
192 /// Attempts to parse the proof from `bytes`. Returns `None` if `bytes` do not represent
193 /// a well-formed proof.
194 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
195 if bytes.len() != 2 * G::SCALAR_SIZE {
196 return None;
197 }
198
199 let challenge = G::deserialize_scalar(&bytes[..G::SCALAR_SIZE])?;
200 let response = G::deserialize_scalar(&bytes[G::SCALAR_SIZE..])?;
201 Some(Self {
202 challenge,
203 response,
204 })
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use rand::thread_rng;
211
212 use super::*;
213 use crate::group::Ristretto;
214
215 type Keypair = crate::Keypair<Ristretto>;
216
217 #[test]
218 fn log_equality_basics() {
219 let mut rng = thread_rng();
220 let log_base = Keypair::generate(&mut rng).public().clone();
221
222 for _ in 0..100 {
223 let (generator_val, secret) = Keypair::generate(&mut rng).into_tuple();
224 let key_val = log_base.as_element() * secret.expose_scalar();
225 let proof = LogEqualityProof::new(
226 &log_base,
227 &secret,
228 (generator_val.as_element(), key_val),
229 &mut Transcript::new(b"testing_log_equality"),
230 &mut rng,
231 );
232
233 proof
234 .verify(
235 &log_base,
236 (generator_val.as_element(), key_val),
237 &mut Transcript::new(b"testing_log_equality"),
238 )
239 .unwrap();
240 }
241 }
242}