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
  3use std::{array, sync::Arc};
  4
  5use plonky2::{
  6    field::{extension::Extendable, goldilocks_field::GoldilocksField as F, types::Field},
  7    hash::hash_types::RichField,
  8    iop::{
  9        target::{BoolTarget, Target},
 10        witness::{PartialWitness, WitnessWrite},
 11    },
 12    plonk::circuit_builder::CircuitBuilder,
 13};
 14
 15use crate::{
 16    circuit_aes::{xor, ByteArrayTarget as ByteTarget, CircuitBuilderAESState, StateTarget},
 17    D,
 18};
 19
 20/// L: max size of plaintext to cipher.
 21fn gctr_target<const NR: usize, const L: usize>(
 22    builder: &mut CircuitBuilder<F, D>,
 23    sbox_lut_idx: usize,
 24    mix_matrix: [[ByteTarget; 4]; 4],
 25    key: [[ByteTarget; 4]; 4 * (NR + 1)],
 26    icb: &[ByteTarget; 16],
 27    x: &[ByteTarget; L],
 28) -> [ByteTarget; L] {
 29    let n = ((L * 8) as f64 / 128_f64).ceil() as usize;
 30
 31    let mut y: [ByteTarget; L] = x.clone();
 32    let mut cb_i = *icb;
 33
 34    let zero_byte = builder.zero_byte();
 35    for (i, x_i_raw) in x.chunks(16).enumerate() {
 36        if i > 0 {
 37            cb_i = inc32_target(builder, cb_i);
 38        }
 39
 40        let l = x_i_raw.len().min(16);
 41        let mut x_i = [zero_byte; 16];
 42        x_i[..l].copy_from_slice(x_i_raw);
 43
 44        let ciph_cb_i = builder
 45            .encrypt_block::<NR>(sbox_lut_idx, mix_matrix, StateTarget::from_flat(cb_i), key)
 46            .flatten();
 47
 48        let (y_i, n_bytes) = if i < n && x_i_raw.len() == 16 {
 49            (xor_blocks(builder, x_i, ciph_cb_i), 16)
 50        } else {
 51            // last chunk, might be smaller than 16 bytes (L%16 bytes)
 52            let msb_res = msb_t(builder, L % 16, ciph_cb_i.as_slice());
 53            let l = msb_res.len().min(16);
 54            let mut m: [ByteTarget; 16] = [zero_byte; 16];
 55            m[..l].copy_from_slice(&msb_res);
 56            (xor_blocks(builder, x_i, m), l)
 57        };
 58        y[i * 16..i * 16 + n_bytes].clone_from_slice(&y_i[..n_bytes]);
 59    }
 60    y
 61}
 62
 63pub fn inc32_target(
 64    builder: &mut CircuitBuilder<F, D>,
 65    block: [ByteTarget; 16],
 66) -> [ByteTarget; 16] {
 67    let mut r = block;
 68
 69    let mut carry = BoolTarget::new_unsafe(builder.one());
 70    for byte_index in (12..16).rev() {
 71        for bit_index in 0..8 {
 72            let a = block[byte_index][bit_index];
 73            // builder.assert_bool(a); // TODO we can assume it's valid bool, skip check
 74
 75            let sum = xor(builder, a, carry);
 76
 77            let carry_out = builder.and(a, carry);
 78
 79            r[byte_index][bit_index] = sum;
 80            carry = carry_out;
 81        }
 82    }
 83    r
 84}
 85
 86fn xor_byte(builder: &mut CircuitBuilder<F, D>, b1: ByteTarget, b2: ByteTarget) -> ByteTarget {
 87    array::from_fn(|i| xor(builder, b1[i], b2[i]))
 88}
 89
 90fn xor_blocks(
 91    builder: &mut CircuitBuilder<F, D>,
 92    b1: [ByteTarget; 16],
 93    b2: [ByteTarget; 16],
 94) -> [ByteTarget; 16] {
 95    array::from_fn(|i| xor_byte(builder, b1[i], b2[i]))
 96}
 97
 98pub fn msb_t(
 99    builder: &mut CircuitBuilder<F, D>,
100    t_bytes: usize,
101    block: &[ByteTarget],
102) -> Vec<ByteTarget> {
103    let t = t_bytes * 8;
104
105    let full_bytes = t / 8;
106    let rem_bits = t % 8;
107
108    let mut out: Vec<ByteTarget> = Vec::new();
109
110    for i in 0..full_bytes {
111        out.push(block[i]);
112    }
113
114    let zero_bool = BoolTarget::new_unsafe(builder.zero());
115    if rem_bits != 0 {
116        let mut partial = [zero_bool; 8];
117
118        for bit in 0..8 {
119            if bit < rem_bits {
120                partial[bit] = block[full_bytes][bit];
121            } else {
122                partial[bit] = zero_bool;
123            }
124        }
125
126        out.push(partial);
127    }
128    out
129}
130
131#[cfg(test)]
132mod tests {
133    use std::array;
134
135    use anyhow::Result;
136    use plonky2::{
137        field::{goldilocks_field::GoldilocksField as F, types::Field},
138        iop::witness::{PartialWitness, WitnessWrite},
139        plonk::{
140            circuit_builder::CircuitBuilder, circuit_data::CircuitConfig,
141            config::PoseidonGoldilocksConfig,
142        },
143    };
144    use rand::RngExt;
145
146    use super::*;
147    use crate::{
148        circuit_aes::{sbox_lut, state_mix_matrix_bits, PartialWitnessByteArray},
149        native_aes::key_expansion,
150        native_gcm::gctr,
151    };
152
153    use super::D;
154
155    #[test]
156    fn test_gctr() -> Result<()> {
157        // AES-GCM-128
158        test_gctr_opt::<4, 4, 10, 13>()?;
159        test_gctr_opt::<4, 4, 10, 17>()?;
160
161        // AES-GCM-256
162        test_gctr_opt::<8, 4, 14, 13>()?;
163
164        Ok(())
165    }
166
167    fn test_gctr_opt<const NK: usize, const NB: usize, const NR: usize, const L: usize>(
168    ) -> Result<()>
169    where
170        [(); 4 * (NR + 1)]:,
171        [(); NK * NB]:,
172    {
173        let key: &[u8; NK * NB] = &[42; NK * NB];
174        let expanded_key = key_expansion::<NK, NB, NR>(key);
175        let nonce: &[u8; 12] = &[111; 12];
176        let icb: &[u8; 16] = &[222; 16];
177        let pt: &[u8; L] = &[42u8; L];
178
179        let expected = gctr(expanded_key, &icb, pt);
180
181        // Circuit declaration
182        let config = CircuitConfig::standard_recursion_config();
183        let mut builder = CircuitBuilder::<F, D>::new(config);
184
185        let key_target: [ByteTarget; NK * NB] =
186            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
187        let nonce_target: [ByteTarget; 12] =
188            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
189        let icb_target: [ByteTarget; 16] =
190            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
191        let pt_target: [ByteTarget; L] =
192            array::from_fn(|_| array::from_fn(|_| builder.add_virtual_bool_target_safe()));
193        let sbox_lut = sbox_lut(&mut builder);
194        let mix_matrix = state_mix_matrix_bits(&mut builder);
195
196        let key_expanded_target = builder.key_expansion::<NK, NB, NR>(sbox_lut, key_target);
197        let gctr_out_target = gctr_target::<NR, L>(
198            &mut builder,
199            sbox_lut,
200            mix_matrix,
201            key_expanded_target,
202            &icb_target,
203            &pt_target,
204        );
205
206        println!(
207            "encrypt_block (NK:{}, NB:{}, NR:{}, L:{}) num_gates: {}",
208            NK,
209            NB,
210            NR,
211            L,
212            builder.num_gates()
213        );
214        let data = builder.build::<PoseidonGoldilocksConfig>();
215
216        // set values to circuit
217        let mut pw = PartialWitness::<F>::new();
218        std::iter::zip(key_target, key).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
219        std::iter::zip(nonce_target, nonce)
220            .try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
221        std::iter::zip(icb_target, icb).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
222        std::iter::zip(pt_target, pt).try_for_each(|(t, v)| pw.set_byte_array_target(t, *v))?;
223
224        std::iter::zip(gctr_out_target, expected)
225            .try_for_each(|(t, v)| pw.set_byte_array_target(t, v))?;
226
227        let proof = data.prove(pw)?;
228        data.verify(proof)
229    }
230}