arcis_compiler/utils/zkp/
zero_ciphertext.rs1use crate::{
4 core::{
5 circuits::boolean::{boolean_value::BooleanValue, byte::Byte},
6 global_value::{curve_value::CompressedCurveValue, value::FieldValue},
7 },
8 traits::{Reveal, ToLeBytes},
9 utils::{
10 field::ScalarField,
11 zkp::{
12 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
13 transcript::Transcript,
14 util::UNIT_LEN,
15 },
16 },
17};
18use zk_elgamal_proof::encryption::{
19 DECRYPT_HANDLE_LEN,
20 ELGAMAL_CIPHERTEXT_LEN,
21 ELGAMAL_PUBKEY_LEN,
22 PEDERSEN_COMMITMENT_LEN,
23};
24
25pub const ZERO_CIPHERTEXT_PROOF_LEN: usize = 96;
26
27pub const ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN: usize = ELGAMAL_PUBKEY_LEN + ELGAMAL_CIPHERTEXT_LEN;
28
29pub const ZERO_CIPHERTEXT_PROOF_DATA_LEN: usize =
30 ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN + ZERO_CIPHERTEXT_PROOF_LEN;
31
32#[derive(Clone, Copy)]
37pub struct ZeroCiphertextProofData {
38 pub context: ZeroCiphertextProofContext,
39 pub proof: ZeroCiphertextProof,
40}
41
42#[derive(Clone, Copy)]
44pub struct ZeroCiphertextProofContext {
45 pub pubkey: ElGamalPubkey,
47 pub ciphertext: ElGamalCiphertext,
49}
50
51impl ZeroCiphertextProofContext {
52 fn new_transcript(&self) -> Transcript<BooleanValue> {
53 let mut transcript = Transcript::new(b"zero-ciphertext-instruction");
54 transcript.append_point(b"pubkey", &self.pubkey.get_point().compress());
55 transcript.append_elgamal_ciphertext(b"ciphertext", &self.ciphertext);
56 transcript
57 }
58
59 pub fn to_bytes(&self) -> [Byte<BooleanValue>; ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN] {
60 let mut bytes = [Byte::<BooleanValue>::from(0); ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN];
61 bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.pubkey.get_point().compress().to_bytes());
62 let mut offset = ELGAMAL_PUBKEY_LEN;
63 bytes[offset..offset + PEDERSEN_COMMITMENT_LEN]
64 .copy_from_slice(&self.ciphertext.commitment.get_point().compress().to_bytes());
65 offset += PEDERSEN_COMMITMENT_LEN;
66 bytes[offset..offset + DECRYPT_HANDLE_LEN]
67 .copy_from_slice(&self.ciphertext.handle.get_point().compress().to_bytes());
68 bytes
69 }
70}
71
72impl ZeroCiphertextProofData {
73 pub fn new(keypair: &ElGamalKeypair, ciphertext: &ElGamalCiphertext) -> Self {
74 let context = ZeroCiphertextProofContext {
75 pubkey: *keypair.pubkey(),
76 ciphertext: *ciphertext,
77 };
78 let mut transcript = context.new_transcript();
79 let proof = ZeroCiphertextProof::new(keypair, ciphertext, &mut transcript);
80 ZeroCiphertextProofData { context, proof }
81 }
82
83 pub fn to_bytes(&self) -> [Byte<BooleanValue>; ZERO_CIPHERTEXT_PROOF_DATA_LEN] {
84 let mut bytes = [Byte::<BooleanValue>::from(0); ZERO_CIPHERTEXT_PROOF_DATA_LEN];
85 bytes[..ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN].copy_from_slice(&self.context.to_bytes());
86 bytes[ZERO_CIPHERTEXT_PROOF_CONTEXT_LEN..].copy_from_slice(&self.proof.to_bytes());
87 bytes
88 }
89}
90
91#[allow(non_snake_case, dead_code)]
95#[derive(Clone, Copy)]
96pub struct ZeroCiphertextProof {
97 Y_P: CompressedCurveValue,
98 Y_D: CompressedCurveValue,
99 z: FieldValue<ScalarField>,
100}
101
102#[allow(non_snake_case)]
103impl ZeroCiphertextProof {
104 pub fn new(
116 elgamal_keypair: &ElGamalKeypair,
117 ciphertext: &ElGamalCiphertext,
118 transcript: &mut Transcript<BooleanValue>,
119 ) -> Self {
120 transcript.zero_ciphertext_proof_domain_separator();
121
122 let P = elgamal_keypair.pubkey().get_point();
124 let s = elgamal_keypair.secret().get_scalar();
125 let D = ciphertext.handle.get_point();
126
127 let y = FieldValue::<ScalarField>::random();
129 let Y_P = (y * *P).reveal().compress();
130 let Y_D = (y * *D).reveal().compress();
131
132 transcript.append_point(b"Y_P", &Y_P);
134 transcript.append_point(b"Y_D", &Y_D);
135
136 let c = transcript.challenge_scalar(b"c");
137
138 let z = ((c * *s) + y).reveal();
140
141 transcript.append_scalar(b"z", &z);
142 let _w = transcript.challenge_scalar(b"w");
143
144 Self { Y_P, Y_D, z }
145 }
146
147 pub fn to_bytes(&self) -> [Byte<BooleanValue>; ZERO_CIPHERTEXT_PROOF_LEN] {
148 let mut buf = [Byte::<BooleanValue>::from(0); ZERO_CIPHERTEXT_PROOF_LEN];
149 let mut chunks = buf.chunks_mut(UNIT_LEN);
150 chunks.next().unwrap().copy_from_slice(&self.Y_P.to_bytes());
151 chunks.next().unwrap().copy_from_slice(&self.Y_D.to_bytes());
152 chunks
153 .next()
154 .unwrap()
155 .copy_from_slice(&self.z.to_le_bytes());
156 buf
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use crate::{
163 core::{
164 bounds::FieldBounds,
165 expressions::{
166 curve_expr::{CurveExpr, InputInfo},
167 domain::Domain,
168 expr::EvalValue,
169 field_expr::FieldExpr,
170 InputKind,
171 },
172 global_value::{
173 curve_value::CurveValue,
174 global_expr_store::with_local_expr_store_as_global,
175 value::FieldValue,
176 },
177 ir_builder::{ExprStore, IRBuilder},
178 },
179 utils::{
180 curve_point::CurvePoint,
181 field::ScalarField,
182 zkp::{
183 elgamal::{
184 DecryptHandle,
185 ElGamalCiphertext,
186 ElGamalKeypair,
187 ElGamalPubkey,
188 ElGamalSecretKey,
189 },
190 pedersen::PedersenCommitment,
191 zero_ciphertext::ZeroCiphertextProofData,
192 },
193 },
194 };
195 use group::GroupEncoding;
196 use primitives::algebra::elliptic_curve::{Curve as AsyncMPCCurve, Curve25519Ristretto};
197 use rand::{Rng, RngCore};
198 use rustc_hash::FxHashMap;
199 use std::rc::Rc;
200 use zk_elgamal_proof::{
201 encryption::elgamal::ElGamalKeypair as SolanaElGamalKeypair,
202 zk_elgamal_proof_program::proof_data::{
203 ZeroCiphertextProofData as SolanaZeroCiphertextProofData,
204 ZkProofData,
205 },
206 };
207
208 #[test]
209 #[allow(non_snake_case)]
210 fn test_zero_ciphertext_proof() {
211 let rng = &mut crate::utils::test_rng::get();
212
213 let keypair = SolanaElGamalKeypair::new_rand();
215 let mut ciphertext = keypair.pubkey().encrypt(0u32);
216
217 let is_valid_proof = rng.gen_bool(0.5);
219 if !is_valid_proof {
220 let rand = rng.next_u32() + 1;
221 ciphertext = ciphertext.add_amount(rand);
222 }
223
224 let solana_proof_data = SolanaZeroCiphertextProofData::new(&keypair, &ciphertext).unwrap();
225 assert_eq!(solana_proof_data.verify_proof().is_ok(), is_valid_proof);
226
227 let mut expr_store = IRBuilder::new(true);
228
229 let mut input_vals = FxHashMap::<usize, EvalValue>::default();
231 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
232 &mut expr_store,
233 CurveExpr::Input(0, Rc::new(InputInfo::from(InputKind::Plaintext))),
234 );
235 input_vals.insert(
236 0,
237 EvalValue::Curve(CurvePoint::new(
238 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
239 &keypair.pubkey().get_point().to_bytes(),
240 )
241 .unwrap(),
242 )),
243 );
244 let _ = expr_store.push_field(FieldExpr::Input(
245 1,
246 FieldBounds::<ScalarField>::All.as_input_info(InputKind::Secret),
247 ));
248 input_vals.insert(
249 1,
250 EvalValue::Scalar(ScalarField::from(*keypair.secret().get_scalar())),
251 );
252 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
253 &mut expr_store,
254 CurveExpr::Input(2, Rc::new(InputInfo::from(InputKind::Plaintext))),
255 );
256 input_vals.insert(
257 2,
258 EvalValue::Curve(CurvePoint::new(
259 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
260 &ciphertext.commitment.get_point().to_bytes(),
261 )
262 .unwrap(),
263 )),
264 );
265 let _ = <IRBuilder as ExprStore<ScalarField>>::push_curve(
266 &mut expr_store,
267 CurveExpr::Input(3, Rc::new(InputInfo::from(InputKind::Plaintext))),
268 );
269 input_vals.insert(
270 3,
271 EvalValue::Curve(CurvePoint::new(
272 <Curve25519Ristretto as AsyncMPCCurve>::Point::from_bytes(
273 &ciphertext.handle.get_point().to_bytes(),
274 )
275 .unwrap(),
276 )),
277 );
278
279 let outputs = with_local_expr_store_as_global(
280 || {
281 let pubkey = CurveValue::new(0);
282 let secret = FieldValue::<ScalarField>::from_id(1);
283 let commitment = CurveValue::new(2);
284 let handle = CurveValue::new(3);
285
286 let arcis_proof_data = ZeroCiphertextProofData::new(
287 &ElGamalKeypair::new_for_tests(
288 ElGamalPubkey::new_for_tests(pubkey),
289 ElGamalSecretKey::new(secret),
290 ),
291 &ElGamalCiphertext {
292 commitment: PedersenCommitment::new(commitment),
293 handle: DecryptHandle::new_for_tests(handle),
294 },
295 );
296
297 arcis_proof_data
298 .to_bytes()
299 .into_iter()
300 .map(|byte| FieldValue::<ScalarField>::from(byte).get_id())
301 .collect::<Vec<usize>>()
302 },
303 &mut expr_store,
304 );
305
306 let ir = expr_store.into_ir(outputs);
307 let result = ir
308 .eval(rng, &mut input_vals)
309 .map(|x| {
310 x.into_iter()
311 .map(ScalarField::unwrap)
312 .collect::<Vec<ScalarField>>()
313 })
314 .unwrap();
315
316 let arcis_proof_data_bytes = result
317 .iter()
318 .map(|byte| byte.to_le_bytes()[0])
319 .collect::<Vec<u8>>();
320
321 let arcis_proof_data =
322 SolanaZeroCiphertextProofData::from_bytes(&arcis_proof_data_bytes).unwrap();
323
324 let arcis_verification = arcis_proof_data.verify_proof();
325
326 assert_eq!(arcis_verification.is_ok(), is_valid_proof);
327 }
328}