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