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