nectar_primitives/chunk/encryption/
cipher.rs1use alloy_primitives::Keccak256;
6
7use super::error::EncryptionError;
8use super::key::EncryptionKey;
9
10#[inline]
15fn key_state(key: &EncryptionKey) -> Keccak256 {
16 let mut h = Keccak256::new();
17 h.update(key.as_bytes());
18 h
19}
20
21#[inline]
23fn derive_segment_key(key_state: &Keccak256, counter: u32) -> [u8; 32] {
24 let mut h1 = key_state.clone();
25 h1.update(counter.to_le_bytes());
26 let round1 = h1.finalize();
27
28 let mut h2 = Keccak256::new();
29 h2.update(round1.as_slice());
30 h2.finalize().into()
31}
32
33#[inline]
35fn apply_keystream(key: &EncryptionKey, init_ctr: u32, data: &mut [u8]) {
36 let ks = key_state(key);
37 for (i, chunk) in data.chunks_mut(EncryptionKey::SIZE).enumerate() {
38 let seg = derive_segment_key(&ks, init_ctr.wrapping_add(i as u32));
39 for (j, byte) in chunk.iter_mut().enumerate() {
40 *byte ^= seg[j];
41 }
42 }
43}
44
45#[inline]
53pub fn transcrypt(
54 key: &EncryptionKey,
55 init_ctr: u32,
56 input: &[u8],
57 output: &mut [u8],
58) -> Result<(), EncryptionError> {
59 if output.len() < input.len() {
60 return Err(EncryptionError::OutputBufferTooSmall {
61 len: output.len(),
62 required: input.len(),
63 });
64 }
65 output[..input.len()].copy_from_slice(input);
66 apply_keystream(key, init_ctr, &mut output[..input.len()]);
67 Ok(())
68}
69
70#[inline]
75pub fn transcrypt_in_place(key: &EncryptionKey, init_ctr: u32, data: &mut [u8]) {
76 apply_keystream(key, init_ctr, data);
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
85 fn go_test_vector() {
86 let key_hex = "8abf1502f557f15026716030fb6384792583daf39608a3cd02ff2f47e9bc6e49";
87 let key_bytes: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
88 let key = EncryptionKey::from(key_bytes);
89
90 let input = [0u8; 4096];
91 let mut output = [0u8; 4096];
92 transcrypt(&key, 0, &input, &mut output).unwrap();
93
94 let expected_hex = include_str!("testdata/go_vector_4096.hex");
95 let expected = hex::decode(expected_hex.trim()).unwrap();
96 assert_eq!(output.as_slice(), expected.as_slice());
97 }
98
99 #[test]
100 fn go_test_vector_in_place() {
101 let key_hex = "8abf1502f557f15026716030fb6384792583daf39608a3cd02ff2f47e9bc6e49";
102 let key_bytes: [u8; 32] = hex::decode(key_hex).unwrap().try_into().unwrap();
103 let key = EncryptionKey::from(key_bytes);
104
105 let mut data = [0u8; 4096];
106 transcrypt_in_place(&key, 0, &mut data);
107
108 let expected_hex = include_str!("testdata/go_vector_4096.hex");
109 let expected = hex::decode(expected_hex.trim()).unwrap();
110 assert_eq!(data.as_slice(), expected.as_slice());
111 }
112
113 #[test]
114 fn symmetry() {
115 let key = EncryptionKey::from([0x42; 32]);
116 let plaintext = b"hello world, this is a test!!!!!"; let mut ciphertext = [0u8; 32];
118 let mut recovered = [0u8; 32];
119
120 transcrypt(&key, 0, plaintext, &mut ciphertext).unwrap();
121 assert_ne!(&ciphertext[..], plaintext);
122
123 transcrypt(&key, 0, &ciphertext, &mut recovered).unwrap();
124 assert_eq!(&recovered[..], plaintext);
125 }
126
127 #[test]
128 fn in_place_symmetry() {
129 let key = EncryptionKey::from([0x42; 32]);
130 let original = *b"hello world, this is a test!!!!!";
131 let mut data = original;
132
133 transcrypt_in_place(&key, 0, &mut data);
134 assert_ne!(data, original);
135
136 transcrypt_in_place(&key, 0, &mut data);
137 assert_eq!(data, original);
138 }
139
140 #[test]
141 fn in_place_matches_transcrypt() {
142 let key = EncryptionKey::from([0xbb; 32]);
143 let input = [0x77u8; 256];
144
145 let mut via_transcrypt = [0u8; 256];
146 transcrypt(&key, 3, &input, &mut via_transcrypt).unwrap();
147
148 let mut via_in_place = input;
149 transcrypt_in_place(&key, 3, &mut via_in_place);
150
151 assert_eq!(via_transcrypt, via_in_place);
152 }
153
154 #[test]
155 fn segmented_equals_whole() {
156 let key = EncryptionKey::from([0xaa; 32]);
157 let input = [0x55u8; 128]; let mut whole = [0u8; 128];
159 transcrypt(&key, 0, &input, &mut whole).unwrap();
160
161 let mut segmented = [0u8; 128];
163 for (i, chunk) in input.chunks(EncryptionKey::SIZE).enumerate() {
164 transcrypt(
165 &key,
166 i as u32,
167 chunk,
168 &mut segmented[i * EncryptionKey::SIZE..],
169 )
170 .unwrap();
171 }
172 assert_eq!(whole, segmented);
173 }
174
175 #[test]
176 fn partial_block() {
177 let key = EncryptionKey::from([0x11; 32]);
178 let input = [0xffu8; 17]; let mut output = [0u8; 17];
180 transcrypt(&key, 0, &input, &mut output).unwrap();
181
182 let mut recovered = [0u8; 17];
184 transcrypt(&key, 0, &output, &mut recovered).unwrap();
185 assert_eq!(recovered, input);
186 }
187
188 #[test]
189 fn nonzero_init_ctr() {
190 let key = EncryptionKey::from([0x33; 32]);
191 let input = [0u8; 32];
192 let mut out_ctr0 = [0u8; 32];
193 let mut out_ctr5 = [0u8; 32];
194
195 transcrypt(&key, 0, &input, &mut out_ctr0).unwrap();
196 transcrypt(&key, 5, &input, &mut out_ctr5).unwrap();
197
198 assert_ne!(out_ctr0, out_ctr5);
199 }
200
201 mod hex {
203 pub(super) fn decode(s: &str) -> Result<Vec<u8>, String> {
204 if !s.len().is_multiple_of(2) {
205 return Err("odd length".into());
206 }
207 (0..s.len())
208 .step_by(2)
209 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string()))
210 .collect()
211 }
212 }
213}