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