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
70fn gctr<const NR: usize>(key: [[u8; 4]; 4 * (NR + 1)], icb: &[u8; 16], x: &[u8]) -> Vec<u8> {
71 if x.is_empty() {
72 return x.to_vec();
73 }
74
75 let n = ((x.len() * 8) as f64 / 128_f64).ceil() as usize;
76
77 let mut y: Vec<Vec<u8>> = vec![];
78 let mut cb_i = *icb;
79 for (i, x_i_raw) in x.chunks(16).enumerate() {
80 if i > 0 {
81 cb_i = inc32(cb_i);
82 }
83
84 let l = x_i_raw.len().min(16);
85 let mut x_i = [0u8; 16];
86 x_i[..l].copy_from_slice(x_i_raw);
87
88 let ciph_cb_i = flatten_state(encrypt_block::<NR>(&cb_i, &key));
89
90 let (y_i, n_bytes) = if i < n && x_i_raw.len() == 16 {
91 (xor_blocks(x_i, ciph_cb_i), 16)
92 } else {
93 // last chunk, might be smaller than 16 bytes
94 let msb_res = msb_t(x_i_raw.len(), ciph_cb_i.as_slice());
95 let l = msb_res.len().min(16);
96 let mut m = [0u8; 16];
97 m[..l].copy_from_slice(&msb_res);
98 (xor_blocks(x_i, m), l)
99 };
100 y.push(y_i[..n_bytes].to_vec());
101 }
102 y.concat()
103}
104
105/// Section 6.4, Algorithm 2
106fn ghash(h: [u8; 16], x: &[u8]) -> [u8; 16] {
107 assert!(x.len().is_multiple_of(16)); // multiple of 128 bits
108 let m = x.len() / 16;
109
110 let mut y = [0u8; 16]; // (128 bits)
111 for i in 0..m {
112 let mut xi = [0u8; 16];
113 xi.clone_from_slice(&x[i * 16..i * 16 + 16]);
114 let y_xi = xor_blocks(y, xi);
115 y = gf_2_128_mul(y_xi, h);
116 }
117 y
118}
119fn xor_blocks(b1: [u8; 16], b2: [u8; 16]) -> [u8; 16] {
120 let mut r = [0u8; 16];
121 for i in 0..16 {
122 r[i] = b1[i] ^ b2[i];
123 }
124 r
125}
126
127/// multiplication of blocks (in GF(2^128)), Algorithm 1, Section 6.3
128pub fn gf_2_128_mul(x: [u8; 16], y: [u8; 16]) -> [u8; 16] {
129 // R = 11100001 || 0^120
130 const R: [u8; 16] = [
131 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
132 0x00,
133 ];
134
135 let mut z = [0u8; 16];
136 let mut v = y;
137 for i in 0..128 {
138 // xi: i-th bit of x
139 let byte_index = i / 8;
140 let bit_index = 7 - (i % 8);
141 let xi = (x[byte_index] >> bit_index) & 1;
142
143 if xi == 1 {
144 z = xor_blocks(z, v);
145 }
146 let lsb = v[15] & 1;
147 right_shift_one(&mut v);
148 if lsb == 1 {
149 v = xor_blocks(v, R);
150 }
151 }
152 z
153}
154fn right_shift_one(block: &mut [u8; 16]) {
155 let mut carry = 0u8;
156
157 for byte in block.iter_mut() {
158 let new_carry = *byte & 1;
159 *byte = (*byte >> 1) | (carry << 7);
160 carry = new_carry;
161 }
162}
163
164/// increment the right-most 32 bits of the given block (128 bits). Section 2.
165pub fn inc32(b: [u8; 16]) -> [u8; 16] {
166 let mut r = b;
167 // counter = last 32bits
168 let counter = u32::from_be_bytes([r[12], r[13], r[14], r[15]]);
169 let incremented = counter.wrapping_add(1);
170 let bytes = incremented.to_be_bytes();
171 r[12..16].copy_from_slice(&bytes);
172 r
173}
174
175/// returns the t left-most (most-significant in BE) bits of the block
176pub fn msb_t(t_bytes: usize, block: &[u8]) -> Vec<u8> {
177 let t = t_bytes * 8;
178
179 let mut out = Vec::with_capacity(t.div_ceil(8));
180 let mut bits_left = t;
181 for byte in block.iter() {
182 if bits_left == 0 {
183 break;
184 }
185 if bits_left >= 8 {
186 out.push(*byte);
187 bits_left -= 8;
188 } else {
189 // take the top `bits_left` bits of the byte
190 let mask = 0xFFu8 << (8 - bits_left);
191 out.push(byte & mask);
192 break;
193 }
194 }
195 out
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 use aes_gcm::{
203 aead::{Aead, KeyInit},
204 Aes128Gcm, Aes256Gcm, Nonce,
205 };
206 use rand::Rng;
207
208 fn hex_to_array<const N: usize>(h: &str) -> [u8; N] {
209 let bytes = hex::decode(h).unwrap();
210 assert_eq!(bytes.len(), N);
211 let mut r: [u8; N] = [0u8; N];
212 r.copy_from_slice(&bytes);
213 r
214 }
215
216 #[test]
217 fn test_aes_gcm_encrypt_nist_vector() {
218 // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES
219
220 // test vector from line 7
221 let key: [u8; 16] = hex_to_array("cf063a34d4a9a76c2c86787d3f96db71");
222 let iv: [u8; 12] = hex_to_array("113b9785971864c83b01c787");
223 let pt = [];
224 let expected_c: &[u8] = &[];
225 let expected_tag: [u8; 16] = hex_to_array("72ac8493e3a5228b5d130a69d2510e42");
226 let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
227 assert_eq!(c, expected_c);
228 assert_eq!(tag, expected_tag);
229
230 // test vector from line 4417
231 let key: [u8; 16] = hex_to_array("e98b72a9881a84ca6b76e0f43e68647a");
232 let iv: [u8; 12] = hex_to_array("8b23299fde174053f3d652ba");
233 let pt: [u8; 16] = hex_to_array("28286a321293253c3e0aa2704a278032");
234 let expected_c: [u8; 16] = hex_to_array("5a3c1cf1985dbb8bed818036fdd5ab42");
235 let expected_tag: [u8; 16] = hex_to_array("23c7ab0f952b7091cd324835043b5eb5");
236 let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
237 assert_eq!(c, expected_c);
238 assert_eq!(tag, expected_tag);
239
240 // test vector from line 8834
241 let key: [u8; 16] = hex_to_array("387218b246c1a8257748b56980e50c94");
242 let iv: [u8; 12] = hex_to_array("dd7e014198672be39f95b69d");
243 let expected_c: [u8; 13] = hex_to_array("cdba9e73eaf3d38eceb2b04a8d");
244 let expected_tag: [u8; 16] = hex_to_array("ecf90f4a47c9c626d6fb2c765d201556");
245 let pt: [u8; 13] = hex_to_array("48f5b426baca03064554cc2b30");
246 let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
247 assert_eq!(c, expected_c);
248 assert_eq!(tag, expected_tag);
249
250 // test vector from line 13237
251 let key: [u8; 16] = hex_to_array("bfd414a6212958a607a0f5d3ab48471d");
252 let iv: [u8; 12] = hex_to_array("86d8ea0ab8e40dcc481cd0e2");
253 let expected_c: [u8; 32] =
254 hex_to_array("62171db33193292d930bf6647347652c1ef33316d7feca99d54f1db4fcf513f8");
255 let expected_tag: [u8; 16] = hex_to_array("c28280aa5c6c7a8bd366f28c1cfd1f6e");
256 let pt: [u8; 32] =
257 hex_to_array("a6b76a066e63392c9443e60272ceaeb9d25c991b0f2e55e2804e168c05ea591a");
258 let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt);
259 assert_eq!(c, expected_c);
260 assert_eq!(tag, expected_tag);
261 }
262
263 /// test checking against external aes-gcm lib
264 #[test]
265 fn test_with_external_lib() -> anyhow::Result<()> {
266 test_with_external_lib_op::<4, 4, 10, Aes128Gcm>()?;
267 test_with_external_lib_op::<8, 4, 14, Aes256Gcm>()
268 }
269 fn test_with_external_lib_op<const NK: usize, const NB: usize, const NR: usize, C>(
270 ) -> anyhow::Result<()>
271 where
272 [(); NK * NB]:,
273 [(); 4 * (NR + 1)]:,
274 C: KeyInit + Aead,
275 {
276 let key: &[u8; NK * NB] = &[42; NK * NB];
277 let nonce: &[u8; 12] = &[111; 12];
278 let pt: Vec<Vec<u8>> = vec![
279 vec![42u8; 0],
280 vec![42u8; 16],
281 vec![42u8; 32],
282 vec![42u8; 1],
283 vec![42u8; 17],
284 b"test plaintext".to_vec(),
285 b"test plaintext for AES-GCM".to_vec(),
286 rand_bytes(131),
287 rand_bytes(1027),
288 rand_bytes(4242),
289 ];
290
291 let nonce_ext = Nonce::from_slice(nonce);
292 let cipher = C::new_from_slice(key.as_slice()).unwrap();
293
294 for pt_i in pt.iter() {
295 let ciphertext = cipher.encrypt(nonce_ext, pt_i.as_ref()).unwrap();
296
297 let plaintext = cipher.decrypt(nonce_ext, ciphertext.as_ref()).unwrap();
298 assert_eq!(plaintext, *pt_i);
299
300 let (c, t) = encrypt::<NK, NB, NR>(key, nonce, pt_i);
301 assert_eq!(c.len() + t.len(), ciphertext.len());
302 assert_eq!([c, t].concat(), ciphertext);
303 }
304 Ok(())
305 }
306 fn rand_bytes(n: usize) -> Vec<u8> {
307 let mut v = vec![0u8; n];
308 rand::rng().fill_bytes(&mut v);
309 v
310 }
311}