cuprate_cryptonight/
hash_v4.rs

1use std::cmp::max;
2
3use seq_macro::seq;
4use InstructionList::{Add, Mul, Ret, Rol, Ror, Sub, Xor};
5
6use crate::{
7    blake256::{Blake256, Digest},
8    util::subarray_copy,
9};
10
11const TOTAL_LATENCY: usize = 15 * 3;
12const NUM_INSTRUCTIONS_MIN: usize = 60;
13pub(crate) const NUM_INSTRUCTIONS_MAX: usize = 70;
14const ALU_COUNT_MUL: usize = 1;
15const ALU_COUNT: usize = 3;
16
17#[repr(u8)]
18#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub(crate) enum InstructionList {
20    Mul, // a*b
21    Add, // a+b + C, C is an unsigned 32-bit constant
22    Sub, // a-b
23    Ror, // rotate right "a" by "b & 31" bits
24    Rol, // rotate left "a" by "b & 31" bits
25    Xor, // a^b
26    #[default]
27    Ret, // finish execution
28}
29
30const INSTRUCTION_COUNT: usize = Ret as usize;
31
32/// INSTRUCTION_* constants are used to generate code from random data.
33/// Every random sequence of bytes is a valid code.
34///
35/// There are 9 registers in total:
36/// - 4 variable registers
37/// - 5 constant registers initialized from loop variables
38const INSTRUCTION_OPCODE_BITS: usize = 3;
39const INSTRUCTION_DST_INDEX_BITS: usize = 2;
40const INSTRUCTION_SRC_INDEX_BITS: usize = 3;
41
42#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43pub(crate) struct Instruction {
44    pub(crate) opcode: InstructionList,
45    pub(crate) dst_index: u8,
46    pub(crate) src_index: u8,
47    pub(crate) c: u32,
48}
49
50/// If we don't have enough data available, generate more.
51/// Original C code:
52/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/variant4_random_math.h#L171-L178>
53fn check_data(data_index: &mut usize, bytes_needed: usize, data: &mut [u8]) {
54    if *data_index + bytes_needed > data.len() {
55        let output = Blake256::digest(&data);
56        data.copy_from_slice(&output);
57        *data_index = 0;
58    }
59}
60
61/// Generates as many random math operations as possible with given latency and
62/// ALU restrictions.
63///
64/// Original C code:
65/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/variant4_random_math.h#L180-L439>
66///
67#[expect(clippy::cast_sign_loss)]
68#[expect(clippy::cast_possible_wrap)]
69#[expect(clippy::cast_possible_truncation)]
70pub(crate) fn random_math_init(
71    code: &mut [Instruction; NUM_INSTRUCTIONS_MAX + 1],
72    height: u64,
73) -> usize {
74    // MUL is 3 cycles, 3-way addition and rotations are 2 cycles, SUB/XOR are 1
75    // cycle These latencies match real-life instruction latencies for Intel
76    // CPUs starting from Sandy Bridge and up to Skylake/Coffee lake
77    //
78    // AMD Ryzen has the same latencies except 1-cycle ROR/ROL, so it'll be a bit
79    // faster than Intel Sandy Bridge and newer processors Surprisingly, Intel
80    // Nehalem also has 1-cycle ROR/ROL, so it'll also be faster than Intel Sandy
81    // Bridge and newer processors AMD Bulldozer has 4 cycles latency for MUL
82    // (slower than Intel) and 1 cycle for ROR/ROL (faster than Intel), so average
83    // performance will be the same Source: https://www.agner.org/optimize/instruction_tables.pdf
84    const OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 2, 1, 2, 2, 1];
85
86    // Instruction latencies for theoretical ASIC implementation
87    const ASIC_OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 1, 1, 1, 1, 1];
88
89    // Available ALUs for each instruction
90    const OP_ALUS: [usize; INSTRUCTION_COUNT] = [
91        ALU_COUNT_MUL,
92        ALU_COUNT,
93        ALU_COUNT,
94        ALU_COUNT,
95        ALU_COUNT,
96        ALU_COUNT,
97    ];
98
99    let mut data = [0_u8; 32];
100    data[0..8].copy_from_slice(&height.to_le_bytes());
101
102    data[20] = -38_i8 as u8; // change seed
103
104    // Set data_index past the last byte in data
105    // to trigger full data update with blake hash
106    // before we start using it
107    let mut data_index: usize = data.len();
108
109    let mut code_size: usize;
110
111    // There is a small chance (1.8%) that register R8 won't be used in the
112    // generated program, so we keep track of it and try again if it's not used
113    loop {
114        let mut latency = [0_usize; 9];
115        let mut asic_latency = [0_usize; 9];
116
117        // Tracks previous instruction and value of the source operand for
118        // registers R0-R3 throughout code execution:
119        // byte 0: current value of the destination register
120        // byte 1: instruction opcode
121        // byte 2: current value of the source register
122        //
123        // Registers R4-R8 are constant and are treated as having the same
124        // value, because when we do the same operation twice with two constant
125        // source registers, it can be optimized into a single operation.
126        let mut inst_data: [usize; 9] =
127            [0, 1, 2, 3, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF];
128
129        let mut alu_busy = [[false; ALU_COUNT]; TOTAL_LATENCY + 1];
130        let mut is_rotation = [false; INSTRUCTION_COUNT];
131        is_rotation[Ror as usize] = true;
132        is_rotation[Rol as usize] = true;
133        let mut rotated = [false; 4];
134        let mut rotate_count = 0_usize;
135
136        let mut num_retries = 0_usize;
137        code_size = 0;
138
139        let mut total_iterations = 0_usize;
140        let mut r8_used = false;
141
142        // Generate random code to achieve minimal required latency for our abstract CPU
143        // Try to get this latency for all 4 registers
144        while (latency[0] < TOTAL_LATENCY
145            || latency[1] < TOTAL_LATENCY
146            || latency[2] < TOTAL_LATENCY
147            || latency[3] < TOTAL_LATENCY)
148            && num_retries < 64
149        {
150            // Fail-safe to guarantee loop termination
151            total_iterations += 1;
152            if total_iterations > 256 {
153                break;
154            }
155
156            check_data(&mut data_index, 1, &mut data);
157
158            let c = data[data_index];
159            data_index += 1;
160
161            // MUL = opcodes 0-2
162            // ADD = opcode 3
163            // SUB = opcode 4
164            // ROR/ROL = opcode 5, shift direction is selected randomly
165            // XOR = opcodes 6-7
166            let opcode_bits = c & ((1 << INSTRUCTION_OPCODE_BITS) - 1);
167            let opcode: InstructionList;
168            if opcode_bits == 5 {
169                check_data(&mut data_index, 1, &mut data);
170                opcode = if data[data_index] as i8 >= 0 {
171                    Ror
172                } else {
173                    Rol
174                };
175                data_index += 1;
176            } else if opcode_bits >= 6 {
177                opcode = Xor;
178            } else if opcode_bits <= 2 {
179                opcode = Mul;
180            } else {
181                // remaining values are 3-4
182                opcode = match opcode_bits {
183                    3 => Add,
184                    4 => Sub,
185                    _ => unreachable!(),
186                };
187            }
188
189            let dst_index =
190                (c >> INSTRUCTION_OPCODE_BITS) & ((1 << INSTRUCTION_DST_INDEX_BITS) - 1);
191            let mut src_index = (c >> (INSTRUCTION_OPCODE_BITS + INSTRUCTION_DST_INDEX_BITS))
192                & ((1 << INSTRUCTION_SRC_INDEX_BITS) - 1);
193
194            let a = dst_index as usize;
195            let mut b = src_index as usize;
196
197            // Don't do ADD/SUB/XOR with the same register
198            if matches!(opcode, Add | Sub | Xor) && a == b {
199                b = 8;
200                src_index = 8;
201            }
202
203            // Don't do rotation with the same destination twice because it's equal to a
204            // single rotation
205            if is_rotation[opcode as usize] && rotated[a] {
206                continue;
207            }
208
209            // Don't do the same instruction (except MUL) with the same source value twice,
210            // because all other cases can be optimized:
211            //      2xADD(a, b, C) = ADD(a,b*2, C1+C2),
212            // Same for SUB and rotations:
213            //      2xXOR(a, b) = NOP
214            if opcode != Mul
215                && inst_data[a] & 0xFFFF00
216                    == ((opcode as usize) << 8) + ((inst_data[b] & 255) << 16)
217            {
218                continue;
219            }
220
221            // Find which ALU is available (and when) for this instruction
222            let mut next_latency = if latency[a] > latency[b] {
223                latency[a]
224            } else {
225                latency[b]
226            };
227            let mut alu_index = -1;
228            while next_latency < TOTAL_LATENCY {
229                for i in (0..OP_ALUS[opcode as usize]).rev() {
230                    if alu_busy[next_latency][i] {
231                        continue;
232                    }
233
234                    if opcode == Add && alu_busy[next_latency + 1][i] {
235                        continue;
236                    }
237
238                    if is_rotation[opcode as usize]
239                        && next_latency < (rotate_count * OP_LATENCY[opcode as usize])
240                    {
241                        continue;
242                    }
243
244                    alu_index = i as isize;
245                    break;
246                }
247                if alu_index >= 0 {
248                    break;
249                }
250                next_latency += 1;
251            }
252
253            // Don't generate instructions that leave some register unchanged for more than
254            // 7 cycles
255            if next_latency > latency[a] + 7 {
256                continue;
257            }
258
259            next_latency += OP_LATENCY[opcode as usize];
260
261            if next_latency <= TOTAL_LATENCY {
262                if is_rotation[opcode as usize] {
263                    rotate_count += 1;
264                }
265
266                // Mark ALU as busy only for the first cycle when it starts executing the
267                // instruction because ALUs are fully pipelined.
268                alu_busy[next_latency - OP_LATENCY[opcode as usize]][alu_index as usize] = true;
269                latency[a] = next_latency;
270
271                // ASIC is supposed to have enough ALUs to run as many independent instructions
272                // per cycle as possible, so latency calculation for ASIC is straightforward.
273                asic_latency[a] =
274                    max(asic_latency[a], asic_latency[b]) + ASIC_OP_LATENCY[opcode as usize];
275
276                rotated[a] = is_rotation[opcode as usize];
277
278                inst_data[a] = code_size + ((opcode as usize) << 8) + ((inst_data[b] & 255) << 16);
279
280                code[code_size].opcode = opcode;
281                code[code_size].dst_index = dst_index;
282                code[code_size].src_index = src_index;
283                code[code_size].c = 0;
284
285                if src_index == 8 {
286                    r8_used = true;
287                }
288
289                if opcode == Add {
290                    alu_busy[next_latency - OP_LATENCY[opcode as usize] + 1][alu_index as usize] =
291                        true;
292
293                    check_data(&mut data_index, size_of::<u32>(), &mut data);
294                    code[code_size].c = u32::from_le_bytes(subarray_copy(&data, data_index));
295                    data_index += 4;
296                }
297                code_size += 1;
298                if code_size >= NUM_INSTRUCTIONS_MIN {
299                    break;
300                }
301            } else {
302                num_retries += 1;
303            }
304        }
305
306        // ASIC has more execution resources and can extract as much parallelism
307        // from the code as possible. We need to add a few more MUL and ROR
308        // instructions to achieve minimal required latency for ASIC. Get this
309        // latency for at least 1 of the 4 registers.
310        let prev_code_size = code_size;
311        while code_size < NUM_INSTRUCTIONS_MAX
312            && asic_latency.iter().take(4).all(|&lat| lat < TOTAL_LATENCY)
313        {
314            let mut min_idx: usize = 0;
315            let mut max_idx: usize = 0;
316            for i in 1..4 {
317                if asic_latency[i] < asic_latency[min_idx] {
318                    min_idx = i;
319                }
320                if asic_latency[i] > asic_latency[max_idx] {
321                    max_idx = i;
322                }
323            }
324
325            let pattern = [Ror, Mul, Mul];
326            let opcode = pattern[(code_size - prev_code_size) % 3];
327            latency[min_idx] = latency[max_idx] + OP_LATENCY[opcode as usize];
328            asic_latency[min_idx] = asic_latency[max_idx] + ASIC_OP_LATENCY[opcode as usize];
329
330            code[code_size] = Instruction {
331                opcode,
332                dst_index: min_idx as u8,
333                src_index: max_idx as u8,
334                c: 0,
335            };
336            code_size += 1;
337        }
338
339        // There is ~98.15% chance that loop condition is false, so this loop will
340        // execute only 1 iteration most of the time. It never does more than 4
341        // iterations for all block heights < 10,000,000.
342
343        if r8_used && (NUM_INSTRUCTIONS_MIN..=NUM_INSTRUCTIONS_MAX).contains(&code_size) {
344            break;
345        }
346    }
347
348    // It's guaranteed that NUM_INSTRUCTIONS_MIN <= code_size <=
349    // NUM_INSTRUCTIONS_MAX here. Add final instruction to stop the interpreter.
350    code[code_size].opcode = Ret;
351    code[code_size].dst_index = 0;
352    code[code_size].src_index = 0;
353    code[code_size].c = 0;
354
355    code_size
356}
357
358/// Original C code:
359/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/variant4_random_math.h#L81-L168>
360#[expect(clippy::needless_return)] // last iteration of unrolled loop
361pub(crate) fn v4_random_math(code: &[Instruction; NUM_INSTRUCTIONS_MAX + 1], r: &mut [u32; 9]) {
362    const REG_BITS: u32 = 32;
363
364    debug_assert_eq!(NUM_INSTRUCTIONS_MAX, 70);
365    seq!(i in 0..70 {
366        let op = &code[i];
367        let src = r[op.src_index as usize];
368        let dst = &mut r[op.dst_index as usize];
369        match op.opcode {
370            Mul => *dst = dst.wrapping_mul(src),
371            Add => *dst = dst.wrapping_add(src).wrapping_add(op.c),
372            Sub => *dst = dst.wrapping_sub(src),
373            Ror => *dst = dst.rotate_right(src % REG_BITS),
374            Rol => *dst = dst.rotate_left(src % REG_BITS),
375            Xor => *dst ^= src,
376            Ret => return,
377        }
378    });
379}
380
381/// Original C code:
382/// <https://github.com/monero-project/monero/blob/v0.18.3.4/src/crypto/slow-hash.c#L336-L370>
383/// To match the C code organization, this function would be in `slow_hash.rs`, but
384/// the test code for it is so large, that it was moved here.
385#[expect(clippy::cast_possible_truncation)]
386pub(crate) fn variant4_random_math(
387    a1: &mut u128,
388    c2: &mut u128,
389    r: &mut [u32; 9],
390    b: &[u128; 2],
391    code: &[Instruction; 71],
392) {
393    let t64 = u64::from(r[0].wrapping_add(r[1])) | (u64::from(r[2].wrapping_add(r[3])) << 32);
394    *c2 ^= u128::from(t64);
395
396    r[4] = *a1 as u32;
397    r[5] = (*a1 >> 64) as u32;
398    r[6] = b[0] as u32;
399    r[7] = b[1] as u32;
400    r[8] = (b[1] >> 64) as u32;
401
402    v4_random_math(code, r);
403
404    *a1 ^= u128::from(r[2])
405        | (u128::from(r[3]) << 32)
406        | (u128::from(r[0]) << 64)
407        | (u128::from(r[1]) << 96);
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crate::util::hex_to_array;
414
415    #[rustfmt::skip]
416    const CODE: [Instruction; 71] = [
417        Instruction { opcode: Rol, dst_index: 0, src_index: 7, c: 0 },
418        Instruction { opcode: Mul, dst_index: 3, src_index: 1, c: 0 },
419        Instruction { opcode: Add, dst_index: 2, src_index: 7, c: 3553557725 },
420        Instruction { opcode: Sub, dst_index: 0, src_index: 8, c: 0 },
421        Instruction { opcode: Add, dst_index: 3, src_index: 4, c: 3590470404 },
422        Instruction { opcode: Xor, dst_index: 1, src_index: 0, c: 0 },
423        Instruction { opcode: Xor, dst_index: 1, src_index: 5, c: 0 },
424        Instruction { opcode: Xor, dst_index: 1, src_index: 0, c: 0 },
425        Instruction { opcode: Mul, dst_index: 0, src_index: 7, c: 0 },
426        Instruction { opcode: Mul, dst_index: 2, src_index: 1, c: 0 },
427        Instruction { opcode: Mul, dst_index: 2, src_index: 4, c: 0 },
428        Instruction { opcode: Mul, dst_index: 2, src_index: 7, c: 0 },
429        Instruction { opcode: Sub, dst_index: 1, src_index: 8, c: 0 },
430        Instruction { opcode: Add, dst_index: 0, src_index: 6, c: 1516169632 },
431        Instruction { opcode: Add, dst_index: 2, src_index: 0, c: 1587456779 },
432        Instruction { opcode: Mul, dst_index: 3, src_index: 5, c: 0 },
433        Instruction { opcode: Mul, dst_index: 1, src_index: 0, c: 0 },
434        Instruction { opcode: Xor, dst_index: 2, src_index: 0, c: 0 },
435        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
436        Instruction { opcode: Sub, dst_index: 3, src_index: 6, c: 0 },
437        Instruction { opcode: Rol, dst_index: 3, src_index: 0, c: 0 },
438        Instruction { opcode: Xor, dst_index: 2, src_index: 4, c: 0 },
439        Instruction { opcode: Mul, dst_index: 3, src_index: 5, c: 0 },
440        Instruction { opcode: Xor, dst_index: 2, src_index: 0, c: 0 },
441        Instruction { opcode: Rol, dst_index: 2, src_index: 4, c: 0 },
442        Instruction { opcode: Xor, dst_index: 3, src_index: 8, c: 0 },
443        Instruction { opcode: Mul, dst_index: 0, src_index: 4, c: 0 },
444        Instruction { opcode: Add, dst_index: 2, src_index: 3, c: 2235486112 },
445        Instruction { opcode: Xor, dst_index: 0, src_index: 3, c: 0 },
446        Instruction { opcode: Mul, dst_index: 0, src_index: 2, c: 0 },
447        Instruction { opcode: Xor, dst_index: 2, src_index: 7, c: 0 },
448        Instruction { opcode: Mul, dst_index: 0, src_index: 7, c: 0 },
449        Instruction { opcode: Ror, dst_index: 0, src_index: 4, c: 0 },
450        Instruction { opcode: Mul, dst_index: 3, src_index: 2, c: 0 },
451        Instruction { opcode: Add, dst_index: 2, src_index: 3, c: 382729823 },
452        Instruction { opcode: Mul, dst_index: 1, src_index: 4, c: 0 },
453        Instruction { opcode: Sub, dst_index: 3, src_index: 5, c: 0 },
454        Instruction { opcode: Add, dst_index: 3, src_index: 7, c: 446636115 },
455        Instruction { opcode: Sub, dst_index: 0, src_index: 5, c: 0 },
456        Instruction { opcode: Add, dst_index: 1, src_index: 8, c: 1136500848 },
457        Instruction { opcode: Xor, dst_index: 3, src_index: 8, c: 0 },
458        Instruction { opcode: Mul, dst_index: 0, src_index: 4, c: 0 },
459        Instruction { opcode: Ror, dst_index: 3, src_index: 5, c: 0 },
460        Instruction { opcode: Mul, dst_index: 2, src_index: 0, c: 0 },
461        Instruction { opcode: Ror, dst_index: 0, src_index: 1, c: 0 },
462        Instruction { opcode: Add, dst_index: 0, src_index: 7, c: 4221005163 },
463        Instruction { opcode: Rol, dst_index: 0, src_index: 2, c: 0 },
464        Instruction { opcode: Add, dst_index: 0, src_index: 7, c: 1789679560 },
465        Instruction { opcode: Xor, dst_index: 0, src_index: 3, c: 0 },
466        Instruction { opcode: Add, dst_index: 2, src_index: 8, c: 2725270475 },
467        Instruction { opcode: Xor, dst_index: 1, src_index: 4, c: 0 },
468        Instruction { opcode: Sub, dst_index: 3, src_index: 8, c: 0 },
469        Instruction { opcode: Xor, dst_index: 3, src_index: 5, c: 0 },
470        Instruction { opcode: Sub, dst_index: 3, src_index: 2, c: 0 },
471        Instruction { opcode: Rol, dst_index: 2, src_index: 2, c: 0 },
472        Instruction { opcode: Add, dst_index: 3, src_index: 6, c: 4110965463 },
473        Instruction { opcode: Xor, dst_index: 2, src_index: 6, c: 0 },
474        Instruction { opcode: Sub, dst_index: 2, src_index: 7, c: 0 },
475        Instruction { opcode: Sub, dst_index: 3, src_index: 1, c: 0 },
476        Instruction { opcode: Sub, dst_index: 1, src_index: 8, c: 0 },
477        Instruction { opcode: Ror, dst_index: 1, src_index: 2, c: 0 },
478        Instruction { opcode: Mul, dst_index: 0, src_index: 1, c: 0 },
479        Instruction { opcode: Mul, dst_index: 2, src_index: 0, c: 0 },
480        Instruction { opcode: Ret, dst_index: 0, src_index: 0, c: 0 },
481        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
482        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
483        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
484        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
485        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
486        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
487        Instruction { opcode: Mul, dst_index: 0, src_index: 0, c: 0 },
488    ];
489
490    #[test]
491    fn test1_variant4_random_math() {
492        let mut a1 = u128::from_le_bytes(hex_to_array("969ecd223474a6bb3be76c637db7457b"));
493        let mut c2 = u128::from_le_bytes(hex_to_array("dbd1f6404d4826c52f209951334e6ea7"));
494        let mut r: [u32; 9] = [1336109178, 464004736, 1552145461, 3528897376, 0, 0, 0, 0, 0];
495        let b_bytes: [u8; 32] =
496            hex_to_array("8dfa6d2c82e1806367b844c15f0439ced99c9a4bae0badfb8a8cf8504b813b7d");
497        let b: [u128; 2] = [
498            u128::from_le_bytes(subarray_copy(&b_bytes, 0)),
499            u128::from_le_bytes(subarray_copy(&b_bytes, 16)),
500        ];
501
502        variant4_random_math(&mut a1, &mut c2, &mut r, &b, &CODE);
503
504        assert_eq!(
505            hex::encode(a1.to_le_bytes()),
506            "1cb6fe7738de9e764dd73ea37c438056"
507        );
508        assert_eq!(
509            hex::encode(c2.to_le_bytes()),
510            "215fbd2bd8c7fceb2f209951334e6ea7"
511        );
512        #[rustfmt::skip]
513        assert_eq!(r, [
514            3226611830, 767947777, 1429416074, 3443042828, 583900822, 1668081467, 745405069,
515            1268423897, 1358466186
516        ]);
517    }
518
519    #[test]
520    fn test2_variant4_random_math() {
521        let mut a1 = u128::from_le_bytes(hex_to_array("643955bde578c845e4898703c3ce5eaa"));
522        let mut c2 = u128::from_le_bytes(hex_to_array("787e2613b8fd0a2dadad16d4ec189035"));
523        let mut r: [u32; 9] = [
524            3226611830, 767947777, 1429416074, 3443042828, 583900822, 1668081467, 745405069,
525            1268423897, 1358466186,
526        ];
527        let b_bytes: [u8; 32] =
528            hex_to_array("d4d1e70f7da4089ae53b2e7545e4242a8dfa6d2c82e1806367b844c15f0439ce");
529        let b: [u128; 2] = [
530            u128::from_le_bytes(subarray_copy(&b_bytes, 0)),
531            u128::from_le_bytes(subarray_copy(&b_bytes, 16)),
532        ];
533
534        variant4_random_math(&mut a1, &mut c2, &mut r, &b, &CODE);
535
536        assert_eq!(
537            hex::encode(a1.to_le_bytes()),
538            "c40cb4b3a3640a958cc919ccb4ff29e6"
539        );
540        assert_eq!(
541            hex::encode(c2.to_le_bytes()),
542            "0f5a3efd2e2f610fadad16d4ec189035"
543        );
544        #[rustfmt::skip]
545        assert_eq!(r, [
546            3483254888_u32, 1282879863, 249640352, 3502382150, 3176479076, 59214308, 266850772,
547            745405069, 3242506343
548        ]);
549    }
550}