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