1#[cfg(target_arch = "wasm32")]
12use wasm_bindgen::prelude::*;
13#[cfg(not(target_os = "solana"))]
14use {
15 crate::{
16 encryption::{
17 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
18 pedersen::{PedersenCommitment, PedersenOpening, G, H},
19 },
20 sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
21 UNIT_LEN,
22 },
23 curve25519_dalek::traits::MultiscalarMul,
24 rand::rngs::OsRng,
25 zeroize::Zeroize,
26};
27use {
28 crate::{
29 sigma_proofs::errors::{EqualityProofVerificationError, SigmaProofVerificationError},
30 transcript::TranscriptProtocol,
31 },
32 curve25519_dalek::{
33 ristretto::{CompressedRistretto, RistrettoPoint},
34 scalar::Scalar,
35 traits::{IsIdentity, VartimeMultiscalarMul},
36 },
37 merlin::Transcript,
38};
39
40const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = UNIT_LEN * 6;
42
43#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
47#[allow(non_snake_case)]
48#[derive(Clone)]
49pub struct CiphertextCommitmentEqualityProof {
50 Y_0: CompressedRistretto,
51 Y_1: CompressedRistretto,
52 Y_2: CompressedRistretto,
53 z_s: Scalar,
54 z_x: Scalar,
55 z_r: Scalar,
56}
57
58#[allow(non_snake_case)]
59#[cfg(not(target_os = "solana"))]
60impl CiphertextCommitmentEqualityProof {
61 pub fn new(
78 keypair: &ElGamalKeypair,
79 ciphertext: &ElGamalCiphertext,
80 opening: &PedersenOpening,
81 amount: u64,
82 transcript: &mut Transcript,
83 ) -> Self {
84 transcript.ciphertext_commitment_equality_proof_domain_separator();
85
86 let P = keypair.pubkey().get_point();
88 let D = ciphertext.handle.get_point();
89
90 let s = keypair.secret().get_scalar();
91 let mut x = Scalar::from(amount);
92 let r = opening.get_scalar();
93
94 let mut y_s = Scalar::random(&mut OsRng);
96 let mut y_x = Scalar::random(&mut OsRng);
97 let mut y_r = Scalar::random(&mut OsRng);
98
99 let Y_0 = (&y_s * P).compress();
100 let Y_1 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&G, D]).compress();
101 let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&G, &(*H)]).compress();
102
103 transcript.append_point(b"Y_0", &Y_0);
105 transcript.append_point(b"Y_1", &Y_1);
106 transcript.append_point(b"Y_2", &Y_2);
107
108 let c = transcript.challenge_scalar(b"c");
109
110 let z_s = &(&c * s) + &y_s;
112 let z_x = &(&c * &x) + &y_x;
113 let z_r = &(&c * r) + &y_r;
114
115 transcript.append_scalar(b"z_s", &z_s);
117 transcript.append_scalar(b"z_x", &z_x);
118 transcript.append_scalar(b"z_r", &z_r);
119 let _w = transcript.challenge_scalar(b"w");
120
121 x.zeroize();
123 y_s.zeroize();
124 y_x.zeroize();
125 y_r.zeroize();
126
127 CiphertextCommitmentEqualityProof {
128 Y_0,
129 Y_1,
130 Y_2,
131 z_s,
132 z_x,
133 z_r,
134 }
135 }
136
137 pub fn verify(
144 self,
145 pubkey: &ElGamalPubkey,
146 ciphertext: &ElGamalCiphertext,
147 commitment: &PedersenCommitment,
148 transcript: &mut Transcript,
149 ) -> Result<(), EqualityProofVerificationError> {
150 transcript.ciphertext_commitment_equality_proof_domain_separator();
151
152 let P = pubkey.get_point();
154 let C_ciphertext = ciphertext.commitment.get_point();
155 let D = ciphertext.handle.get_point();
156 let C_commitment = commitment.get_point();
157
158 transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
160 transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
161 transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
162
163 let c = transcript.challenge_scalar(b"c");
164
165 transcript.append_scalar(b"z_s", &self.z_s);
166 transcript.append_scalar(b"z_x", &self.z_x);
167 transcript.append_scalar(b"z_r", &self.z_r);
168 let w = transcript.challenge_scalar(b"w"); let ww = &w * &w;
170
171 let w_negated = -&w;
172 let ww_negated = -&ww;
173
174 let Y_0 = self
176 .Y_0
177 .decompress()
178 .ok_or(SigmaProofVerificationError::Deserialization)?;
179 let Y_1 = self
180 .Y_1
181 .decompress()
182 .ok_or(SigmaProofVerificationError::Deserialization)?;
183 let Y_2 = self
184 .Y_2
185 .decompress()
186 .ok_or(SigmaProofVerificationError::Deserialization)?;
187
188 let check = RistrettoPoint::vartime_multiscalar_mul(
189 vec![
190 &self.z_s, &(-&c), &(-&Scalar::ONE), &(&w * &self.z_x), &(&w * &self.z_s), &(&w_negated * &c), &w_negated, &(&ww * &self.z_x), &(&ww * &self.z_r), &(&ww_negated * &c), &ww_negated, ],
202 vec![
203 P, &(*H), &Y_0, &G, D, C_ciphertext, &Y_1, &G, &(*H), C_commitment, &Y_2, ],
215 );
216
217 if check.is_identity() {
218 Ok(())
219 } else {
220 Err(SigmaProofVerificationError::AlgebraicRelation.into())
221 }
222 }
223
224 pub fn to_bytes(&self) -> [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN] {
225 let mut buf = [0_u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN];
226 let mut chunks = buf.chunks_mut(UNIT_LEN);
227 chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
228 chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
229 chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
230 chunks.next().unwrap().copy_from_slice(self.z_s.as_bytes());
231 chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
232 chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
233 buf
234 }
235
236 pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofVerificationError> {
237 let mut chunks = bytes.chunks(UNIT_LEN);
238 let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
239 let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
240 let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
241 let z_s = canonical_scalar_from_optional_slice(chunks.next())?;
242 let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
243 let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
244
245 Ok(CiphertextCommitmentEqualityProof {
246 Y_0,
247 Y_1,
248 Y_2,
249 z_s,
250 z_x,
251 z_r,
252 })
253 }
254}
255
256#[cfg(test)]
257mod test {
258 use {
259 super::*,
260 crate::{
261 encryption::{
262 elgamal::ElGamalSecretKey,
263 pedersen::Pedersen,
264 pod::{
265 elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
266 pedersen::PodPedersenCommitment,
267 },
268 },
269 sigma_proofs::pod::PodCiphertextCommitmentEqualityProof,
270 },
271 std::str::FromStr,
272 };
273
274 #[test]
275 fn test_ciphertext_commitment_equality_proof_correctness() {
276 let keypair = ElGamalKeypair::new_rand();
278 let message: u64 = 55;
279
280 let ciphertext = keypair.pubkey().encrypt(message);
281 let (commitment, opening) = Pedersen::new(message);
282
283 let mut prover_transcript = Transcript::new(b"Test");
284 let mut verifier_transcript = Transcript::new(b"Test");
285
286 let proof = CiphertextCommitmentEqualityProof::new(
287 &keypair,
288 &ciphertext,
289 &opening,
290 message,
291 &mut prover_transcript,
292 );
293
294 proof
295 .verify(
296 keypair.pubkey(),
297 &ciphertext,
298 &commitment,
299 &mut verifier_transcript,
300 )
301 .unwrap();
302
303 let keypair = ElGamalKeypair::new_rand();
305 let encrypted_message: u64 = 55;
306 let committed_message: u64 = 77;
307
308 let ciphertext = keypair.pubkey().encrypt(encrypted_message);
309 let (commitment, opening) = Pedersen::new(committed_message);
310
311 let mut prover_transcript = Transcript::new(b"Test");
312 let mut verifier_transcript = Transcript::new(b"Test");
313
314 let proof = CiphertextCommitmentEqualityProof::new(
315 &keypair,
316 &ciphertext,
317 &opening,
318 encrypted_message,
319 &mut prover_transcript,
320 );
321
322 assert!(proof
323 .verify(
324 keypair.pubkey(),
325 &ciphertext,
326 &commitment,
327 &mut verifier_transcript
328 )
329 .is_err());
330
331 assert_eq!(
332 prover_transcript.challenge_scalar(b"test"),
333 verifier_transcript.challenge_scalar(b"test"),
334 )
335 }
336
337 #[test]
338 fn test_ciphertext_commitment_equality_proof_edge_cases() {
339 let public = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap();
341 let secret = ElGamalSecretKey::new_rand();
342
343 let elgamal_keypair = ElGamalKeypair::new_for_tests(public, secret);
344
345 let message: u64 = 55;
346 let ciphertext = elgamal_keypair.pubkey().encrypt(message);
347 let (commitment, opening) = Pedersen::new(message);
348
349 let mut prover_transcript = Transcript::new(b"Test");
350 let mut verifier_transcript = Transcript::new(b"Test");
351
352 let proof = CiphertextCommitmentEqualityProof::new(
353 &elgamal_keypair,
354 &ciphertext,
355 &opening,
356 message,
357 &mut prover_transcript,
358 );
359
360 assert!(proof
361 .verify(
362 elgamal_keypair.pubkey(),
363 &ciphertext,
364 &commitment,
365 &mut verifier_transcript
366 )
367 .is_err());
368
369 let elgamal_keypair = ElGamalKeypair::new_rand();
372
373 let message: u64 = 0;
374 let ciphertext = ElGamalCiphertext::from_bytes(&[0u8; 64]).unwrap();
375 let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap();
376 let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap();
377
378 let mut prover_transcript = Transcript::new(b"Test");
379 let mut verifier_transcript = Transcript::new(b"Test");
380
381 let proof = CiphertextCommitmentEqualityProof::new(
382 &elgamal_keypair,
383 &ciphertext,
384 &opening,
385 message,
386 &mut prover_transcript,
387 );
388
389 proof
390 .verify(
391 elgamal_keypair.pubkey(),
392 &ciphertext,
393 &commitment,
394 &mut verifier_transcript,
395 )
396 .unwrap();
397
398 let elgamal_keypair = ElGamalKeypair::new_rand();
401
402 let message: u64 = 0;
403 let ciphertext = elgamal_keypair.pubkey().encrypt(message);
404 let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap();
405 let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap();
406
407 let mut prover_transcript = Transcript::new(b"Test");
408 let mut verifier_transcript = Transcript::new(b"Test");
409
410 let proof = CiphertextCommitmentEqualityProof::new(
411 &elgamal_keypair,
412 &ciphertext,
413 &opening,
414 message,
415 &mut prover_transcript,
416 );
417
418 proof
419 .verify(
420 elgamal_keypair.pubkey(),
421 &ciphertext,
422 &commitment,
423 &mut verifier_transcript,
424 )
425 .unwrap();
426
427 let elgamal_keypair = ElGamalKeypair::new_rand();
430
431 let message: u64 = 0;
432 let ciphertext = ElGamalCiphertext::from_bytes(&[0u8; 64]).unwrap();
433 let (commitment, opening) = Pedersen::new(message);
434
435 let mut prover_transcript = Transcript::new(b"Test");
436 let mut verifier_transcript = Transcript::new(b"Test");
437
438 let proof = CiphertextCommitmentEqualityProof::new(
439 &elgamal_keypair,
440 &ciphertext,
441 &opening,
442 message,
443 &mut prover_transcript,
444 );
445
446 proof
447 .verify(
448 elgamal_keypair.pubkey(),
449 &ciphertext,
450 &commitment,
451 &mut verifier_transcript,
452 )
453 .unwrap();
454 }
455
456 #[test]
457 fn test_ciphertext_commitment_equality_proof_string() {
458 let pubkey_str = "JNa7rRrDm35laU7f8HPds1PmHoZEPSHFK/M+aTtEhAk=";
459 let pod_pubkey = PodElGamalPubkey::from_str(pubkey_str).unwrap();
460 let pubkey: ElGamalPubkey = pod_pubkey.try_into().unwrap();
461
462 let ciphertext_str = "RAXnbQ/DPRlYAWmD+iHRNqMDv7oQcPgQ7OejRzj4bxVy2qOJNziqqDOC7VP3iTW1+z/jckW4smA3EUF7i/r8Rw==";
463 let pod_ciphertext = PodElGamalCiphertext::from_str(ciphertext_str).unwrap();
464 let ciphertext: ElGamalCiphertext = pod_ciphertext.try_into().unwrap();
465
466 let commitment_str = "ngPTYvbY9P5l6aOfr7bLQiI+0HZsw8GBgiumdW3tNzw=";
467 let pod_commitment = PodPedersenCommitment::from_str(commitment_str).unwrap();
468 let commitment: PedersenCommitment = pod_commitment.try_into().unwrap();
469
470 let proof_str = "cCZySLxB2XJdGyDvckVBm2OWiXqf7Jf54IFoDuLJ4G+ySj+lh5DbaDMHDhuozQC9tDWtk2mFITuaXOc5Zw3nZ2oEvVYpqv5hN+k5dx9k8/nZKabUCkZwx310z7x4fE4Np5SY9PYia1hkrq9AWq0b3v97XvW1+XCSSxuflvBk5wsdaQQ+ZgcmPnKWKjHfRwmU2k5iVgYzs2VmvZa5E3OWBoM/M2yFNvukY+FCC2YMnspO0c4lNBr/vDFQuHdW0OgJ";
471 let pod_proof = PodCiphertextCommitmentEqualityProof::from_str(proof_str).unwrap();
472 let proof: CiphertextCommitmentEqualityProof = pod_proof.try_into().unwrap();
473
474 let mut verifier_transcript = Transcript::new(b"Test");
475
476 proof
477 .verify(&pubkey, &ciphertext, &commitment, &mut verifier_transcript)
478 .unwrap();
479 }
480}