arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
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>(false)?;
565        test_encrypt_opt::<4, 4, 10, 13, true>(false)?; // with tag
566        test_encrypt_opt::<4, 4, 10, 17, false>(false)?;
567
568        // AES-GCM-256
569        test_encrypt_opt::<8, 4, 14, 13, false>(false)?;
570
571        Ok(())
572    }
573    // run it with: cargo test -- --ignored
574    #[ignore]
575    #[test]
576    fn test_encrypt_report_sizes() -> Result<()> {
577        // AES-GCM-128
578        test_encrypt_opt::<4, 4, 10, 16, false>(true)?;
579        test_encrypt_opt::<4, 4, 10, 17, false>(true)?;
580        test_encrypt_opt::<4, 4, 10, 32, false>(true)?;
581        test_encrypt_opt::<4, 4, 10, 33, false>(true)?;
582        test_encrypt_opt::<4, 4, 10, 64, false>(true)?;
583        test_encrypt_opt::<4, 4, 10, 128, false>(true)?;
584        test_encrypt_opt::<4, 4, 10, 256, false>(true)?;
585        test_encrypt_opt::<4, 4, 10, 512, false>(true)?;
586        test_encrypt_opt::<4, 4, 10, 1024, false>(true)?;
587        test_encrypt_opt::<4, 4, 10, 2048, false>(true)?;
588
589        Ok(())
590    }
591    fn test_encrypt_opt<
592        const NK: usize,
593        const NB: usize,
594        const NR: usize,
595        const L: usize,
596        const TAG: bool,
597    >(
598        only_build: bool,
599    ) -> Result<()>
600    where
601        [(); 4 * (NR + 1)]:,
602        [(); NK * NB]:,
603    {
604        let key: &[u8; NK * NB] = &[42; NK * NB];
605        let nonce: &[u8; 12] = &[111; 12];
606        let pt: &[u8; L] = &[42u8; L];
607
608        let (expected_c, expected_tag) = crate::native_gcm::encrypt::<NK, NB, NR>(key, nonce, pt);
609
610        // Circuit declaration
611        let config = CircuitConfig::standard_recursion_config();
612        let mut builder = CircuitBuilder::<F, D>::new(config);
613
614        let key_target: [ByteTarget; NK * NB] =
615            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
616        let nonce_target: [ByteTarget; 12] =
617            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
618        let pt_target: [ByteTarget; L] =
619            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
620        let (circuit_c, circuit_tag) = encrypt_target::<NK, NB, NR, L, TAG>(
621            &mut builder,
622            key_target,
623            &nonce_target,
624            &pt_target,
625        );
626
627        println!(
628            "encrypt (NK:{}, NB:{}, NR:{}, L:{}, TAG:{}) num_gates: {}",
629            NK,
630            NB,
631            NR,
632            L,
633            TAG,
634            builder.num_gates()
635        );
636        let data = builder.build::<PoseidonGoldilocksConfig>();
637
638        if only_build {
639            return Ok(());
640        }
641
642        // set values to circuit
643        let mut pw = PartialWitness::<F>::new();
644        std::iter::zip(key_target, key).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
645        std::iter::zip(nonce_target, nonce)
646            .try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
647        std::iter::zip(pt_target, pt).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
648
649        // expect circuit's ciphertext to match rust native ciphertext
650        std::iter::zip(circuit_c, expected_c)
651            .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
652
653        // if TAG==true, check computed tag, else expect it to be zero
654        if TAG {
655            std::iter::zip(circuit_tag, expected_tag)
656                .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
657        } else {
658            circuit_tag
659                .iter()
660                .try_for_each(|t| pw.set_byte_array_target(*t, 0))?;
661        }
662
663        let proof = data.prove(pw)?;
664        data.verify(proof)
665    }
666}