plonky2-crypto-gadgets/src/circuit_gcm.rs
src/circuit_gcm.rsRaw
1//! For LICENSE check out https://github.com/0xPARC/plonky2-aes/blob/main/LICENSE
2
3use std::{array, sync::Arc};
4
5use plonky2::{
6 field::{extension::Extendable, goldilocks_field::GoldilocksField as F, types::Field},
7 hash::hash_types::RichField,
8 iop::{
9 target::{BoolTarget, Target},
10 witness::{PartialWitness, WitnessWrite},
11 },
12 plonk::circuit_builder::CircuitBuilder,
13};
14
15use crate::{
16 circuit_aes::{
17 le_bits_from_byte, sbox_lut, state_mix_matrix_bits, xor, ByteArrayTarget as ByteTarget,
18 CircuitBuilderAESState, StateTarget,
19 },
20 native_gcm::TAG_LEN,
21 D,
22};
23
24/// L: max size of plaintext to cipher.
25fn encrypt_target<
26 const NK: usize,
27 const NB: usize,
28 const NR: usize,
29 const L: usize,
30 const TAG: bool,
31>(
32 builder: &mut CircuitBuilder<F, D>,
33 key: [ByteTarget; NK * NB],
34 nonce: &[ByteTarget; 12],
35 pt: &[ByteTarget; L],
36) -> ([ByteTarget; L], [ByteTarget; TAG_LEN / 8])
37where
38 [(); NK * NB]:,
39 [(); 4 * (NR + 1)]:,
40{
41 let sbox_lut = sbox_lut(builder);
42 let mix_matrix = state_mix_matrix_bits(builder);
43
44 let a: &[ByteTarget] = &[]; // additional authenticated data // TODO maybe as input
45
46 let expanded_key = builder.key_expansion::<NK, NB, NR>(sbox_lut, key);
47
48 let empty_state = builder.empty_state();
49
50 // 1. CIPH_K
51 let h = builder
52 .encrypt_block::<NR>(sbox_lut, mix_matrix, empty_state, expanded_key)
53 .flatten();
54
55 // 2. J_0
56 let zero_byte = builder.zero_byte();
57 let one_byte = array::from_fn(|i| {
58 // 0x01
59 if i == 0 {
60 builder._true()
61 } else {
62 builder._false()
63 }
64 });
65 let j0: [ByteTarget; 16] = if nonce.len() * 8 == 96 {
66 // J_0 = IV || 0^31 || 1
67 let mut out = [zero_byte; 16];
68 out[..12].copy_from_slice(nonce.as_slice());
69 out[12..16].copy_from_slice(&[zero_byte, zero_byte, zero_byte, one_byte]);
70 out
71 } else {
72 panic!("unsuported at initial version; nonce.len()=12 (96 bits)");
73 };
74
75 // 3. C=GCTR()
76 let inc32_j0 = inc32_target(builder, j0);
77 let c = gctr_target::<NR, L>(builder, sbox_lut, mix_matrix, expanded_key, &inc32_j0, &pt);
78
79 // the rest of the logic is for the tag; for some use cases we might skip it
80 // (saving a notable amount of gates, about double)
81 if !TAG {
82 let t: [ByteTarget; TAG_LEN / 8] = [zero_byte; TAG_LEN / 8];
83 return (c, t);
84 }
85
86 // 4. u, v
87 let u: usize = 16 * (L as f64 / 16_f64).ceil() as usize - L; // L=x.len()
88 let v: usize = 16 * (a.len() as f64 / 16_f64).ceil() as usize - a.len();
89
90 // 5. S = GHASH()
91 let const_a_len_u8: [u8; 8] = (a.len() * 8).to_be_bytes(); // const
92 let const_c_len_u8: [u8; 8] = (c.len() * 8).to_be_bytes(); // const
93 let a_len: [ByteTarget; 8] = array::from_fn(|byte_i| {
94 let const_bits = le_bits_from_byte(const_a_len_u8[byte_i]);
95 let byte: ByteTarget = array::from_fn(|bit_i| {
96 let bit_goldilocks = if const_bits[bit_i] { F::ONE } else { F::ZERO };
97 BoolTarget::new_unsafe(builder.constant(bit_goldilocks))
98 });
99 byte
100 });
101 let c_len: [ByteTarget; 8] = array::from_fn(|byte_i| {
102 let const_bits = le_bits_from_byte(const_c_len_u8[byte_i]);
103 let byte: ByteTarget = array::from_fn(|bit_i| {
104 let bit_goldilocks = if const_bits[bit_i] { F::ONE } else { F::ZERO };
105 BoolTarget::new_unsafe(builder.constant(bit_goldilocks))
106 });
107 byte
108 });
109 let ghash_input_vec = vec![
110 a.to_vec(),
111 vec![zero_byte; v],
112 c.to_vec(),
113 vec![zero_byte; u],
114 a_len.to_vec(),
115 c_len.to_vec(),
116 ]
117 .concat();
118 let s = ghash_target(builder, h, ghash_input_vec);
119
120 // 6. T=MSB(GCTR()))
121 let msb_input = gctr_target(builder, sbox_lut, mix_matrix, expanded_key, &j0, &s);
122 let t_vec = msb_t_target(builder, TAG_LEN / 8, &msb_input);
123 let mut t: [ByteTarget; TAG_LEN / 8] = [zero_byte; TAG_LEN / 8];
124 t.copy_from_slice(&t_vec);
125 (c, t)
126}
127
128/// L: max size of plaintext to cipher.
129fn gctr_target<const NR: usize, const L: usize>(
130 builder: &mut CircuitBuilder<F, D>,
131 sbox_lut_idx: usize,
132 mix_matrix: [[ByteTarget; 4]; 4],
133 key: [[ByteTarget; 4]; 4 * (NR + 1)],
134 icb: &[ByteTarget; 16],
135 x: &[ByteTarget; L],
136) -> [ByteTarget; L] {
137 let n = ((L * 8) as f64 / 128_f64).ceil() as usize;
138
139 let mut y: [ByteTarget; L] = x.clone();
140 let mut cb_i = *icb;
141
142 let zero_byte = builder.zero_byte();
143 for (i, x_i_raw) in x.chunks(16).enumerate() {
144 if i > 0 {
145 cb_i = inc32_target(builder, cb_i);
146 }
147
148 let l = x_i_raw.len().min(16);
149 let mut x_i = [zero_byte; 16];
150 x_i[..l].copy_from_slice(x_i_raw);
151
152 let ciph_cb_i = builder
153 .encrypt_block::<NR>(sbox_lut_idx, mix_matrix, StateTarget::from_flat(cb_i), key)
154 .flatten();
155
156 let (y_i, n_bytes) = if i < n && x_i_raw.len() == 16 {
157 (xor_blocks(builder, x_i, ciph_cb_i), 16)
158 } else {
159 // last chunk, might be smaller than 16 bytes (L%16 bytes)
160 let msb_res = msb_t_target(builder, L % 16, ciph_cb_i.as_slice());
161 let l = msb_res.len().min(16);
162 let mut m: [ByteTarget; 16] = [zero_byte; 16];
163 m[..l].copy_from_slice(&msb_res);
164 (xor_blocks(builder, x_i, m), l)
165 };
166 y[i * 16..i * 16 + n_bytes].clone_from_slice(&y_i[..n_bytes]);
167 }
168 y
169}
170
171// fn ghash_target<const L: usize>(
172fn ghash_target(
173 builder: &mut CircuitBuilder<F, D>,
174 h: [ByteTarget; 16],
175 // x: &[ByteTarget; L],
176 x: Vec<ByteTarget>,
177) -> [ByteTarget; 16] {
178 // assert!(L.is_multiple_of(16)); // multiple of 128 bits
179 // let m = L / 16;
180 assert!(x.len().is_multiple_of(16)); // multiple of 128 bits
181 let m = x.len() / 16;
182
183 let zero_byte = builder.zero_byte();
184 let mut y = [zero_byte; 16]; // (128 bits)
185 for i in 0..m {
186 let mut xi = [zero_byte; 16];
187 xi.clone_from_slice(&x[i * 16..i * 16 + 16]);
188 let y_xi = xor_blocks(builder, y, xi);
189 y = gf_2_128_mul_target(builder, y_xi, h);
190 }
191 y
192}
193fn gf_2_128_mul_target(
194 builder: &mut CircuitBuilder<F, D>,
195 x: [ByteTarget; 16],
196 y: [ByteTarget; 16],
197) -> [ByteTarget; 16] {
198 let zero_byte = builder.zero_byte();
199 let one_bool = builder._true();
200
201 // R: 10000111 || 0^120 (in little-endian)
202 let mut r = [zero_byte; 16];
203 r[0][0] = one_bool;
204 r[0][5] = one_bool;
205 r[0][6] = one_bool;
206 r[0][7] = one_bool;
207
208 let mut z = [zero_byte; 16];
209 let mut v = y;
210 for i in 0..128 {
211 let byte_index = i / 8;
212 let bit_index = 7 - (i % 8);
213 let xi = x[byte_index][bit_index];
214
215 // set z = if xi==1: z^v, else: z
216 for b in 0..16 {
217 for k in 0..8 {
218 let z_xor_v = xor(builder, z[b][k], v[b][k]);
219 z[b][k] =
220 BoolTarget::new_unsafe(builder.select(xi, z_xor_v.target, z[b][k].target));
221 }
222 }
223
224 let lsb = v[15][0].clone(); // (little-endian)
225 v = right_shift_one_target(builder, &v);
226
227 // if lsb==1: v=v^R, else: v
228 for b in 0..16 {
229 for k in 0..8 {
230 let v_xor_r = xor(builder, v[b][k], r[b][k]);
231 v[b][k] =
232 BoolTarget::new_unsafe(builder.select(lsb, v_xor_r.target, v[b][k].target));
233 }
234 }
235 }
236 z
237}
238pub fn right_shift_one_target(
239 builder: &mut CircuitBuilder<F, D>,
240 v: &[ByteTarget; 16],
241) -> [ByteTarget; 16] {
242 let mut r: [ByteTarget; 16] = v.clone();
243
244 let mut carry = builder._false();
245 for i in 0..16 {
246 let current = v[i];
247
248 let next_carry = current[0];
249
250 let mut shifted = [builder._false(); 8];
251 for j in 0..7 {
252 shifted[j] = current[j + 1];
253 }
254 shifted[7] = carry;
255 r[i] = shifted;
256 carry = next_carry;
257 }
258 r
259}
260
261pub fn inc32_target(
262 builder: &mut CircuitBuilder<F, D>,
263 block: [ByteTarget; 16],
264) -> [ByteTarget; 16] {
265 let mut r = block;
266
267 let mut carry = BoolTarget::new_unsafe(builder.one());
268 for byte_index in (12..16).rev() {
269 for bit_index in 0..8 {
270 let a = block[byte_index][bit_index];
271 // builder.assert_bool(a); // TODO we can assume it's valid bool, skip check
272
273 let sum = xor(builder, a, carry);
274 let carry_out = builder.and(a, carry);
275 r[byte_index][bit_index] = sum;
276 carry = carry_out;
277 }
278 }
279 r
280}
281
282fn xor_byte(builder: &mut CircuitBuilder<F, D>, b1: ByteTarget, b2: ByteTarget) -> ByteTarget {
283 array::from_fn(|i| xor(builder, b1[i], b2[i]))
284}
285
286fn xor_blocks(
287 builder: &mut CircuitBuilder<F, D>,
288 b1: [ByteTarget; 16],
289 b2: [ByteTarget; 16],
290) -> [ByteTarget; 16] {
291 array::from_fn(|i| xor_byte(builder, b1[i], b2[i]))
292}
293
294pub fn msb_t_target(
295 builder: &mut CircuitBuilder<F, D>,
296 t_bytes: usize,
297 block: &[ByteTarget],
298) -> Vec<ByteTarget> {
299 let t = t_bytes * 8;
300
301 let full_bytes = t / 8;
302 let rem_bits = t % 8;
303
304 let mut out: Vec<ByteTarget> = Vec::new();
305 for i in 0..full_bytes {
306 out.push(block[i]);
307 }
308
309 let zero_bool = BoolTarget::new_unsafe(builder.zero());
310 if rem_bits != 0 {
311 let mut partial = [zero_bool; 8];
312
313 for bit in 0..8 {
314 if bit < rem_bits {
315 partial[bit] = block[full_bytes][bit];
316 } else {
317 partial[bit] = zero_bool;
318 }
319 }
320 out.push(partial);
321 }
322 out
323}
324
325#[cfg(test)]
326mod tests {
327 use std::array;
328
329 use anyhow::Result;
330 use plonky2::{
331 field::{goldilocks_field::GoldilocksField as F, types::Field},
332 iop::witness::{PartialWitness, WitnessWrite},
333 plonk::{
334 circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
335 config::PoseidonGoldilocksConfig,
336 },
337 };
338
339 use super::*;
340 use crate::{
341 circuit_aes::PartialWitnessByteArray,
342 native_aes::key_expansion,
343 native_gcm::{gctr, gf_2_128_mul, ghash, right_shift_one},
344 };
345
346 use super::D;
347
348 #[test]
349 fn test_gctr() -> Result<()> {
350 // AES-GCM-128
351 test_gctr_opt::<4, 4, 10, 13>()?;
352 test_gctr_opt::<4, 4, 10, 17>()?;
353
354 // AES-GCM-256
355 test_gctr_opt::<8, 4, 14, 13>()?;
356
357 Ok(())
358 }
359 fn test_gctr_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>(
360 ) -> Result<()>
361 where
362 [(); 4 * (NR + 1)]:,
363 [(); NK * NB]:,
364 {
365 let key: &[u8; NK * NB] = &[42; NK * NB];
366 let expanded_key = key_expansion::<NK, NB, NR>(key);
367 let nonce: &[u8; 12] = &[111; 12];
368 let icb: &[u8; 16] = &[222; 16];
369 let pt: &[u8; L] = &[42u8; L];
370
371 let expected = gctr(expanded_key, &icb, pt);
372
373 // Circuit declaration
374 let config = CircuitConfig::standard_recursion_config();
375 let mut builder = CircuitBuilder::<F, D>::new(config);
376
377 let key_target: [ByteTarget; NK * NB] =
378 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
379 let nonce_target: [ByteTarget; 12] =
380 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
381 let icb_target: [ByteTarget; 16] =
382 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
383 let pt_target: [ByteTarget; L] =
384 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
385 let sbox_lut = sbox_lut(&mut builder);
386 let mix_matrix = state_mix_matrix_bits(&mut builder);
387
388 let key_expanded_target = builder.key_expansion::<NK, NB, NR>(sbox_lut, key_target);
389 let gctr_out_target = gctr_target::<NR, L>(
390 &mut builder,
391 sbox_lut,
392 mix_matrix,
393 key_expanded_target,
394 &icb_target,
395 &pt_target,
396 );
397
398 println!(
399 "gctr (NK:{}, NB:{}, NR:{}, L:{}) num_gates: {}",
400 NK,
401 NB,
402 NR,
403 L,
404 builder.num_gates()
405 );
406 let data = builder.build::<PoseidonGoldilocksConfig>();
407
408 // set values to circuit
409 let mut pw = PartialWitness::<F>::new();
410 std::iter::zip(key_target, key).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
411 std::iter::zip(nonce_target, nonce)
412 .try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
413 std::iter::zip(icb_target, icb).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
414 std::iter::zip(pt_target, pt).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
415
416 std::iter::zip(gctr_out_target, expected)
417 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
418
419 let proof = data.prove(pw)?;
420 data.verify(proof)
421 }
422
423 #[test]
424 fn test_right_shift_one() -> Result<()> {
425 let x: [u8; 16] = [111; 16];
426
427 let mut expected = x.clone();
428 right_shift_one(&mut expected);
429
430 // Circuit declaration
431 let config = CircuitConfig::standard_recursion_config();
432 let mut builder = CircuitBuilder::<F, D>::new(config);
433
434 let x_target: [ByteTarget; 16] =
435 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
436
437 let out_target: [ByteTarget; 16] = right_shift_one_target(&mut builder, &x_target);
438
439 println!("right_shift_one num_gates: {}", builder.num_gates());
440 let data = builder.build::<PoseidonGoldilocksConfig>();
441
442 // set values to circuit
443 let mut pw = PartialWitness::<F>::new();
444 std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
445
446 std::iter::zip(out_target, expected)
447 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
448
449 let proof = data.prove(pw)?;
450 data.verify(proof)?;
451 Ok(())
452 }
453
454 #[test]
455 fn test_gf_mul() -> Result<()> {
456 // AES-GCM-128
457 test_gf_mul_opt::<4, 4, 10, 16>()?;
458 test_gf_mul_opt::<4, 4, 10, 32>()?;
459
460 // AES-GCM-256
461 test_gf_mul_opt::<8, 4, 14, 16>()?;
462
463 Ok(())
464 }
465 fn test_gf_mul_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>(
466 ) -> Result<()>
467 where
468 [(); 4 * (NR + 1)]:,
469 [(); NK * NB]:,
470 {
471 let x: [u8; 16] = [111; 16];
472 let y: [u8; 16] = [222; 16];
473
474 let expected = gf_2_128_mul(x, y);
475 let x_bits: [[bool; 8]; 16] =
476 array::from_fn(|b| crate::circuit_aes::le_bits_from_byte(x[b]));
477 let y_bits: [[bool; 8]; 16] =
478 array::from_fn(|b| crate::circuit_aes::le_bits_from_byte(y[b]));
479
480 // sanity check
481 let expected2 = crate::native_gcm::gf_2_128_mul_circuit_version(x_bits, y_bits);
482 let expected_bits: [[bool; 8]; 16] =
483 array::from_fn(|b| crate::circuit_aes::le_bits_from_byte(expected[b]));
484 assert_eq!(expected2, expected_bits);
485
486 // Circuit declaration
487 let config = CircuitConfig::standard_recursion_config();
488 let mut builder = CircuitBuilder::<F, D>::new(config);
489
490 let x_target: [ByteTarget; 16] =
491 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
492 let y_target: [ByteTarget; 16] =
493 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
494
495 let out_target = gf_2_128_mul_target(&mut builder, x_target, y_target);
496
497 println!("gf_2_128_mul (L:{}) num_gates: {}", L, builder.num_gates());
498 let data = builder.build::<PoseidonGoldilocksConfig>();
499
500 // set values to circuit
501 let mut pw = PartialWitness::<F>::new();
502 std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
503 std::iter::zip(y_target, y).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
504
505 std::iter::zip(out_target, expected)
506 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
507
508 let proof = data.prove(pw)?;
509 data.verify(proof)
510 }
511
512 #[test]
513 fn test_ghash() -> Result<()> {
514 // AES-GCM-128
515 test_ghash_opt::<4, 4, 10, 16>()?;
516 test_ghash_opt::<4, 4, 10, 32>()?;
517
518 // AES-GCM-256
519 test_ghash_opt::<8, 4, 14, 16>()?;
520
521 Ok(())
522 }
523 fn test_ghash_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>(
524 ) -> Result<()>
525 where
526 [(); 4 * (NR + 1)]:,
527 [(); NK * NB]:,
528 {
529 let h: [u8; 16] = [222; 16];
530 let x: &[u8; L] = &[42u8; L];
531
532 let expected = ghash(h, x);
533
534 // Circuit declaration
535 let config = CircuitConfig::standard_recursion_config();
536 let mut builder = CircuitBuilder::<F, D>::new(config);
537
538 let h_target: [ByteTarget; 16] =
539 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
540 let x_target: [ByteTarget; L] =
541 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
542
543 // let ghash_out_target = ghash_target::<L>(&mut builder, h_target, &x_target);
544 let ghash_out_target = ghash_target(&mut builder, h_target, x_target.to_vec());
545
546 println!("ghash (L:{}) num_gates: {}", L, builder.num_gates());
547 let data = builder.build::<PoseidonGoldilocksConfig>();
548
549 // set values to circuit
550 let mut pw = PartialWitness::<F>::new();
551 std::iter::zip(h_target, h).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
552 std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
553
554 std::iter::zip(ghash_out_target, expected)
555 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
556
557 let proof = data.prove(pw)?;
558 data.verify(proof)
559 }
560
561 #[test]
562 fn test_encrypt() -> Result<()> {
563 // AES-GCM-128
564 test_encrypt_opt::<4, 4, 10, 13, false>()?;
565 test_encrypt_opt::<4, 4, 10, 13, true>()?; // with tag
566 test_encrypt_opt::<4, 4, 10, 17, false>()?;
567
568 // AES-GCM-256
569 test_encrypt_opt::<8, 4, 14, 13, false>()?;
570
571 Ok(())
572 }
573 fn test_encrypt_opt<
574 const NK: usize,
575 const NB: usize,
576 const NR: usize,
577 const L: usize,
578 const TAG: bool,
579 >() -> Result<()>
580 where
581 [(); 4 * (NR + 1)]:,
582 [(); NK * NB]:,
583 {
584 let key: &[u8; NK * NB] = &[42; NK * NB];
585 let nonce: &[u8; 12] = &[111; 12];
586 let pt: &[u8; L] = &[42u8; L];
587
588 let (expected_c, expected_tag) = crate::native_gcm::encrypt::<NK, NB, NR>(key, nonce, pt);
589
590 // Circuit declaration
591 let config = CircuitConfig::standard_recursion_config();
592 let mut builder = CircuitBuilder::<F, D>::new(config);
593
594 let key_target: [ByteTarget; NK * NB] =
595 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
596 let nonce_target: [ByteTarget; 12] =
597 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
598 let pt_target: [ByteTarget; L] =
599 array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
600 let (circuit_c, circuit_tag) = encrypt_target::<NK, NB, NR, L, TAG>(
601 &mut builder,
602 key_target,
603 &nonce_target,
604 &pt_target,
605 );
606
607 println!(
608 "encrypt (NK:{}, NB:{}, NR:{}, L:{}, TAG:{}) num_gates: {}",
609 NK,
610 NB,
611 NR,
612 L,
613 TAG,
614 builder.num_gates()
615 );
616 let data = builder.build::<PoseidonGoldilocksConfig>();
617
618 // set values to circuit
619 let mut pw = PartialWitness::<F>::new();
620 std::iter::zip(key_target, key).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
621 std::iter::zip(nonce_target, nonce)
622 .try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
623 std::iter::zip(pt_target, pt).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
624
625 // expect circuit's ciphertext to match rust native ciphertext
626 std::iter::zip(circuit_c, expected_c)
627 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
628
629 // if TAG==true, check computed tag, else expect it to be zero
630 if TAG {
631 std::iter::zip(circuit_tag, expected_tag)
632 .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
633 } else {
634 circuit_tag
635 .iter()
636 .try_for_each(|t| pw.set_byte_array_target(*t, 0))?;
637 }
638
639 let proof = data.prove(pw)?;
640 data.verify(proof)
641 }
642}