1use crate::cipher::{Key, Nonce, Pad, KEY_SIZE, NONCE_SIZE, PAD_SIZE};
2use bytes::Bytes;
3use xor_name::XorName;
4
5pub(crate) fn xor(data: &Bytes, &Pad(pad): &Pad) -> Bytes {
7 let vec: Vec<_> = data
8 .iter()
9 .zip(pad.iter().cycle())
10 .map(|(&a, &b)| a ^ b)
11 .collect();
12 Bytes::from(vec)
13}
14
15pub fn extract_hashes(data_map: &crate::DataMap) -> Vec<XorName> {
16 data_map.infos().iter().map(|c| c.src_hash).collect()
17}
18
19pub(crate) fn get_pad_key_and_nonce(
20 chunk_index: usize,
21 chunk_hashes: &[XorName],
22 child_level: usize,
23) -> crate::Result<(Pad, Key, Nonce)> {
24 let (n_1, n_2) = get_n_1_n_2(chunk_index, chunk_hashes.len())?;
25
26 let src_hash = chunk_hashes
27 .get(chunk_index)
28 .ok_or_else(|| crate::Error::Generic(format!("chunk_index {chunk_index} out of bounds")))?;
29 let n_1_src_hash = chunk_hashes
30 .get(n_1)
31 .ok_or_else(|| crate::Error::Generic(format!("n_1 index {n_1} out of bounds")))?;
32 let n_2_src_hash = chunk_hashes
33 .get(n_2)
34 .ok_or_else(|| crate::Error::Generic(format!("n_2 index {n_2} out of bounds")))?;
35
36 Ok(get_pki(
37 src_hash,
38 n_1_src_hash,
39 n_2_src_hash,
40 chunk_index,
41 child_level,
42 ))
43}
44
45pub(crate) fn get_n_1_n_2(
46 chunk_index: usize,
47 total_num_chunks: usize,
48) -> crate::Result<(usize, usize)> {
49 if total_num_chunks < 3 {
50 return Err(crate::Error::Generic(format!(
51 "total_num_chunks must be at least 3, got {total_num_chunks}"
52 )));
53 }
54 if chunk_index >= total_num_chunks {
55 return Err(crate::Error::Generic(format!(
56 "chunk_index {chunk_index} out of bounds for total_num_chunks {total_num_chunks}"
57 )));
58 }
59 match chunk_index {
60 0 => Ok((total_num_chunks - 1, total_num_chunks - 2)),
61 1 => Ok((0, total_num_chunks - 1)),
62 n => Ok((n - 1, n - 2)),
63 }
64}
65
66pub(crate) fn get_pki(
67 src_hash: &XorName,
68 n_1_src_hash: &XorName,
69 n_2_src_hash: &XorName,
70 chunk_index: usize,
71 child_level: usize,
72) -> (Pad, Key, Nonce) {
73 let mut context_material = Vec::with_capacity(32 + 32 + 32 + 8 + 8);
78 context_material.extend_from_slice(&src_hash.0);
79 context_material.extend_from_slice(&n_1_src_hash.0);
80 context_material.extend_from_slice(&n_2_src_hash.0);
81 context_material.extend_from_slice(&(chunk_index as u64).to_le_bytes());
82 context_material.extend_from_slice(&(child_level as u64).to_le_bytes());
83
84 let mut output = [0u8; PAD_SIZE + KEY_SIZE + NONCE_SIZE];
85 let mut hasher = blake3::Hasher::new_derive_key("self_encryption/chunk/v2");
86 let _ = hasher.update(&context_material);
87 let mut output_reader = hasher.finalize_xof();
88 output_reader.fill(&mut output);
89
90 let mut pad = [0u8; PAD_SIZE];
91 let mut key = [0u8; KEY_SIZE];
92 let mut nonce = [0u8; NONCE_SIZE];
93
94 pad.copy_from_slice(&output[..PAD_SIZE]);
95 key.copy_from_slice(&output[PAD_SIZE..PAD_SIZE + KEY_SIZE]);
96 nonce.copy_from_slice(&output[PAD_SIZE + KEY_SIZE..]);
97
98 (Pad(pad), Key(key), Nonce(nonce))
99}
100
101pub(crate) fn get_num_chunks(file_size: usize) -> usize {
103 get_num_chunks_with_variable_max(file_size, crate::MAX_CHUNK_SIZE)
104}
105
106pub(crate) fn get_num_chunks_with_variable_max(file_size: usize, max_chunk_size: usize) -> usize {
108 if file_size < (3 * crate::MIN_CHUNK_SIZE) {
109 return 0;
110 }
111 if file_size < (3 * max_chunk_size) {
112 return 3;
113 }
114 if file_size.is_multiple_of(max_chunk_size) {
115 file_size / max_chunk_size
116 } else {
117 (file_size / max_chunk_size) + 1
118 }
119}
120
121pub(crate) fn get_chunk_size(file_size: usize, chunk_index: usize) -> usize {
123 get_chunk_size_with_variable_max(file_size, chunk_index, crate::MAX_CHUNK_SIZE)
124}
125
126pub(crate) fn get_chunk_size_with_variable_max(
128 file_size: usize,
129 chunk_index: usize,
130 max_chunk_size: usize,
131) -> usize {
132 if file_size < 3 * crate::MIN_CHUNK_SIZE {
133 return 0;
134 }
135 if file_size < 3 * max_chunk_size {
136 if chunk_index < 2 {
137 return file_size / 3;
138 } else {
139 return file_size - (2 * (file_size / 3));
141 }
142 }
143 let total_chunks = get_num_chunks_with_variable_max(file_size, max_chunk_size);
144 if chunk_index < total_chunks - 2 {
145 return max_chunk_size;
146 }
147 let remainder = file_size % max_chunk_size;
148 let penultimate = (total_chunks - 2) == chunk_index;
149 if remainder == 0 {
150 return max_chunk_size;
151 }
152 if remainder < crate::MIN_CHUNK_SIZE {
153 if penultimate {
154 max_chunk_size - crate::MIN_CHUNK_SIZE
155 } else {
156 crate::MIN_CHUNK_SIZE + remainder
157 }
158 } else if penultimate {
159 max_chunk_size
160 } else {
161 remainder
162 }
163}
164
165pub(crate) fn get_start_end_positions(file_size: usize, chunk_index: usize) -> (usize, usize) {
167 if get_num_chunks(file_size) == 0 {
168 return (0, 0);
169 }
170 let start = get_start_position(file_size, chunk_index);
171 (start, start + get_chunk_size(file_size, chunk_index))
172}
173
174pub(crate) fn get_start_position(file_size: usize, chunk_index: usize) -> usize {
175 let total_chunks = get_num_chunks(file_size);
176 if total_chunks == 0 {
177 return 0;
178 }
179 let last = (total_chunks - 1) == chunk_index;
180 let first_chunk_size = get_chunk_size(file_size, 0);
181 if last {
182 first_chunk_size * (chunk_index - 1) + get_chunk_size(file_size, chunk_index - 1)
183 } else {
184 first_chunk_size * chunk_index
185 }
186}
187
188#[allow(dead_code)]
189pub(crate) fn get_chunk_index(file_size: usize, position: usize) -> usize {
190 let num_chunks = get_num_chunks(file_size);
191 if num_chunks == 0 {
192 return 0; }
194
195 let chunk_size = get_chunk_size(file_size, 0);
196 let remainder = file_size % chunk_size;
197
198 if remainder == 0
199 || remainder >= crate::MIN_CHUNK_SIZE
200 || position < file_size - remainder - crate::MIN_CHUNK_SIZE
201 {
202 usize::min(position / chunk_size, num_chunks - 1)
203 } else {
204 num_chunks - 1
205 }
206}
207
208#[cfg(test)]
209#[allow(clippy::unwrap_used, clippy::expect_used)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_different_chunks_same_predecessors_yield_different_key_nonce() {
215 let n1 = XorName([0xAA; 32]);
216 let n2 = XorName([0xBB; 32]);
217
218 let src_hash_a = XorName([0x01; 32]);
219 let src_hash_b = XorName([0x02; 32]);
220
221 let (pad_a, key_a, nonce_a) = get_pki(&src_hash_a, &n1, &n2, 0, 0);
222 let (pad_b, key_b, nonce_b) = get_pki(&src_hash_b, &n1, &n2, 0, 0);
223
224 assert_ne!(
225 key_a.0, key_b.0,
226 "different src_hash must produce different keys"
227 );
228 assert_ne!(
229 nonce_a.0, nonce_b.0,
230 "different src_hash must produce different nonces"
231 );
232 assert_ne!(
233 pad_a.0, pad_b.0,
234 "different src_hash must produce different pads"
235 );
236 }
237
238 #[test]
239 fn test_kdf_domain_separation_by_chunk_index() {
240 let src = XorName([0x11; 32]);
241 let n1 = XorName([0x22; 32]);
242 let n2 = XorName([0x33; 32]);
243
244 let (_, key_0, nonce_0) = get_pki(&src, &n1, &n2, 0, 0);
245 let (_, key_1, nonce_1) = get_pki(&src, &n1, &n2, 1, 0);
246
247 assert_ne!(
248 key_0.0, key_1.0,
249 "different chunk_index must produce different keys"
250 );
251 assert_ne!(
252 nonce_0.0, nonce_1.0,
253 "different chunk_index must produce different nonces"
254 );
255 }
256
257 #[test]
258 fn test_kdf_domain_separation_by_child_level() {
259 let src = XorName([0x11; 32]);
260 let n1 = XorName([0x22; 32]);
261 let n2 = XorName([0x33; 32]);
262
263 let (_, key_c0, nonce_c0) = get_pki(&src, &n1, &n2, 0, 0);
264 let (_, key_c1, nonce_c1) = get_pki(&src, &n1, &n2, 0, 1);
265
266 assert_ne!(
267 key_c0.0, key_c1.0,
268 "different child_level must produce different keys"
269 );
270 assert_ne!(
271 nonce_c0.0, nonce_c1.0,
272 "different child_level must produce different nonces"
273 );
274 }
275
276 #[test]
277 fn test_kdf_deterministic() {
278 let src = XorName([0x42; 32]);
279 let n1 = XorName([0xAA; 32]);
280 let n2 = XorName([0xBB; 32]);
281
282 let (pad_a, key_a, nonce_a) = get_pki(&src, &n1, &n2, 5, 2);
283 let (pad_b, key_b, nonce_b) = get_pki(&src, &n1, &n2, 5, 2);
284
285 assert_eq!(pad_a.0, pad_b.0, "same inputs must produce identical pads");
286 assert_eq!(key_a.0, key_b.0, "same inputs must produce identical keys");
287 assert_eq!(
288 nonce_a.0, nonce_b.0,
289 "same inputs must produce identical nonces"
290 );
291 }
292
293 #[test]
294 fn test_kdf_avalanche_single_bit_flip() {
295 let src_a = [0x42u8; 32];
296 let src_b = {
297 let mut b = src_a;
298 b[0] ^= 0x01; b
300 };
301
302 let n1 = XorName([0xAA; 32]);
303 let n2 = XorName([0xBB; 32]);
304
305 let (pad_a, key_a, nonce_a) = get_pki(&XorName(src_a), &n1, &n2, 0, 0);
306 let (pad_b, key_b, nonce_b) = get_pki(&XorName(src_b), &n1, &n2, 0, 0);
307
308 let key_diff = key_a
310 .0
311 .iter()
312 .zip(key_b.0.iter())
313 .filter(|(a, b)| a != b)
314 .count();
315 let nonce_diff = nonce_a
316 .0
317 .iter()
318 .zip(nonce_b.0.iter())
319 .filter(|(a, b)| a != b)
320 .count();
321 let pad_diff = pad_a
322 .0
323 .iter()
324 .zip(pad_b.0.iter())
325 .filter(|(a, b)| a != b)
326 .count();
327
328 assert!(
329 key_diff >= 10,
330 "expected at least 10/32 key bytes to differ, got {}",
331 key_diff
332 );
333 assert!(
334 nonce_diff >= 4,
335 "expected at least 4/12 nonce bytes to differ, got {}",
336 nonce_diff
337 );
338 assert!(
339 pad_diff >= 15,
340 "expected at least 15/52 pad bytes to differ, got {}",
341 pad_diff
342 );
343 }
344
345 #[test]
346 fn test_get_n_1_n_2_out_of_bounds() {
347 let result = get_n_1_n_2(5, 3);
348 assert!(result.is_err(), "chunk_index >= total should fail");
349 let result = get_n_1_n_2(3, 3);
350 assert!(result.is_err(), "chunk_index == total should fail");
351 let result = get_n_1_n_2(2, 3);
352 assert!(result.is_ok(), "chunk_index < total should succeed");
353 }
354
355 #[test]
356 fn test_kdf_output_sizes() {
357 let src = XorName([0x01; 32]);
358 let n1 = XorName([0x02; 32]);
359 let n2 = XorName([0x03; 32]);
360
361 let (pad, key, nonce) = get_pki(&src, &n1, &n2, 0, 0);
362
363 assert_eq!(pad.0.len(), PAD_SIZE, "pad must be {PAD_SIZE} bytes");
364 assert_eq!(key.0.len(), KEY_SIZE, "key must be {KEY_SIZE} bytes");
365 assert_eq!(
366 nonce.0.len(),
367 NONCE_SIZE,
368 "nonce must be {NONCE_SIZE} bytes"
369 );
370 }
371}