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, Add, Sub, Ror, Rol, Xor, #[default]
27 Ret, }
29
30const INSTRUCTION_COUNT: usize = Ret as usize;
31
32const 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
50fn 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#[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 const OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 2, 1, 2, 2, 1];
85
86 const ASIC_OP_LATENCY: [usize; INSTRUCTION_COUNT] = [3, 1, 1, 1, 1, 1];
88
89 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; let mut data_index: usize = data.len();
108
109 let mut code_size: usize;
110
111 loop {
114 let mut latency = [0_usize; 9];
115 let mut asic_latency = [0_usize; 9];
116
117 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 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 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 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 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 if matches!(opcode, Add | Sub | Xor) && a == b {
199 b = 8;
200 src_index = 8;
201 }
202
203 if is_rotation[opcode as usize] && rotated[a] {
206 continue;
207 }
208
209 if opcode != Mul
215 && inst_data[a] & 0xFFFF00
216 == ((opcode as usize) << 8) + ((inst_data[b] & 255) << 16)
217 {
218 continue;
219 }
220
221 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 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 alu_busy[next_latency - OP_LATENCY[opcode as usize]][alu_index as usize] = true;
269 latency[a] = next_latency;
270
271 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 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 if r8_used && (NUM_INSTRUCTIONS_MIN..=NUM_INSTRUCTIONS_MAX).contains(&code_size) {
344 break;
345 }
346 }
347
348 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#[expect(clippy::needless_return)] pub(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#[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}