handles_3.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. //! The grouped ciphertext with 3 handles validity sigma proof system.
  2. //!
  3. //! This ciphertext validity proof is defined with respect to a Pedersen commitment and three
  4. //! decryption handles. The proof certifies that a given Pedersen commitment can be decrypted using
  5. //! ElGamal private keys that are associated with each of the three decryption handles. To generate
  6. //! the proof, a prover must provide the Pedersen opening associated with the commitment.
  7. //!
  8. //! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
  9. //! zero-knowledge in the random oracle model.
  10. #[cfg(target_arch = "wasm32")]
  11. use wasm_bindgen::prelude::*;
  12. #[cfg(not(target_os = "solana"))]
  13. use {
  14. crate::{
  15. encryption::{
  16. elgamal::{DecryptHandle, ElGamalPubkey},
  17. pedersen::{PedersenCommitment, PedersenOpening, G, H},
  18. },
  19. sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
  20. UNIT_LEN,
  21. },
  22. curve25519_dalek::traits::MultiscalarMul,
  23. rand::rngs::OsRng,
  24. zeroize::Zeroize,
  25. };
  26. use {
  27. crate::{
  28. sigma_proofs::errors::{SigmaProofVerificationError, ValidityProofVerificationError},
  29. transcript::TranscriptProtocol,
  30. },
  31. curve25519_dalek::{
  32. ristretto::{CompressedRistretto, RistrettoPoint},
  33. scalar::Scalar,
  34. traits::{IsIdentity, VartimeMultiscalarMul},
  35. },
  36. merlin::Transcript,
  37. };
  38. /// Byte length of a grouped ciphertext validity proof for 3 handles
  39. const GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 6;
  40. /// The grouped ciphertext validity proof for 3 handles.
  41. ///
  42. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
  43. #[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
  44. #[allow(non_snake_case)]
  45. #[derive(Clone)]
  46. pub struct GroupedCiphertext3HandlesValidityProof {
  47. Y_0: CompressedRistretto,
  48. Y_1: CompressedRistretto,
  49. Y_2: CompressedRistretto,
  50. Y_3: CompressedRistretto,
  51. z_r: Scalar,
  52. z_x: Scalar,
  53. }
  54. #[allow(non_snake_case)]
  55. #[cfg(not(target_os = "solana"))]
  56. impl GroupedCiphertext3HandlesValidityProof {
  57. /// Creates a grouped ciphertext with 3 handles validity proof.
  58. ///
  59. /// The function does *not* hash the public keys, commitment, or decryption handles into the
  60. /// transcript. For security, the caller (the main protocol) should hash these public
  61. /// components prior to invoking this constructor.
  62. ///
  63. /// This function is randomized. It uses `OsRng` internally to generate random scalars.
  64. ///
  65. /// Note that the proof constructor does not take the actual Pedersen commitment or decryption
  66. /// handles as input; it only takes the associated Pedersen opening instead.
  67. ///
  68. /// * `first_pubkey` - The first ElGamal public key
  69. /// * `second_pubkey` - The second ElGamal public key
  70. /// * `third_pubkey` - The third ElGamal public key
  71. /// * `amount` - The committed message in the commitment
  72. /// * `opening` - The opening associated with the Pedersen commitment
  73. /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
  74. pub fn new<T: Into<Scalar>>(
  75. first_pubkey: &ElGamalPubkey,
  76. second_pubkey: &ElGamalPubkey,
  77. third_pubkey: &ElGamalPubkey,
  78. amount: T,
  79. opening: &PedersenOpening,
  80. transcript: &mut Transcript,
  81. ) -> Self {
  82. transcript.grouped_ciphertext_validity_proof_domain_separator(3);
  83. // extract the relevant scalar and Ristretto points from the inputs
  84. let P_first = first_pubkey.get_point();
  85. let P_second = second_pubkey.get_point();
  86. let P_third = third_pubkey.get_point();
  87. let x = amount.into();
  88. let r = opening.get_scalar();
  89. // generate random masking factors that also serves as nonces
  90. let mut y_r = Scalar::random(&mut OsRng);
  91. let mut y_x = Scalar::random(&mut OsRng);
  92. let Y_0 = RistrettoPoint::multiscalar_mul(vec![&y_r, &y_x], vec![&(*H), &G]).compress();
  93. let Y_1 = (&y_r * P_first).compress();
  94. let Y_2 = (&y_r * P_second).compress();
  95. let Y_3 = (&y_r * P_third).compress();
  96. // record masking factors in transcript and get challenges
  97. transcript.append_point(b"Y_0", &Y_0);
  98. transcript.append_point(b"Y_1", &Y_1);
  99. transcript.append_point(b"Y_2", &Y_2);
  100. transcript.append_point(b"Y_3", &Y_3);
  101. let c = transcript.challenge_scalar(b"c");
  102. transcript.challenge_scalar(b"w");
  103. // compute masked message and opening
  104. let z_r = &(&c * r) + &y_r;
  105. let z_x = &(&c * &x) + &y_x;
  106. y_r.zeroize();
  107. y_x.zeroize();
  108. Self {
  109. Y_0,
  110. Y_1,
  111. Y_2,
  112. Y_3,
  113. z_r,
  114. z_x,
  115. }
  116. }
  117. /// Verifies a grouped ciphertext with 3 handles validity proof.
  118. ///
  119. /// * `commitment` - The Pedersen commitment
  120. /// * `first_pubkey` - The first ElGamal public key
  121. /// * `second_pubkey` - The second ElGamal public key
  122. /// * `third_pubkey` - The third ElGamal public key
  123. /// * `first_handle` - The first decryption handle
  124. /// * `second_handle` - The second decryption handle
  125. /// * `third_handle` - The third decryption handle
  126. /// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
  127. pub fn verify(
  128. self,
  129. commitment: &PedersenCommitment,
  130. first_pubkey: &ElGamalPubkey,
  131. second_pubkey: &ElGamalPubkey,
  132. third_pubkey: &ElGamalPubkey,
  133. first_handle: &DecryptHandle,
  134. second_handle: &DecryptHandle,
  135. third_handle: &DecryptHandle,
  136. transcript: &mut Transcript,
  137. ) -> Result<(), ValidityProofVerificationError> {
  138. transcript.grouped_ciphertext_validity_proof_domain_separator(3);
  139. // include `Y_0`, `Y_1`, `Y_2` to transcript and extract challenges
  140. transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
  141. transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
  142. transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
  143. // the point `Y_3` is defined with respect to the third public key and can be zero if the
  144. // third public key is zero
  145. transcript.append_point(b"Y_3", &self.Y_3);
  146. let c = transcript.challenge_scalar(b"c");
  147. transcript.append_scalar(b"z_r", &self.z_r);
  148. transcript.append_scalar(b"z_x", &self.z_x);
  149. let w = transcript.challenge_scalar(b"w");
  150. let ww = &w * &w;
  151. let www = &w * &ww;
  152. let w_negated = -&w;
  153. let ww_negated = -&ww;
  154. let www_negated = -&www;
  155. // check the required algebraic conditions
  156. let Y_0 = self
  157. .Y_0
  158. .decompress()
  159. .ok_or(SigmaProofVerificationError::Deserialization)?;
  160. let Y_1 = self
  161. .Y_1
  162. .decompress()
  163. .ok_or(SigmaProofVerificationError::Deserialization)?;
  164. let Y_2 = self
  165. .Y_2
  166. .decompress()
  167. .ok_or(SigmaProofVerificationError::Deserialization)?;
  168. let Y_3 = self
  169. .Y_3
  170. .decompress()
  171. .ok_or(SigmaProofVerificationError::Deserialization)?;
  172. let P_first = first_pubkey.get_point();
  173. let P_second = second_pubkey.get_point();
  174. let P_third = third_pubkey.get_point();
  175. let C = commitment.get_point();
  176. let D_first = first_handle.get_point();
  177. let D_second = second_handle.get_point();
  178. let D_third = third_handle.get_point();
  179. let check = RistrettoPoint::vartime_multiscalar_mul(
  180. vec![
  181. &self.z_r, // z_r
  182. &self.z_x, // z_x
  183. &(-&c), // -c
  184. &-(&Scalar::ONE), // -identity
  185. &(&w * &self.z_r), // w * z_r
  186. &(&w_negated * &c), // -w * c
  187. &w_negated, // -w
  188. &(&ww * &self.z_r), // ww * z_r
  189. &(&ww_negated * &c), // -ww * c
  190. &ww_negated, // -ww
  191. &(&www * &self.z_r), // www * z_r
  192. &(&www_negated * &c), // -www * c
  193. &www_negated, // -www
  194. ],
  195. vec![
  196. &(*H), // H
  197. &G, // G
  198. C, // C
  199. &Y_0, // Y_0
  200. P_first, // P_first
  201. D_first, // D_first
  202. &Y_1, // Y_1
  203. P_second, // P_second
  204. D_second, // D_second
  205. &Y_2, // Y_2
  206. P_third, // P_third
  207. D_third, // D_third
  208. &Y_3, // Y_3
  209. ],
  210. );
  211. if check.is_identity() {
  212. Ok(())
  213. } else {
  214. Err(SigmaProofVerificationError::AlgebraicRelation.into())
  215. }
  216. }
  217. pub fn to_bytes(&self) -> [u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN] {
  218. let mut buf = [0_u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN];
  219. let mut chunks = buf.chunks_mut(UNIT_LEN);
  220. chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
  221. chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
  222. chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
  223. chunks.next().unwrap().copy_from_slice(self.Y_3.as_bytes());
  224. chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
  225. chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
  226. buf
  227. }
  228. pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofVerificationError> {
  229. let mut chunks = bytes.chunks(UNIT_LEN);
  230. let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
  231. let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
  232. let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
  233. let Y_3 = ristretto_point_from_optional_slice(chunks.next())?;
  234. let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
  235. let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
  236. Ok(GroupedCiphertext3HandlesValidityProof {
  237. Y_0,
  238. Y_1,
  239. Y_2,
  240. Y_3,
  241. z_r,
  242. z_x,
  243. })
  244. }
  245. }
  246. #[cfg(test)]
  247. mod test {
  248. use {
  249. super::*,
  250. crate::{
  251. encryption::{
  252. elgamal::ElGamalKeypair,
  253. pedersen::Pedersen,
  254. pod::{
  255. elgamal::{PodDecryptHandle, PodElGamalCiphertext, PodElGamalPubkey},
  256. pedersen::PodPedersenCommitment,
  257. },
  258. },
  259. sigma_proofs::pod::PodGroupedCiphertext3HandlesValidityProof,
  260. },
  261. std::str::FromStr,
  262. };
  263. #[test]
  264. fn test_grouped_ciphertext_3_handles_validity_proof_correctness() {
  265. let first_keypair = ElGamalKeypair::new_rand();
  266. let first_pubkey = first_keypair.pubkey();
  267. let second_keypair = ElGamalKeypair::new_rand();
  268. let second_pubkey = second_keypair.pubkey();
  269. let third_keypair = ElGamalKeypair::new_rand();
  270. let third_pubkey = third_keypair.pubkey();
  271. let amount: u64 = 55;
  272. let (commitment, opening) = Pedersen::new(amount);
  273. let first_handle = first_pubkey.decrypt_handle(&opening);
  274. let second_handle = second_pubkey.decrypt_handle(&opening);
  275. let third_handle = third_pubkey.decrypt_handle(&opening);
  276. let mut prover_transcript = Transcript::new(b"Test");
  277. let mut verifier_transcript = Transcript::new(b"Test");
  278. let proof = GroupedCiphertext3HandlesValidityProof::new(
  279. first_pubkey,
  280. second_pubkey,
  281. third_pubkey,
  282. amount,
  283. &opening,
  284. &mut prover_transcript,
  285. );
  286. proof
  287. .verify(
  288. &commitment,
  289. first_pubkey,
  290. second_pubkey,
  291. third_pubkey,
  292. &first_handle,
  293. &second_handle,
  294. &third_handle,
  295. &mut verifier_transcript,
  296. )
  297. .unwrap();
  298. }
  299. #[test]
  300. fn test_grouped_ciphertext_3_handles_validity_proof_edge_cases() {
  301. // if first or second public key zeroed, then the proof should always reject
  302. let first_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap();
  303. let second_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap();
  304. let third_keypair = ElGamalKeypair::new_rand();
  305. let third_pubkey = third_keypair.pubkey();
  306. let amount: u64 = 55;
  307. let (commitment, opening) = Pedersen::new(amount);
  308. let first_handle = second_pubkey.decrypt_handle(&opening);
  309. let second_handle = second_pubkey.decrypt_handle(&opening);
  310. let third_handle = third_pubkey.decrypt_handle(&opening);
  311. let mut prover_transcript = Transcript::new(b"Test");
  312. let mut verifier_transcript = Transcript::new(b"Test");
  313. let proof = GroupedCiphertext3HandlesValidityProof::new(
  314. &first_pubkey,
  315. &second_pubkey,
  316. third_pubkey,
  317. amount,
  318. &opening,
  319. &mut prover_transcript,
  320. );
  321. assert!(proof
  322. .verify(
  323. &commitment,
  324. &first_pubkey,
  325. &second_pubkey,
  326. third_pubkey,
  327. &first_handle,
  328. &second_handle,
  329. &third_handle,
  330. &mut verifier_transcript,
  331. )
  332. .is_err());
  333. // all zeroed ciphertext should still be valid
  334. let first_keypair = ElGamalKeypair::new_rand();
  335. let first_pubkey = first_keypair.pubkey();
  336. let second_keypair = ElGamalKeypair::new_rand();
  337. let second_pubkey = second_keypair.pubkey();
  338. let third_keypair = ElGamalKeypair::new_rand();
  339. let third_pubkey = third_keypair.pubkey();
  340. let amount: u64 = 0;
  341. let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap();
  342. let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap();
  343. let first_handle = first_pubkey.decrypt_handle(&opening);
  344. let second_handle = second_pubkey.decrypt_handle(&opening);
  345. let third_handle = third_pubkey.decrypt_handle(&opening);
  346. let mut prover_transcript = Transcript::new(b"Test");
  347. let mut verifier_transcript = Transcript::new(b"Test");
  348. let proof = GroupedCiphertext3HandlesValidityProof::new(
  349. first_pubkey,
  350. second_pubkey,
  351. third_pubkey,
  352. amount,
  353. &opening,
  354. &mut prover_transcript,
  355. );
  356. proof
  357. .verify(
  358. &commitment,
  359. first_pubkey,
  360. second_pubkey,
  361. third_pubkey,
  362. &first_handle,
  363. &second_handle,
  364. &third_handle,
  365. &mut verifier_transcript,
  366. )
  367. .unwrap();
  368. // decryption handles can be zero as long as the Pedersen commitment is valid
  369. let first_keypair = ElGamalKeypair::new_rand();
  370. let first_pubkey = first_keypair.pubkey();
  371. let second_keypair = ElGamalKeypair::new_rand();
  372. let second_pubkey = second_keypair.pubkey();
  373. let third_keypair = ElGamalKeypair::new_rand();
  374. let third_pubkey = third_keypair.pubkey();
  375. let amount: u64 = 55;
  376. let zeroed_opening = PedersenOpening::default();
  377. let commitment = Pedersen::with(amount, &zeroed_opening);
  378. let first_handle = first_pubkey.decrypt_handle(&zeroed_opening);
  379. let second_handle = second_pubkey.decrypt_handle(&zeroed_opening);
  380. let third_handle = third_pubkey.decrypt_handle(&zeroed_opening);
  381. let mut prover_transcript = Transcript::new(b"Test");
  382. let mut verifier_transcript = Transcript::new(b"Test");
  383. let proof = GroupedCiphertext3HandlesValidityProof::new(
  384. first_pubkey,
  385. second_pubkey,
  386. third_pubkey,
  387. amount,
  388. &opening,
  389. &mut prover_transcript,
  390. );
  391. proof
  392. .verify(
  393. &commitment,
  394. first_pubkey,
  395. second_pubkey,
  396. third_pubkey,
  397. &first_handle,
  398. &second_handle,
  399. &third_handle,
  400. &mut verifier_transcript,
  401. )
  402. .unwrap();
  403. }
  404. #[test]
  405. fn test_grouped_ciphertext_3_handles_validity_proof_string() {
  406. let commitment_str = "DDSCVZLH+eqC9gX+ZeP3HQQxigojAOgda3YwVChR5W4=";
  407. let pod_commitment = PodPedersenCommitment::from_str(commitment_str).unwrap();
  408. let commitment: PedersenCommitment = pod_commitment.try_into().unwrap();
  409. let first_pubkey_str = "yGGJnLUs8B744So/Ua3n2wNm+8u9ey/6KrDdHx4ySwk=";
  410. let pod_first_pubkey = PodElGamalPubkey::from_str(first_pubkey_str).unwrap();
  411. let first_pubkey: ElGamalPubkey = pod_first_pubkey.try_into().unwrap();
  412. let second_pubkey_str = "ZFETe85sZdWpxLAo177kwiOxZCpsXGeyZEnzern7tAk=";
  413. let pod_second_pubkey = PodElGamalPubkey::from_str(second_pubkey_str).unwrap();
  414. let second_pubkey: ElGamalPubkey = pod_second_pubkey.try_into().unwrap();
  415. let third_pubkey_str = "duUYiBx0l0jRRPsTLCoCD8PIKFczPdrxl+2f4eCflhQ=";
  416. let pod_third_pubkey = PodElGamalPubkey::from_str(third_pubkey_str).unwrap();
  417. let third_pubkey: ElGamalPubkey = pod_third_pubkey.try_into().unwrap();
  418. let first_handle_str = "Asor2klomf847EmJZmXn3qoi0SGE3cBXCkKttbJa+lE=";
  419. let pod_first_handle_str = PodDecryptHandle::from_str(first_handle_str).unwrap();
  420. let first_handle: DecryptHandle = pod_first_handle_str.try_into().unwrap();
  421. let second_handle_str = "kJ0GYHDVeB1Kgvqp+MY/my3BYZvqsC5Mv0gQLJHnNBQ=";
  422. let pod_second_handle_str = PodDecryptHandle::from_str(second_handle_str).unwrap();
  423. let second_handle: DecryptHandle = pod_second_handle_str.try_into().unwrap();
  424. let third_handle_str = "Jnd5jZLNDOMMt+kbgQWCQqTytbwHx3Bz5vwtfDLhRn0=";
  425. let pod_third_handle_str = PodDecryptHandle::from_str(third_handle_str).unwrap();
  426. let third_handle: DecryptHandle = pod_third_handle_str.try_into().unwrap();
  427. let proof_str = "8NoqOM40+fvPY2aHzO0SdWZM6lvSoaqI7KpaFuE4wQUaqewILtQV8IMHeHmpevxt/GTErJsdcV8kY3HDZ1GHbMoDujYpstUhyubX1voJh/DstYAL1SQqlRpNLG+kWEUZYvCudTur7i5R+zqZQY3sRMEAxW458V+1GmyCWbWP3FZEz5gX/Pa28/ZNLBvmSPpJBZapXRI5Ra0dKPskFmQ0CH0gBWo6pxj/PH9sgNEkLrbVZB7jpVtdmNzivwgFeb4M";
  428. let pod_proof = PodGroupedCiphertext3HandlesValidityProof::from_str(proof_str).unwrap();
  429. let proof: GroupedCiphertext3HandlesValidityProof = pod_proof.try_into().unwrap();
  430. let mut verifier_transcript = Transcript::new(b"Test");
  431. proof
  432. .verify(
  433. &commitment,
  434. &first_pubkey,
  435. &second_pubkey,
  436. &third_pubkey,
  437. &first_handle,
  438. &second_handle,
  439. &third_handle,
  440. &mut verifier_transcript,
  441. )
  442. .unwrap();
  443. }
  444. }