wip: implementing ghash circuit
arnaucube git@arnaucube.com
Thu, 12 Feb 2026 22:01:27 +0000
4 files changed,
223 insertions(+),
9 deletions(-)
M
README.md
→
README.md
@@ -2,3 +2,5 @@ # plonky2-aes
Plonky2 circuits for AES encryption system as described at - AES: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf - AES-GCM (Galois Counter Mode): https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + +> Warning: this code is unaudited.
M
src/circuit_aes.rs
→
src/circuit_aes.rs
@@ -314,7 +314,7 @@ std::array::from_fn(|i| self.bytearray_ip_bits(a[i], x))
} fn zero_byte(&mut self) -> ByteArrayTarget { - array::from_fn(|_| BoolTarget::new_unsafe(self.zero())) + array::from_fn(|_| self._false()) } }
M
src/circuit_gcm.rs
→
src/circuit_gcm.rs
@@ -60,6 +60,95 @@ }
y } +fn ghash_target<const L: usize>( + builder: &mut CircuitBuilder<F, D>, + h: [ByteTarget; 16], + x: &[ByteTarget; L], +) -> [ByteTarget; 16] { + assert!(L.is_multiple_of(16)); // multiple of 128 bits + let m = L / 16; + + let zero_byte = builder.zero_byte(); + let mut y = [zero_byte; 16]; // (128 bits) + for i in 0..m { + let mut xi = [zero_byte; 16]; + xi.clone_from_slice(&x[i * 16..i * 16 + 16]); + let y_xi = xor_blocks(builder, y, xi); + y = gf_2_128_mul_target(builder, y_xi, h); + } + y +} +fn gf_2_128_mul_target( + builder: &mut CircuitBuilder<F, D>, + x: [ByteTarget; 16], + y: [ByteTarget; 16], +) -> [ByteTarget; 16] { + // WIP TODO + + let zero_byte = builder.zero_byte(); + let one_bool = builder._true(); + + // R: 11100001 || 0^120 + let mut r = [zero_byte; 16]; + r[0][0] = one_bool; + r[0][1] = one_bool; + r[0][2] = one_bool; + r[0][7] = one_bool; + + let mut z = [zero_byte; 16]; + let mut v = y; + for i in 0..128 { + let byte_index = i / 8; + let bit_index = 7 - (i % 8); + // dbg!(byte_index, bit_index); + let xi = x[byte_index][bit_index]; + + // set z = if xi==1: z^v, else: z + for b in 0..16 { + for k in 0..8 { + let z_xor_v = xor(builder, z[b][k], v[b][k]); + z[b][k] = + BoolTarget::new_unsafe(builder.select(xi, z_xor_v.target, z[b][k].target)); + } + } + + let lsb = v[15][7].clone(); + v = right_shift_one_target(builder, &v); + + // if lsb==1: v=v^R, else: v + for b in 0..16 { + for k in 0..8 { + let v_xor_r = xor(builder, v[b][k], r[b][k]); + v[b][k] = + BoolTarget::new_unsafe(builder.select(lsb, v_xor_r.target, v[b][k].target)); + } + } + } + z +} +pub fn right_shift_one_target( + builder: &mut CircuitBuilder<F, D>, + v: &[ByteTarget; 16], +) -> [ByteTarget; 16] { + let mut r: [ByteTarget; 16] = v.clone(); + + let mut carry = builder._false(); + for i in 0..16 { + let current = v[i]; + + let next_carry = current[0]; + + let mut shifted = [builder._false(); 8]; + for j in 0..7 { + shifted[j] = current[j + 1]; + } + shifted[7] = carry; + r[i] = shifted; + carry = next_carry; + } + r +} + pub fn inc32_target( builder: &mut CircuitBuilder<F, D>, block: [ByteTarget; 16],@@ -73,9 +162,7 @@ let a = block[byte_index][bit_index];
// builder.assert_bool(a); // TODO we can assume it's valid bool, skip check let sum = xor(builder, a, carry); - let carry_out = builder.and(a, carry); - r[byte_index][bit_index] = sum; carry = carry_out; }@@ -106,7 +193,6 @@ let full_bytes = t / 8;
let rem_bits = t % 8; let mut out: Vec<ByteTarget> = Vec::new(); - for i in 0..full_bytes { out.push(block[i]); }@@ -122,7 +208,6 @@ } else {
partial[bit] = zero_bool; } } - out.push(partial); } out@@ -147,7 +232,7 @@ use super::*;
use crate::{ circuit_aes::{sbox_lut, state_mix_matrix_bits, PartialWitnessByteArray}, native_aes::key_expansion, - native_gcm::gctr, + native_gcm::{gctr, gf_2_128_mul, ghash, right_shift_one}, }; use super::D;@@ -163,7 +248,6 @@ test_gctr_opt::<8, 4, 14, 13>()?;
Ok(()) } - fn test_gctr_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>( ) -> Result<()> where@@ -204,7 +288,7 @@ &pt_target,
); println!( - "encrypt_block (NK:{}, NB:{}, NR:{}, L:{}) num_gates: {}", + "gctr (NK:{}, NB:{}, NR:{}, L:{}) num_gates: {}", NK, NB, NR,@@ -222,6 +306,133 @@ std::iter::zip(icb_target, icb).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
std::iter::zip(pt_target, pt).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?; std::iter::zip(gctr_out_target, expected) + .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + + let proof = data.prove(pw)?; + data.verify(proof) + } + + #[test] + fn test_right_shift_one() -> Result<()> { + let x: [u8; 16] = [111; 16]; + + let mut expected = x.clone(); + right_shift_one(&mut expected); + + // Circuit declaration + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::<F, D>::new(config); + + let x_target: [ByteTarget; 16] = + array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe())); + + let out_target: [ByteTarget; 16] = right_shift_one_target(&mut builder, &x_target); + + println!("right_shift_one num_gates: {}", builder.num_gates()); + let data = builder.build::<PoseidonGoldilocksConfig>(); + + // set values to circuit + let mut pw = PartialWitness::<F>::new(); + std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + + std::iter::zip(out_target, expected) + .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + + let proof = data.prove(pw)?; + data.verify(proof)?; + Ok(()) + } + + #[test] + fn test_gf_mul() -> Result<()> { + // AES-GCM-128 + test_gf_mul_opt::<4, 4, 10, 16>()?; + // test_gf_mul_opt::<4, 4, 10, 32>()?; + // + // // AES-GCM-256 + // test_gf_mul_opt::<8, 4, 14, 16>()?; + + Ok(()) + } + fn test_gf_mul_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>( + ) -> Result<()> + where + [(); 4 * (NR + 1)]:, + [(); NK * NB]:, + { + let x: [u8; 16] = [111; 16]; + let y: [u8; 16] = [222; 16]; + + let expected = gf_2_128_mul(x, y); + + // Circuit declaration + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::<F, D>::new(config); + + let x_target: [ByteTarget; 16] = + array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe())); + let y_target: [ByteTarget; 16] = + array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe())); + + let out_target = gf_2_128_mul_target(&mut builder, x_target, y_target); + + println!("gf_2_128_mul (L:{}) num_gates: {}", L, builder.num_gates()); + let data = builder.build::<PoseidonGoldilocksConfig>(); + + // set values to circuit + let mut pw = PartialWitness::<F>::new(); + std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + std::iter::zip(y_target, y).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + + std::iter::zip(out_target, expected) + .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + + let proof = data.prove(pw)?; + data.verify(proof) + } + + #[test] + fn test_ghash() -> Result<()> { + // AES-GCM-128 + test_ghash_opt::<4, 4, 10, 16>()?; + test_ghash_opt::<4, 4, 10, 32>()?; + + // AES-GCM-256 + test_ghash_opt::<8, 4, 14, 16>()?; + + Ok(()) + } + fn test_ghash_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>( + ) -> Result<()> + where + [(); 4 * (NR + 1)]:, + [(); NK * NB]:, + { + let h: [u8; 16] = [222; 16]; + let x: &[u8; L] = &[42u8; L]; + + let expected = ghash(h, x); + + // Circuit declaration + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::<F, D>::new(config); + + let h_target: [ByteTarget; 16] = + array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe())); + let x_target: [ByteTarget; L] = + array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe())); + + let ghash_out_target = ghash_target::<L>(&mut builder, h_target, &x_target); + + println!("ghash (L:{}) num_gates: {}", L, builder.num_gates()); + let data = builder.build::<PoseidonGoldilocksConfig>(); + + // set values to circuit + let mut pw = PartialWitness::<F>::new(); + std::iter::zip(h_target, h).try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; + std::iter::zip(x_target, x).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?; + + std::iter::zip(ghash_out_target, expected) .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?; let proof = data.prove(pw)?;
M
src/native_gcm.rs
→
src/native_gcm.rs
@@ -155,7 +155,8 @@ }
} z } -fn right_shift_one(block: &mut [u8; 16]) { + +pub(crate) fn right_shift_one(block: &mut [u8; 16]) { let mut carry = 0u8; for byte in block.iter_mut() {