1use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
2use aes::Aes128;
3use ethers_core::types::U256;
4
5use crate::error::CryptoError;
6use crate::field::{poseidon_hash, string_to_fr};
7
8type Aes128CbcEnc = cbc::Encryptor<Aes128>;
9type Aes128CbcDec = cbc::Decryptor<Aes128>;
10
11#[allow(clippy::expect_used)]
13static KDF_KEY_PURPOSE: std::sync::LazyLock<U256> = std::sync::LazyLock::new(|| {
14 string_to_fr("hisoka.enc_key")
15 .expect("domain string 'hisoka.enc_key' is 14 bytes, always valid")
16});
17
18#[allow(clippy::expect_used)]
19static KDF_IV_PURPOSE: std::sync::LazyLock<U256> = std::sync::LazyLock::new(|| {
20 string_to_fr("hisoka.enc_iv").expect("domain string 'hisoka.enc_iv' is 13 bytes, always valid")
21});
22
23#[must_use]
25pub fn kdf_to_aes_key_iv(shared_secret: U256) -> ([u8; 16], [u8; 16]) {
26 let key_purpose = *KDF_KEY_PURPOSE;
27 let iv_purpose = *KDF_IV_PURPOSE;
28
29 let key_fr = poseidon_hash(&[shared_secret, key_purpose]);
30 let iv_fr = poseidon_hash(&[shared_secret, iv_purpose]);
31
32 let mut key_bytes = [0u8; 32];
33 let mut iv_bytes = [0u8; 32];
34 key_fr.to_big_endian(&mut key_bytes);
35 iv_fr.to_big_endian(&mut iv_bytes);
36
37 let mut key = [0u8; 16];
38 let mut iv = [0u8; 16];
39 key.copy_from_slice(&key_bytes[16..]);
40 iv.copy_from_slice(&iv_bytes[16..]);
41
42 (key, iv)
43}
44
45#[must_use]
47#[allow(clippy::expect_used)]
48pub fn aes128_encrypt(plaintext: &[u8; 192], key: &[u8; 16], iv: &[u8; 16]) -> [u8; 208] {
49 let mut buf = [0u8; 208];
50 buf[..192].copy_from_slice(plaintext);
51
52 let cipher = Aes128CbcEnc::new(key.into(), iv.into());
53 let ciphertext = cipher
55 .encrypt_padded_mut::<Pkcs7>(&mut buf, 192)
56 .expect("buffer is correct size");
57
58 let mut result = [0u8; 208];
59 result.copy_from_slice(ciphertext);
60 result
61}
62
63pub fn aes128_decrypt(
65 ciphertext: &[u8; 208],
66 key: &[u8; 16],
67 iv: &[u8; 16],
68) -> Result<[u8; 192], CryptoError> {
69 let mut buf = [0u8; 208];
70 buf.copy_from_slice(ciphertext);
71
72 let cipher = Aes128CbcDec::new(key.into(), iv.into());
73 let plaintext = cipher
74 .decrypt_padded_mut::<Pkcs7>(&mut buf)
75 .map_err(|_| CryptoError::DecryptionFailed("invalid PKCS#7 padding".to_string()))?;
76
77 if plaintext.len() != 192 {
78 return Err(CryptoError::DecryptionFailed(format!(
79 "expected 192 bytes plaintext, got {}",
80 plaintext.len()
81 )));
82 }
83
84 let mut result = [0u8; 192];
85 result.copy_from_slice(plaintext);
86 Ok(result)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_kdf_produces_valid_lengths() {
95 let shared_secret = U256::from(12345u64);
96 let (key, iv) = kdf_to_aes_key_iv(shared_secret);
97
98 assert_eq!(key.len(), 16);
99 assert_eq!(iv.len(), 16);
100 }
101
102 #[test]
103 fn test_kdf_deterministic() {
104 let shared_secret = U256::from(987654321u64);
105 let (key1, iv1) = kdf_to_aes_key_iv(shared_secret);
106 let (key2, iv2) = kdf_to_aes_key_iv(shared_secret);
107
108 assert_eq!(key1, key2);
109 assert_eq!(iv1, iv2);
110 }
111
112 #[test]
113 fn test_kdf_key_iv_different() {
114 let shared_secret = U256::from(123456789u64);
115 let (key, iv) = kdf_to_aes_key_iv(shared_secret);
116
117 assert_ne!(key, iv);
118 }
119
120 #[test]
121 fn test_aes_encrypt_decrypt_roundtrip() {
122 let key = [0x12u8; 16];
123 let iv = [0x34u8; 16];
124 let plaintext = [0x42u8; 192];
125
126 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
127 assert_eq!(ciphertext.len(), 208);
128 assert_ne!(&ciphertext[..192], &plaintext[..]);
129
130 let decrypted = aes128_decrypt(&ciphertext, &key, &iv).expect("decryption should succeed");
131 assert_eq!(decrypted, plaintext);
132 }
133
134 #[test]
135 fn test_aes_different_keys_different_ciphertext() {
136 let key1 = [0x11u8; 16];
137 let key2 = [0x22u8; 16];
138 let iv = [0x00u8; 16];
139 let plaintext = [0x55u8; 192];
140
141 let ct1 = aes128_encrypt(&plaintext, &key1, &iv);
142 let ct2 = aes128_encrypt(&plaintext, &key2, &iv);
143
144 assert_ne!(ct1, ct2);
145 }
146
147 #[test]
148 fn test_aes_wrong_key_fails() {
149 let key = [0x12u8; 16];
150 let wrong_key = [0x99u8; 16];
151 let iv = [0x34u8; 16];
152 let plaintext = [0x42u8; 192];
153
154 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
155 let result = aes128_decrypt(&ciphertext, &wrong_key, &iv);
156
157 assert!(result.is_err());
158 }
159
160 #[test]
161 fn test_aes_wrong_iv_fails() {
162 let key = [0x12u8; 16];
163 let iv = [0x34u8; 16];
164 let wrong_iv = [0x99u8; 16];
165 let plaintext = [0x42u8; 192];
166
167 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
168 let result = aes128_decrypt(&ciphertext, &key, &wrong_iv);
169
170 if let Ok(decrypted) = result {
171 assert_ne!(decrypted, plaintext);
172 }
173 }
174
175 #[test]
176 fn test_aes_tampered_ciphertext() {
177 let key = [0x12u8; 16];
178 let iv = [0x34u8; 16];
179 let plaintext = [0x42u8; 192];
180
181 let mut ciphertext = aes128_encrypt(&plaintext, &key, &iv);
182
183 ciphertext[207] ^= 0x01;
184
185 let result = aes128_decrypt(&ciphertext, &key, &iv);
186 assert!(result.is_err());
187 }
188
189 #[test]
190 fn test_kdf_different_secrets() {
191 let (key1, iv1) = kdf_to_aes_key_iv(U256::from(1u64));
192 let (key2, iv2) = kdf_to_aes_key_iv(U256::from(2u64));
193
194 assert_ne!(key1, key2);
195 assert_ne!(iv1, iv2);
196 }
197
198 #[test]
199 fn test_full_kdf_encrypt_decrypt_roundtrip() {
200 let shared_secret = U256::from(0xDEADBEEFu64);
201 let (key, iv) = kdf_to_aes_key_iv(shared_secret);
202
203 let mut plaintext = [0u8; 192];
204 for (i, byte) in plaintext.iter_mut().enumerate() {
205 *byte = (i % 256) as u8;
206 }
207
208 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
209 let decrypted = aes128_decrypt(&ciphertext, &key, &iv).expect("should decrypt");
210 assert_eq!(decrypted, plaintext);
211 }
212
213 #[test]
214 fn test_aes_ciphertext_length() {
215 let key = [0xAAu8; 16];
216 let iv = [0xBBu8; 16];
217 let plaintext = [0u8; 192];
218
219 let ct = aes128_encrypt(&plaintext, &key, &iv);
220 assert_eq!(ct.len(), 208);
221 }
222
223 #[test]
224 fn test_decrypt_error_variant() {
225 let key = [0x12u8; 16];
226 let wrong_key = [0x99u8; 16];
227 let iv = [0x34u8; 16];
228 let plaintext = [0x42u8; 192];
229
230 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
231 let result = aes128_decrypt(&ciphertext, &wrong_key, &iv);
232
233 if let Err(e) = result {
234 match e {
235 CryptoError::DecryptionFailed(_) => {} other => panic!("Expected DecryptionFailed, got: {other:?}"),
237 }
238 }
239 }
240
241 #[test]
242 fn test_aes128_parity_with_typescript() {
243 let key = [0u8; 16];
244 let iv = [0u8; 16];
245 let plaintext = [1u8; 192];
246
247 let ct = aes128_encrypt(&plaintext, &key, &iv);
248 let ct_hex = hex::encode(ct);
249
250 let expected_hex = "e14d5d0ee27715df08b4152ba23da8e066224f25d2578c169989600e70029eac0f990cdc49f2d1fbeca95ad327def624092611708a75d10f1476b8ed6499538208f47f32921f2f184cd4323eb9d24935ea8316bc08ad5e26b76f839e93cf1e26217f8c3755e8345a5d3fc257235b7c5728814627e7c922096920484cefc7dc83a9d39bfc7abb06c8576c247a650edd007ed65ff16cd8ea38a80d8b055f3ded1748499828bcc7c0baf772b5c08f4b7dc0deead18cab10f6904fea5e4ffb8b9fdd1e83330fd492c872204b49b8105231a4";
251
252 assert_eq!(
253 ct_hex, expected_hex,
254 "AES-128-CBC ciphertext mismatch!\n Rust: {ct_hex}\n TS: {expected_hex}"
255 );
256 }
257}