cuprate_cryptonight/
hash_v2.rs

1use crate::slow_hash::{Variant, MEMORY_BLOCKS};
2
3const U64_MASK: u128 = u64::MAX as u128;
4
5/// Original C code:
6/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/slow-hash.c#L217-L254>
7/// If we kept the C code organization, this function would be in `slow_hash.rs`, but it's
8/// here in the rust code to keep the `slow_hash.rs` file size manageable.
9pub(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; // TODO remove some shifting above
39
40    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    // Get an approximation using floating point math
63    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    // Fixup the edge cases to get the exact integer result. For more information,
67    // see: https://github.com/monero-project/monero/blob/v0.18.3.3/src/crypto/variant2_int_sqrt.h#L65-L152
68    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        // Not sure that this is possible. I tried writing a test program
79        // to search subsets of u64 for a value that can trigger this
80        // branch, but couldn't find anything. The Go implementation came
81        // to the same conclusion:
82        // https://github.com/Equim-chan/cryptonight/blob/v0.3.0/arith_ref.go#L39-L45
83        sqrt_result = sqrt_result.wrapping_add(1);
84    }
85
86    sqrt_result
87}
88
89/// Original C code:
90/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/slow-hash.c#L277-L283>
91#[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        // Edge case values taken from here:
363        // https://github.com/monero-project/monero/blob/v0.18.3.3/src/crypto/variant2_int_sqrt.h#L33-L43
364        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            // Every byte of long_state memory is initialized with it's offset index mod 256
405            // when the u128 blocks are converted to bytes in native endian format.
406            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}