1use crate::{
4 core::{
5 circuits::boolean::{boolean_value::BooleanValue, byte::Byte},
6 global_value::{
7 curve_value::{CompressedCurveValue, CurveValue},
8 value::FieldValue,
9 },
10 },
11 traits::{Reveal, ToLeBytes},
12 utils::{
13 field::ScalarField,
14 zkp::{
15 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
16 pedersen::{PedersenCommitment, PedersenOpening},
17 transcript::Transcript,
18 util::UNIT_LEN,
19 },
20 },
21};
22use std::sync::LazyLock;
23use zk_elgamal_proof::encryption::{
24 pedersen::H,
25 DECRYPT_HANDLE_LEN,
26 ELGAMAL_CIPHERTEXT_LEN,
27 ELGAMAL_PUBKEY_LEN,
28 PEDERSEN_COMMITMENT_LEN,
29};
30
31pub const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = 192;
32
33pub const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN: usize =
34 ELGAMAL_PUBKEY_LEN + ELGAMAL_CIPHERTEXT_LEN + PEDERSEN_COMMITMENT_LEN;
35
36pub const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_DATA_LEN: usize =
37 CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN + CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN;
38
39#[derive(Clone, Copy)]
45pub struct CiphertextCommitmentEqualityProofData {
46 pub context: CiphertextCommitmentEqualityProofContext,
47 pub proof: CiphertextCommitmentEqualityProof,
48}
49
50#[derive(Clone, Copy)]
52pub struct CiphertextCommitmentEqualityProofContext {
53 pub pubkey: ElGamalPubkey,
55 pub ciphertext: ElGamalCiphertext,
57 pub commitment: PedersenCommitment,
59}
60
61impl CiphertextCommitmentEqualityProofContext {
62 pub fn new_transcript(&self) -> Transcript<BooleanValue> {
63 let mut transcript = Transcript::new(b"ciphertext-commitment-equality-instruction");
64 transcript.append_point(b"pubkey", &self.pubkey.get_point().compress());
65 transcript.append_elgamal_ciphertext(b"ciphertext", &self.ciphertext);
66 transcript.append_point(b"commitment", &self.commitment.get_point().compress());
67 transcript
68 }
69
70 pub fn to_bytes(
71 &self,
72 ) -> [Byte<BooleanValue>; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN] {
73 let mut bytes =
74 [Byte::<BooleanValue>::from(0); CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN];
75 bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.pubkey.get_point().compress().to_bytes());
76 let mut offset = ELGAMAL_PUBKEY_LEN;
77 bytes[offset..offset + PEDERSEN_COMMITMENT_LEN]
78 .copy_from_slice(&self.ciphertext.commitment.get_point().compress().to_bytes());
79 offset += PEDERSEN_COMMITMENT_LEN;
80 bytes[offset..offset + DECRYPT_HANDLE_LEN]
81 .copy_from_slice(&self.ciphertext.handle.get_point().compress().to_bytes());
82 offset += DECRYPT_HANDLE_LEN;
83 bytes[offset..offset + PEDERSEN_COMMITMENT_LEN]
84 .copy_from_slice(&self.commitment.get_point().compress().to_bytes());
85 bytes
86 }
87}
88
89impl CiphertextCommitmentEqualityProofData {
90 pub fn new(
91 keypair: &ElGamalKeypair,
92 ciphertext: &ElGamalCiphertext,
93 commitment: &PedersenCommitment,
94 opening: &PedersenOpening,
95 amount: FieldValue<ScalarField>,
96 ) -> Self {
97 let context = CiphertextCommitmentEqualityProofContext {
98 pubkey: *keypair.pubkey(),
99 ciphertext: *ciphertext,
100 commitment: *commitment,
101 };
102 let mut transcript = context.new_transcript();
103 let proof = CiphertextCommitmentEqualityProof::new(
104 keypair,
105 ciphertext,
106 opening,
107 amount,
108 &mut transcript,
109 );
110 CiphertextCommitmentEqualityProofData { context, proof }
111 }
112
113 pub fn to_bytes(&self) -> [Byte<BooleanValue>; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_DATA_LEN] {
114 let mut bytes =
115 [Byte::<BooleanValue>::from(0); CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_DATA_LEN];
116 bytes[..CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN]
117 .copy_from_slice(&self.context.to_bytes());
118 bytes[CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_CONTEXT_LEN..]
119 .copy_from_slice(&self.proof.to_bytes());
120 bytes
121 }
122}
123
124#[allow(non_snake_case, dead_code)]
128#[derive(Clone, Copy)]
129pub struct CiphertextCommitmentEqualityProof {
130 Y_0: CompressedCurveValue,
131 Y_1: CompressedCurveValue,
132 Y_2: CompressedCurveValue,
133 z_s: FieldValue<ScalarField>,
134 z_x: FieldValue<ScalarField>,
135 z_r: FieldValue<ScalarField>,
136}
137
138#[allow(non_snake_case)]
139impl CiphertextCommitmentEqualityProof {
140 pub fn new(
157 keypair: &ElGamalKeypair,
158 ciphertext: &ElGamalCiphertext,
159 opening: &PedersenOpening,
160 amount: FieldValue<ScalarField>,
161 transcript: &mut Transcript<BooleanValue>,
162 ) -> Self {
163 transcript.ciphertext_commitment_equality_proof_domain_separator();
164
165 let P = keypair.pubkey().get_point();
167 let D = ciphertext.handle.get_point();
168
169 let s = keypair.secret().get_scalar();
170 let x = amount;
171 let r = opening.get_scalar();
172
173 let y_s = FieldValue::<ScalarField>::random();
175 let y_x = FieldValue::<ScalarField>::random();
176 let y_r = FieldValue::<ScalarField>::random();
177
178 let Y_0 = (y_s * *P).reveal().compress();
179 let Y_1 = CurveValue::multiscalar_mul(vec![y_x, y_s], vec![CurveValue::generator(), *D])
180 .reveal()
181 .compress();
182 let Y_2 = CurveValue::multiscalar_mul(
183 vec![y_x, y_r],
184 vec![
185 CurveValue::generator(),
186 CurveValue::from(*LazyLock::force(&H)),
187 ],
188 )
189 .reveal()
190 .compress();
191
192 transcript.append_point(b"Y_0", &Y_0);
194 transcript.append_point(b"Y_1", &Y_1);
195 transcript.append_point(b"Y_2", &Y_2);
196
197 let c = transcript.challenge_scalar(b"c");
198
199 let z_s = ((c * *s) + y_s).reveal();
201 let z_x = ((c * x) + y_x).reveal();
202 let z_r = ((c * *r) + y_r).reveal();
203
204 transcript.append_scalar(b"z_s", &z_s);
206 transcript.append_scalar(b"z_x", &z_x);
207 transcript.append_scalar(b"z_r", &z_r);
208 let _w = transcript.challenge_scalar(b"w");
209
210 CiphertextCommitmentEqualityProof {
211 Y_0,
212 Y_1,
213 Y_2,
214 z_s,
215 z_x,
216 z_r,
217 }
218 }
219
220 pub fn to_bytes(&self) -> [Byte<BooleanValue>; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN] {
221 let mut buf = [Byte::<BooleanValue>::from(0); CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN];
222 let mut chunks = buf.chunks_mut(UNIT_LEN);
223 chunks.next().unwrap().copy_from_slice(&self.Y_0.to_bytes());
224 chunks.next().unwrap().copy_from_slice(&self.Y_1.to_bytes());
225 chunks.next().unwrap().copy_from_slice(&self.Y_2.to_bytes());
226 chunks
227 .next()
228 .unwrap()
229 .copy_from_slice(&self.z_s.to_le_bytes());
230 chunks
231 .next()
232 .unwrap()
233 .copy_from_slice(&self.z_x.to_le_bytes());
234 chunks
235 .next()
236 .unwrap()
237 .copy_from_slice(&self.z_r.to_le_bytes());
238 buf
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use crate::{
245 core::{
246 bounds::FieldBounds,
247 expressions::{
248 curve_expr::{CurveExpr, InputInfo},
249 domain::Domain,
250 expr::EvalValue,
251 field_expr::FieldExpr,
252 InputKind,
253 },
254 global_value::{
255 curve_value::CurveValue,
256 global_expr_store::with_local_expr_store_as_global,
257 value::FieldValue,
258 },
259 ir_builder::{ExprStore, IRBuilder},
260 },
261 utils::{
262 curve_point::CurvePoint,
263 field::ScalarField,
264 used_field::UsedField,
265 zkp::{
266 ciphertext_commitment_equality::CiphertextCommitmentEqualityProofData,
267 elgamal::{
268 DecryptHandle,
269 ElGamalCiphertext,
270 ElGamalKeypair,
271 ElGamalPubkey,
272 ElGamalSecretKey,
273 },
274 pedersen::{PedersenCommitment, PedersenOpening},
275 },
276 },
277 };
278 use group::GroupEncoding;
279 use primitives::algebra::elliptic_curve::{Curve as AsyncMPCCurve, Curve25519Ristretto};
280 use rand::{Rng, RngCore};
281 use rustc_hash::FxHashMap;
282 use std::rc::Rc;
283 use zk_elgamal_proof::{
284 encryption::{
285 elgamal::ElGamalKeypair as SolanaElGamalKeypair,
286 pedersen::Pedersen as SolanaPedersen,
287 },
288 zk_elgamal_proof_program::proof_data::{
289 CiphertextCommitmentEqualityProofData as SolanaCiphertextCommitmentEqualityProofData,
290 ZkProofData,
291 },
292 };
293
294 #[test]
295 #[allow(non_snake_case)]
296 fn test_ciphertext_commitment_equality() {
297 let rng = &mut crate::utils::test_rng::get();
298
299 let keypair = SolanaElGamalKeypair::new_rand();
301 let mut amount = rng.next_u64();
302 let ciphertext = keypair.pubkey().encrypt(amount);
303 let (mut commitment, mut opening) = SolanaPedersen::new(amount);
304
305 let is_valid_proof = rng.gen_bool(0.5);
307 if !is_valid_proof {
308 amount = rng.next_u64();
309 (commitment, opening) = SolanaPedersen::new(amount);
310 }
311
312 let solana_proof_data = SolanaCiphertextCommitmentEqualityProofData::new(
313 &keypair,
314 &ciphertext,
315 &commitment,
316 &opening,
317 amount,
318 )
319 .unwrap();
320 assert_eq!(solana_proof_data.verify_proof().is_ok(), is_valid_proof);
321
322 let mut expr_store = IRBuilder::new(true);
323
324 let mut input_vals = FxHashMap::<usize, EvalValue>::default();
326 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
327 &mut expr_store,
328 CurveExpr::Input(0, Rc::new(InputInfo::from(InputKind::Plaintext))),
329 );
330 input_vals.insert(
331 0,
332 EvalValue::Curve(CurvePoint::new(
333 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
334 &keypair.pubkey().get_point().to_bytes(),
335 )
336 .unwrap(),
337 )),
338 );
339 let _ = expr_store.push_field(FieldExpr::Input(
340 1,
341 FieldBounds::<ScalarField>::All.as_input_info(InputKind::Secret),
342 ));
343 input_vals.insert(
344 1,
345 EvalValue::Scalar(ScalarField::from(*keypair.secret().get_scalar())),
346 );
347 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
348 &mut expr_store,
349 CurveExpr::Input(2, Rc::new(InputInfo::from(InputKind::Plaintext))),
350 );
351 input_vals.insert(
352 2,
353 EvalValue::Curve(CurvePoint::new(
354 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
355 &ciphertext.commitment.get_point().to_bytes(),
356 )
357 .unwrap(),
358 )),
359 );
360 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
361 &mut expr_store,
362 CurveExpr::Input(3, Rc::new(InputInfo::from(InputKind::Plaintext))),
363 );
364 input_vals.insert(
365 3,
366 EvalValue::Curve(CurvePoint::new(
367 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
368 &ciphertext.handle.get_point().to_bytes(),
369 )
370 .unwrap(),
371 )),
372 );
373 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
374 &mut expr_store,
375 CurveExpr::Input(4, Rc::new(InputInfo::from(InputKind::Plaintext))),
376 );
377 input_vals.insert(
378 4,
379 EvalValue::Curve(CurvePoint::new(
380 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
381 &commitment.get_point().to_bytes(),
382 )
383 .unwrap(),
384 )),
385 );
386 let _ = expr_store.push_field(FieldExpr::Input(
387 5,
388 FieldBounds::<ScalarField>::All.as_input_info(InputKind::Secret),
389 ));
390 input_vals.insert(
391 5,
392 EvalValue::Scalar(ScalarField::from(*opening.get_scalar())),
393 );
394 let _ = expr_store.push_field(FieldExpr::Input(
395 6,
396 FieldBounds::new(
397 ScalarField::from(0),
398 ScalarField::power_of_two(64) - ScalarField::from(1),
399 )
400 .as_input_info(InputKind::Secret),
401 ));
402 input_vals.insert(6, EvalValue::Scalar(ScalarField::from(amount)));
403
404 let outputs = with_local_expr_store_as_global(
405 || {
406 let pubkey = CurveValue::new(0);
407 let secret = FieldValue::<ScalarField>::from_id(1);
408 let ciphertext_commitment = CurveValue::new(2);
409 let ciphertext_handle = CurveValue::new(3);
410 let commitment = CurveValue::new(4);
411 let opening = FieldValue::<ScalarField>::from_id(5);
412 let amount = FieldValue::<ScalarField>::from_id(6);
413
414 let arcis_proof_data = CiphertextCommitmentEqualityProofData::new(
415 &ElGamalKeypair::new_from_inner(
416 ElGamalPubkey::new_from_inner(pubkey),
417 ElGamalSecretKey::new(secret),
418 ),
419 &ElGamalCiphertext {
420 commitment: PedersenCommitment::new(ciphertext_commitment),
421 handle: DecryptHandle::new_from_inner(ciphertext_handle),
422 },
423 &PedersenCommitment::new(commitment),
424 &PedersenOpening::new(opening),
425 amount,
426 );
427
428 arcis_proof_data
429 .to_bytes()
430 .into_iter()
431 .map(|byte| FieldValue::<ScalarField>::from(byte).get_id())
432 .collect::<Vec<usize>>()
433 },
434 &mut expr_store,
435 );
436
437 let ir = expr_store.into_ir(outputs);
438 let result = ir
439 .eval(rng, &mut input_vals)
440 .map(|x| {
441 x.into_iter()
442 .map(ScalarField::unwrap)
443 .collect::<Vec<ScalarField>>()
444 })
445 .unwrap();
446
447 let arcis_proof_data_bytes = result
448 .iter()
449 .map(|byte| byte.to_le_bytes()[0])
450 .collect::<Vec<u8>>();
451
452 let arcis_proof_data =
453 SolanaCiphertextCommitmentEqualityProofData::from_bytes(&arcis_proof_data_bytes)
454 .unwrap();
455
456 let arcis_verification = arcis_proof_data.verify_proof();
457
458 assert_eq!(arcis_verification.is_ok(), is_valid_proof);
459 }
460}