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}