1use crate::slow_hash::{Variant, MEMORY_BLOCKS};
2
3const U64_MASK: u128 = u64::MAX as u128;
4
5pub(crate) fn variant2_shuffle_add(
10 c1: &mut u128,
11 a: u128,
12 b: &[u128; 2],
13 long_state: &mut [u128; MEMORY_BLOCKS],
14 offset: usize,
15 variant: Variant,
16) {
17 if !matches!(variant, Variant::V2 | Variant::R) {
18 return;
19 }
20
21 let chunk1_start = offset ^ 0x1;
22 let chunk2_start = offset ^ 0x2;
23 let chunk3_start = offset ^ 0x3;
24
25 let chunk1 = long_state[chunk1_start];
26 let chunk2 = long_state[chunk2_start];
27 let chunk3 = long_state[chunk3_start];
28
29 let chunk1_old = chunk1;
30 let chunk2_old = chunk2;
31 let chunk3_old = chunk3;
32
33 let b1 = b[1];
34
35 let chunk1 = &mut long_state[chunk1_start];
36 let sum1 = chunk3_old.wrapping_add(b1) & U64_MASK;
37 let sum2 = (chunk3_old >> 64).wrapping_add(b1 >> 64) & U64_MASK;
38 *chunk1 = (sum2 << 64) | sum1; let chunk3 = &mut long_state[chunk3_start];
41 let sum1 = chunk2_old.wrapping_add(a) & U64_MASK;
42 let sum2 = (chunk2_old >> 64).wrapping_add(a >> 64) & U64_MASK;
43 *chunk3 = (sum2 << 64) | sum1;
44
45 let b0 = b[0];
46 let chunk2 = &mut long_state[chunk2_start];
47 let sum1 = chunk1_old.wrapping_add(b0) & U64_MASK;
48 let sum2 = (chunk1_old >> 64).wrapping_add(b0 >> 64) & U64_MASK;
49 *chunk2 = (sum2 << 64) | sum1;
50
51 if variant == Variant::R {
52 *c1 ^= chunk1_old ^ chunk2_old ^ chunk3_old;
53 }
54}
55
56#[expect(
57 clippy::cast_sign_loss,
58 clippy::cast_precision_loss,
59 clippy::cast_possible_truncation
60)]
61pub(crate) fn variant2_integer_math_sqrt(sqrt_input: u64) -> u64 {
62 let mut sqrt_result =
64 ((sqrt_input as f64 + 18_446_744_073_709_552_000.0).sqrt() * 2.0 - 8589934592.0) as u64;
65
66 let sqrt_div2 = sqrt_result >> 1;
69 let lsb = sqrt_result & 1;
70 let r2 = sqrt_div2
71 .wrapping_mul(sqrt_div2 + lsb)
72 .wrapping_add(sqrt_result << 32);
73
74 if r2.wrapping_add(lsb) > sqrt_input {
75 sqrt_result = sqrt_result.wrapping_sub(1);
76 }
77 if r2.wrapping_add(1 << 32) < sqrt_input.wrapping_sub(sqrt_div2) {
78 sqrt_result = sqrt_result.wrapping_add(1);
84 }
85
86 sqrt_result
87}
88
89#[expect(clippy::cast_possible_truncation)]
92pub(crate) fn variant2_integer_math(
93 c2: &mut u128,
94 c1: u128,
95 division_result: &mut u64,
96 sqrt_result: &mut u64,
97 variant: Variant,
98) {
99 const U32_MASK: u64 = u32::MAX as u64;
100
101 if variant != Variant::V2 {
102 return;
103 }
104
105 let tmpx = *division_result ^ (*sqrt_result << 32);
106 *c2 ^= u128::from(tmpx);
107
108 let c1_low = c1 as u64;
109 let dividend = (c1 >> 64) as u64;
110
111 let divisor = ((c1_low.wrapping_add((*sqrt_result << 1) & U32_MASK)) | 0x80000001) & U32_MASK;
112 *division_result = ((dividend / divisor) & U32_MASK).wrapping_add((dividend % divisor) << 32);
113
114 let sqrt_input = c1_low.wrapping_add(*division_result);
115 *sqrt_result = variant2_integer_math_sqrt(sqrt_input);
116}
117
118#[cfg(test)]
119mod tests {
120 use digest::Digest;
121 use groestl::Groestl256;
122
123 use super::*;
124 use crate::{
125 cnaes::AES_BLOCK_SIZE,
126 slow_hash::MEMORY_BLOCKS,
127 util::{hex_to_array, subarray_mut},
128 };
129
130 #[test]
131 fn test_variant2_integer_math() {
132 fn test(
133 c2_hex: &str,
134 c1_hex: &str,
135 division_result: u64,
136 sqrt_result: u64,
137 c2_hex_end: &str,
138 division_result_end: u64,
139 sqrt_result_end: u64,
140 ) {
141 let mut c2 = u128::from_le_bytes(hex_to_array(c2_hex));
142 let c1 = u128::from_le_bytes(hex_to_array(c1_hex));
143 let mut division_result = division_result;
144 let mut sqrt_result = sqrt_result;
145
146 variant2_integer_math(
147 &mut c2,
148 c1,
149 &mut division_result,
150 &mut sqrt_result,
151 Variant::V2,
152 );
153
154 assert_eq!(hex::encode(c2.to_le_bytes()), c2_hex_end);
155 assert_eq!(division_result, division_result_end);
156 assert_eq!(sqrt_result, sqrt_result_end);
157 }
158
159 test(
160 "00000000000000000000000000000000",
161 "0100000000000000ffffffffffffffff",
162 u64::MAX,
163 u64::MAX,
164 "ffffffff000000000000000000000000",
165 1,
166 0,
167 );
168 test(
169 "8b4d610801fe2049741c4cf1a11912d5",
170 "ef9d5925ad73f044f6310bce80f333a4",
171 1992885167645223034,
172 15156498822412360757,
173 "f125c247b4040b0e741c4cf1a11912d5",
174 11701596267494179432,
175 3261805857,
176 );
177 test(
178 "540ac7dbbddf5b93fdc90f999408b7ad",
179 "10d2c1fdcbf7246e8623a3d946bdf422",
180 6226440187041759132,
181 1708636566,
182 "c83510b077a4e4a0fdc90f999408b7ad",
183 6478148604080708997,
184 2875078897,
185 );
186 test(
187 "0df28c3c3570ae3b68dc9d6c5a486ed7",
188 "a5fba99aa63fa032acf1bd65ff4df3f2",
189 11107069037757228366,
190 2924318811,
191 "4397ce171fdcc70f68dc9d6c5a486ed7",
192 7549089838000449301,
193 2299293038,
194 );
195 test(
196 "bfe14f97a968a35d0dcd6890a03c2913",
197 "d4a80e16ad64e3a0624a795c7b349c8a",
198 15584044376391133794,
199 276486141,
200 "dd4bf8759e1a9c950dcd6890a03c2913",
201 4771913259875991617,
202 3210383690,
203 );
204
205 test(
206 "820692e47779a9aabf0621e52a142468",
207 "df61b75f65251ee61828166e565336a9",
208 3269677112081011360,
209 1493829760,
210 "2254426ff54bc3debf0621e52a142468",
211 2626216843989114230,
212 175440206,
213 );
214 test(
215 "0b364e61de218e00e83c4073b39daa2e",
216 "cc463d4543eb430d08efedf2be86e322",
217 7096668609104405526,
218 713261042,
219 "1d521b6fac307148e83c4073b39daa2e",
220 8234613052379859783,
221 1924288792,
222 );
223 test(
224 "bd8fff861f6315c2be812b64cbdcf646",
225 "38d1e323d9dc282fa5e68f2ecbdcb950",
226 9545374795048279136,
227 271106137,
228 "dd532ef48b584a56be812b64cbdcf646",
229 2790373411402251888,
230 1336862722,
231 );
232 test(
233 "ed57e73448f357bf04dc831d5e8fd848",
234 "a5dcd0971e6ded60d4d98c03cd8ba205",
235 5991074580974163125,
236 2246952057,
237 "580331e9a7a59e6904dc831d5e8fd848",
238 7395390641079862703,
239 2868947253,
240 );
241 test(
242 "07ea0ffc6e182a7e97853f82e459d625",
243 "7e403d950f4adc97b90140875c33d65f",
244 8836830558353968711,
245 1962375668,
246 "40a40e3f08db7f7097853f82e459d625",
247 5478469695216926448,
248 3219877666,
249 );
250 test(
251 "b77688d600a356077021e2333ee3def4",
252 "7a9f061760287a69b57f365163fb9dac",
253 3127636279441542418,
254 1585025819,
255 "a5bb34d8bba848727021e2333ee3def4",
256 3683326568856788118,
257 2315202244,
258 );
259 test(
260 "a246a7f62b7e3d9a0b5ac66166bfcba3",
261 "23329476afdbd46d3be9d3ccc9011c11",
262 12123559059253265496,
263 819016365,
264 "fac2e5d23dc4d3020b5ac66166bfcba3",
265 4214751652441358299,
266 2469122821,
267 );
268 test(
269 "3e1abb8109c688405cd6c866cbdb3e13",
270 "b4c10bf5e06c069928afa173f62d5017",
271 7368515032603121941,
272 2312559799,
273 "2b43d451df231caf5cd6c866cbdb3e13",
274 1324536149240623108,
275 2509236669,
276 );
277 test(
278 "a31260db7c73f249b5fbc182ae7fcc8e",
279 "b4214755b0003e4c82d03f80d8a06bed",
280 1904095218141907119,
281 92928147,
282 "0c5abeec6c3f1756b5fbc182ae7fcc8e",
283 9883090335304272258,
284 3041688469,
285 );
286 test(
287 "e3d0bc3e619f577a1eea5adba205e494",
288 "cd8040848aae39104c310c1fa0eed9b8",
289 4873400164336079541,
290 2436984787,
291 "56c22935133bb7a81eea5adba205e494",
292 8226478499779865232,
293 1963241245,
294 );
295 test(
296 "f22ac244fd17cf5e3ec21bece2581a2d",
297 "785152f272ffa9514ef2ae0bed5cbaa7",
298 6386228481616770937,
299 1413583152,
300 "8bddfda13af62e523ec21bece2581a2d",
301 9654977853452823978,
302 3069608655,
303 );
304 test(
305 "37b3921988d9df1b38b04dc1db01a41b",
306 "054b87f38d203eddb16d458048f3b97b",
307 5592059432235016971,
308 2670380708,
309 "3c10afec40e36fc938b04dc1db01a41b",
310 2475375116655310772,
311 3553266751,
312 );
313 test(
314 "cfd4afb021e526d9cbd4720cc47c4ce2",
315 "a2e3e7fe936c2b38e3708965f2dfc586",
316 11958325643570725319,
317 825185219,
318 "0895d52d3237fd4dcbd4720cc47c4ce2",
319 2253955666499039951,
320 1359567468,
321 );
322 test(
323 "55d2ea9570994bc0aeaf6a3189bf0b4a",
324 "9d102c34665382dfd36e39a67e07b8aa",
325 10171590341391886242,
326 541577843,
327 "f7f59fbe85f4246daeaf6a3189bf0b4a",
328 6907584596503955220,
329 1004462004,
330 );
331 test(
332 "bf32b60d6bbaa87cececd577f2ad15d8",
333 "9a8471b2b72e9d39cd2d2cb124aa270a",
334 9778648685358392468,
335 469385479,
336 "2b9696774746e6e0ececd577f2ad15d8",
337 4910280747850874346,
338 1899784302,
339 );
340 test(
341 "d70ac5de7a390e2a735726324d0b52b5",
342 "6cf5b75b005599047972995ffbe34101",
343 2318211298357120319,
344 1093372020,
345 "e8871a66ea410e4b735726324d0b52b5",
346 14587709575956469579,
347 2962700286,
348 );
349 test(
350 "412f463e5143eace451dcb2a2efd8022",
351 "38ed251c7915236b2aca4ea995b861c9",
352 10458537212399393571,
353 621387691,
354 "623403e9d4ecc77a451dcb2a2efd8022",
355 12914179687381327414,
356 495045866,
357 );
358 }
359
360 #[test]
361 fn test_variant2_integer_math_sqrt() {
362 let test_cases = [
365 (0, 0),
366 (1 << 32, 0),
367 ((1 << 32) + 1, 1),
368 (1 << 50, 262140),
369 ((1 << 55) + 20963331, 8384515),
370 ((1 << 55) + 20963332, 8384516),
371 ((1 << 62) + 26599786, 1013904242),
372 ((1 << 62) + 26599787, 1013904243),
373 (u64::MAX, 3558067407),
374 ];
375
376 for &(input, expected) in &test_cases {
377 assert_eq!(
378 variant2_integer_math_sqrt(input),
379 expected,
380 "input = {input}"
381 );
382 }
383 }
384
385 #[test]
386 fn test_variant2_shuffle_add() {
387 #[expect(clippy::cast_possible_truncation)]
388 fn test(
389 c1_hex: &str,
390 a_hex: &str,
391 b_hex: &str,
392 offset: usize,
393 variant: Variant,
394 c1_hex_end: &str,
395 long_state_end_hash: &str,
396 ) {
397 let mut c1 = u128::from_le_bytes(hex_to_array(c1_hex));
398 let a = u128::from_le_bytes(hex_to_array(a_hex));
399 let b: [u128; 2] = [
400 u128::from_le_bytes(hex_to_array(&b_hex[0..AES_BLOCK_SIZE * 2])),
401 u128::from_le_bytes(hex_to_array(&b_hex[AES_BLOCK_SIZE * 2..])),
402 ];
403
404 let mut long_state: Vec<u128> = Vec::with_capacity(MEMORY_BLOCKS);
407 for i in 0..long_state.capacity() {
408 let mut block = [0_u8; AES_BLOCK_SIZE];
409 for (j, byte) in block.iter_mut().enumerate() {
410 *byte = (i * AES_BLOCK_SIZE + j) as u8;
411 }
412 long_state.push(u128::from_le_bytes(block));
413 }
414
415 variant2_shuffle_add(
416 &mut c1,
417 a,
418 &b,
419 subarray_mut(&mut long_state, 0),
420 offset,
421 variant,
422 );
423 assert_eq!(hex::encode(c1.to_le_bytes()), c1_hex_end);
424 let mut hash = Groestl256::new();
425 for block in long_state {
426 hash.update(block.to_le_bytes());
427 }
428 let hash = hex::encode(hash.finalize().as_slice());
429
430 assert_eq!(hash, long_state_end_hash);
431 }
432
433 test(
434 "d7143e3b6ffdeae4b2ceea30e9889c8a",
435 "875fa34de3af48f15638bad52581ef4c",
436 "b07d6f24f19434289b305525f094d8d7bd9d3c9bc956ac081d6186432a282a36",
437 221056 / AES_BLOCK_SIZE,
438 Variant::R,
439 "5795bcb8eb786c633a4760bb65051205",
440 "26c32c4c2eeec340d62b88f5261d1a264c74240c2f8424c6e7101cf490e5772e",
441 );
442 test(
443 "c7d6fe95ffd8d902d2cfc1883f7a2bc3",
444 "bceb9d8cb71c2ac85c24129c94708e17",
445 "4b3a589c187e26bea487b19ea36eb19e8369f4825642eb467c75bf07466b87ba",
446 1960880 / AES_BLOCK_SIZE,
447 Variant::V2,
448 "c7d6fe95ffd8d902d2cfc1883f7a2bc3",
449 "2d4ddadd0e53a02797c62bf37d11bb2de73e6769abd834a81c1262752176a024",
450 );
451 test(
452 "92ad41fc1596244e2e0f0bfed6555cef",
453 "d1f0337e48c4f53742cedd78b6b33b67",
454 "b17bce6c44e0f680aa0f0a28a4e3865b43cdd18644a383e7a9d2f17310e5b6aa",
455 1306832 / AES_BLOCK_SIZE,
456 Variant::R,
457 "427c932fc143f299f6d6d1250a888230",
458 "984440e0b9f77f1159f09b13d2d455292d5a9b4095037f4e8ca2a0ed982bee8f",
459 );
460 test(
461 "7e2c813d10f06d4b8af85389bc82eb18",
462 "74fc41829b88f55e62aec4749685b323",
463 "7a00c480b31d851359d78fad279dcd343bcd6a5f902ac0b55da656d735dbf329",
464 130160 / AES_BLOCK_SIZE,
465 Variant::V2,
466 "7e2c813d10f06d4b8af85389bc82eb18",
467 "6ccb68ee6fc38a6e91f546f62b8e1a64b5223a4a0ef916e6062188c4ee15a879",
468 );
469 }
470}