arnaucube
arnaucube/plonky2-crypto-gadgetsPublic Self-hosted repository
plonky2-crypto-gadgets/src/native_gcm.rs
src/native_gcm.rsRaw
  1//! For LICENSE check out https://github.com/0xPARC/plonky2-aes/blob/main/LICENSE
  2//!
  3//! Rust native implementation of [AES-GCM (Galois Counter
  4//! Mode)](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf)
  5//! having in mind the approach that will be done in-circuit; this means that it
  6//! is not written in the most rust idiomatic way, nor memory-efficient, but
  7//! simulating the behavior that we will do later inside the circuit.
  8//!
  9
 10use crate::native_aes::{encrypt_block, flatten_state, key_expansion};
 11
 12// supported tag length
 13const TAG_LEN: usize = 128;
 14
 15/// Section 7.1 from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
 16pub fn encrypt<const NK: usize, const NB: usize, const NR: usize>(
 17    key: &[u8; NK * NB],
 18    nonce: &[u8; 12],
 19    pt: &[u8], // plaintext
 20) -> (Vec<u8>, Vec<u8>)
 21where
 22    [(); 4 * (NR + 1)]:,
 23{
 24    let a: &[u8] = &[]; // additional authenticated data // TODO maybe as input
 25    let expanded_key: [[u8; 4]; 4 * (NR + 1)] = key_expansion::<NK, NB, NR>(key);
 26
 27    // 1. CIPH_K
 28    let h = encrypt_block::<NR>(&[0u8; 16], &expanded_key);
 29    let h = flatten_state(h);
 30
 31    // 2. J_0
 32    let j0: [u8; 16] = if nonce.len() * 8 == 96 {
 33        // J_0 = IV || 0^31 || 1
 34        let mut out = [0u8; 16];
 35        out[..12].copy_from_slice(nonce.as_slice());
 36        out[12..16].copy_from_slice(&[0x00, 0x00, 0x00, 0x01]);
 37        out
 38    } else {
 39        panic!("unsuported at initial version; nonce.len()=12 (96 bits)");
 40    };
 41
 42    // 3. C=GCTR()
 43    let c = gctr(expanded_key, &inc32(j0), pt);
 44
 45    // 4. u, v
 46    let u: usize = 16 * (c.len() as f64 / 16_f64).ceil() as usize - c.len();
 47    let v: usize = 16 * (a.len() as f64 / 16_f64).ceil() as usize - a.len();
 48
 49    // 5. S = GHASH()
 50    let a_len: [u8; 8] = (a.len() * 8).to_be_bytes();
 51    let c_len: [u8; 8] = (c.len() * 8).to_be_bytes();
 52    let ghash_input = &[
 53        a.to_vec(),
 54        vec![0u8; v],
 55        c.clone(),
 56        vec![0u8; u],
 57        a_len.to_vec(),
 58        c_len.to_vec(),
 59    ]
 60    .concat();
 61    let s = ghash(h, ghash_input);
 62
 63    // 6. T=MSB(GCTR()))
 64    let t = msb_t(TAG_LEN, &gctr(expanded_key, &j0, &s));
 65
 66    (c, t)
 67}
 68
 69/// GCM-Ctr, Section 6.5, Algorithm 3
 70pub(crate) fn gctr<const NR: usize>(
 71    key: [[u8; 4]; 4 * (NR + 1)],
 72    icb: &[u8; 16],
 73    x: &[u8],
 74) -> Vec<u8> {
 75    if x.is_empty() {
 76        return x.to_vec();
 77    }
 78
 79    let n = ((x.len() * 8) as f64 / 128_f64).ceil() as usize;
 80
 81    let mut y: Vec<Vec<u8>> = vec![];
 82    let mut cb_i = *icb;
 83    for (i, x_i_raw) in x.chunks(16).enumerate() {
 84        if i > 0 {
 85            cb_i = inc32(cb_i);
 86        }
 87
 88        let l = x_i_raw.len().min(16);
 89        let mut x_i = [0u8; 16];
 90        x_i[..l].copy_from_slice(x_i_raw);
 91
 92        let ciph_cb_i = flatten_state(encrypt_block::<NR>(&cb_i, &key));
 93
 94        let (y_i, n_bytes) = if i < n && x_i_raw.len() == 16 {
 95            (xor_blocks(x_i, ciph_cb_i), 16)
 96        } else {
 97            // last chunk, might be smaller than 16 bytes
 98            let msb_res = msb_t(x_i_raw.len(), ciph_cb_i.as_slice());
 99            let l = msb_res.len().min(16);
100            let mut m = [0u8; 16];
101            m[..l].copy_from_slice(&msb_res);
102            (xor_blocks(x_i, m), l)
103        };
104        y.push(y_i[..n_bytes].to_vec());
105    }
106    y.concat()
107}
108
109/// Section 6.4, Algorithm 2
110pub(crate) fn ghash(h: [u8; 16], x: &[u8]) -> [u8; 16] {
111    assert!(x.len().is_multiple_of(16)); // multiple of 128 bits
112    let m = x.len() / 16;
113
114    let mut y = [0u8; 16]; // (128 bits)
115    for i in 0..m {
116        let mut xi = [0u8; 16];
117        xi.clone_from_slice(&x[i * 16..i * 16 + 16]);
118        let y_xi = xor_blocks(y, xi);
119        y = gf_2_128_mul(y_xi, h);
120    }
121    y
122}
123fn xor_blocks(b1: [u8; 16], b2: [u8; 16]) -> [u8; 16] {
124    let mut r = [0u8; 16];
125    for i in 0..16 {
126        r[i] = b1[i] ^ b2[i];
127    }
128    r
129}
130
131/// multiplication of blocks (in GF(2^128)), Algorithm 1, Section 6.3
132pub fn gf_2_128_mul(x: [u8; 16], y: [u8; 16]) -> [u8; 16] {
133    // R = 11100001 || 0^120
134    const R: [u8; 16] = [
135        0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
136        0x00,
137    ];
138
139    let mut z = [0u8; 16];
140    let mut v = y;
141    for i in 0..128 {
142        // xi: i-th bit of x
143        let byte_index = i / 8;
144        let bit_index = 7 - (i % 8);
145        let xi = (x[byte_index] >> bit_index) & 1;
146
147        if xi == 1 {
148            z = xor_blocks(z, v);
149        }
150        let lsb = v[15] & 1;
151        right_shift_one(&mut v);
152        if lsb == 1 {
153            v = xor_blocks(v, R);
154        }
155    }
156    z
157}
158
159pub(crate) fn right_shift_one(block: &mut [u8; 16]) {
160    let mut carry = 0u8;
161
162    for byte in block.iter_mut() {
163        let new_carry = *byte & 1;
164        *byte = (*byte >> 1) | (carry << 7);
165        carry = new_carry;
166    }
167}
168
169/// increment the right-most 32 bits of the given block (128 bits). Section 2.
170pub(crate) fn inc32(b: [u8; 16]) -> [u8; 16] {
171    let mut r = b;
172    // counter = last 32bits
173    let counter = u32::from_be_bytes([r[12], r[13], r[14], r[15]]);
174    let incremented = counter.wrapping_add(1);
175    let bytes = incremented.to_be_bytes();
176    r[12..16].copy_from_slice(&bytes);
177    r
178}
179
180/// returns the t left-most (most-significant in BE) bits of the block
181pub(crate) fn msb_t(t_bytes: usize, block: &[u8]) -> Vec<u8> {
182    let t = t_bytes * 8;
183
184    let mut out = Vec::with_capacity(t.div_ceil(8));
185    let mut bits_left = t;
186    for byte in block.iter() {
187        if bits_left == 0 {
188            break;
189        }
190        if bits_left >= 8 {
191            out.push(*byte);
192            bits_left -= 8;
193        } else {
194            // take the top `bits_left` bits of the byte
195            let mask = 0xFFu8 << (8 - bits_left);
196            out.push(byte & mask);
197            break;
198        }
199    }
200    out
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    use aes_gcm::{
208        aead::{Aead, KeyInit},
209        Aes128Gcm, Aes256Gcm, Nonce,
210    };
211    use rand::Rng;
212
213    fn hex_to_array<const N: usize>(h: &str) -> [u8; N] {
214        let bytes = hex::decode(h).unwrap();
215        assert_eq!(bytes.len(), N);
216        let mut r: [u8; N] = [0u8; N];
217        r.copy_from_slice(&bytes);
218        r
219    }
220
221    #[test]
222    fn test_aes_gcm_encrypt_nist_vector() {
223        // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES
224
225        // test vector from line 7
226        let key: [u8; 16] = hex_to_array("cf063a34d4a9a76c2c86787d3f96db71");
227        let iv: [u8; 12] = hex_to_array("113b9785971864c83b01c787");
228        let pt = [];
229        let expected_c: &[u8] = &[];
230        let expected_tag: [u8; 16] = hex_to_array("72ac8493e3a5228b5d130a69d2510e42");
231        let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
232        assert_eq!(c, expected_c);
233        assert_eq!(tag, expected_tag);
234
235        // test vector from line 4417
236        let key: [u8; 16] = hex_to_array("e98b72a9881a84ca6b76e0f43e68647a");
237        let iv: [u8; 12] = hex_to_array("8b23299fde174053f3d652ba");
238        let pt: [u8; 16] = hex_to_array("28286a321293253c3e0aa2704a278032");
239        let expected_c: [u8; 16] = hex_to_array("5a3c1cf1985dbb8bed818036fdd5ab42");
240        let expected_tag: [u8; 16] = hex_to_array("23c7ab0f952b7091cd324835043b5eb5");
241        let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
242        assert_eq!(c, expected_c);
243        assert_eq!(tag, expected_tag);
244
245        // test vector from line 8834
246        let key: [u8; 16] = hex_to_array("387218b246c1a8257748b56980e50c94");
247        let iv: [u8; 12] = hex_to_array("dd7e014198672be39f95b69d");
248        let expected_c: [u8; 13] = hex_to_array("cdba9e73eaf3d38eceb2b04a8d");
249        let expected_tag: [u8; 16] = hex_to_array("ecf90f4a47c9c626d6fb2c765d201556");
250        let pt: [u8; 13] = hex_to_array("48f5b426baca03064554cc2b30");
251        let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
252        assert_eq!(c, expected_c);
253        assert_eq!(tag, expected_tag);
254
255        // test vector from line 13237
256        let key: [u8; 16] = hex_to_array("bfd414a6212958a607a0f5d3ab48471d");
257        let iv: [u8; 12] = hex_to_array("86d8ea0ab8e40dcc481cd0e2");
258        let expected_c: [u8; 32] =
259            hex_to_array("62171db33193292d930bf6647347652c1ef33316d7feca99d54f1db4fcf513f8");
260        let expected_tag: [u8; 16] = hex_to_array("c28280aa5c6c7a8bd366f28c1cfd1f6e");
261        let pt: [u8; 32] =
262            hex_to_array("a6b76a066e63392c9443e60272ceaeb9d25c991b0f2e55e2804e168c05ea591a");
263        let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
264        assert_eq!(c, expected_c);
265        assert_eq!(tag, expected_tag);
266    }
267
268    /// test checking against external aes-gcm lib
269    #[test]
270    fn test_with_external_lib() -> anyhow::Result<()> {
271        test_with_external_lib_op::<4, 4, 10, Aes128Gcm>()?;
272        test_with_external_lib_op::<8, 4, 14, Aes256Gcm>()
273    }
274    fn test_with_external_lib_op<const NK: usize, const NB: usize, const NR: usize, C>(
275    ) -> anyhow::Result<()>
276    where
277        [(); NK * NB]:,
278        [(); 4 * (NR + 1)]:,
279        C: KeyInit + Aead,
280    {
281        let key: &[u8; NK * NB] = &[42; NK * NB];
282        let nonce: &[u8; 12] = &[111; 12];
283        let pt: Vec<Vec<u8>> = vec![
284            vec![42u8; 0],
285            vec![42u8; 16],
286            vec![42u8; 32],
287            vec![42u8; 1],
288            vec![42u8; 17],
289            b"test plaintext".to_vec(),
290            b"test plaintext for AES-GCM".to_vec(),
291            rand_bytes(131),
292            rand_bytes(1027),
293            rand_bytes(4242),
294        ];
295
296        let nonce_ext = Nonce::from_slice(nonce);
297        let cipher = C::new_from_slice(key.as_slice()).unwrap();
298
299        for pt_i in pt.iter() {
300            let ciphertext = cipher.encrypt(nonce_ext, pt_i.as_ref()).unwrap();
301
302            let plaintext = cipher.decrypt(nonce_ext, ciphertext.as_ref()).unwrap();
303            assert_eq!(plaintext, *pt_i);
304
305            let (c, t) = encrypt::<NK, NB, NR>(key, nonce, pt_i);
306            assert_eq!(c.len() + t.len(), ciphertext.len());
307            assert_eq!([c, t].concat(), ciphertext);
308        }
309        Ok(())
310    }
311    fn rand_bytes(n: usize) -> Vec<u8> {
312        let mut v = vec![0u8; n];
313        rand::rng().fill_bytes(&mut v);
314        v
315    }
316}