use sha3::{Keccak256Full, Digest};
use super::{aesu::derive_key, otheru::{add_pair_u64_2, blake256_hash, groestl256_hash, jh256_hash, mul_pair_u64_2, skein256_hash, turn_to_u64, turn_to_u64_2, turn_to_u8_16, xor_pair_u64_2}};
use crate::crypt::cryptonight::aesu::{aes_round, xor};
const SCRATCHPAD_SIZE: usize = 2 * 1024 * 1024;
pub fn cn_slow_hash(input: &[u8]) -> String {
let mut scratchpad = [0u8; SCRATCHPAD_SIZE];
let mut keccak_hash = [0u8; 200];
let mut hasher = Keccak256Full::new();
hasher.update(input);
keccak_hash.copy_from_slice(&hasher.finalize());
let aes_key = &keccak_hash[0..32];
let round_keys = derive_key(aes_key);
let mut blocks = [0u8; 128];
blocks.copy_from_slice(&keccak_hash[64..192]);
for scratchpad_chunk in scratchpad.chunks_exact_mut(blocks.len()) {
for block in blocks.chunks_exact_mut(16) {
for key in round_keys.chunks_exact(16) {
aes_round(block, key);
}
}
scratchpad_chunk.copy_from_slice(&blocks);
}
let mut sp_u64_2 = [[0u64; 2]; 131072];
for (i, sp_u64_2_chunk) in sp_u64_2.iter_mut().enumerate() {
let u64_slice = unsafe {
std::slice::from_raw_parts(scratchpad[i * 16..(i + 1) * 16].as_ptr() as *const u64, 2)
};
sp_u64_2_chunk.copy_from_slice(u64_slice);
}
let a_1: u64 = turn_to_u64(&keccak_hash[0..8]) ^ turn_to_u64(&keccak_hash[32..40]);
let a_2: u64 = turn_to_u64(&keccak_hash[8..16]) ^ turn_to_u64(&keccak_hash[40..48]);
let b_1: u64 = turn_to_u64(&keccak_hash[16..24]) ^ turn_to_u64(&keccak_hash[48..56]);
let b_2: u64 = turn_to_u64(&keccak_hash[24..32]) ^ turn_to_u64(&keccak_hash[56..64]);
let mut a: [u64; 2] = [a_1, a_2];
let mut b: [u64; 2] = [b_1, b_2];
for _ in 0..524_288 {
let addr: usize = (a[0] & 0x1F_FFF0) as usize / 16;
let block = &mut turn_to_u8_16(sp_u64_2[addr]);
aes_round(block, &turn_to_u8_16(a));
sp_u64_2[addr] = turn_to_u64_2(*block);
let tmp = b;
b = sp_u64_2[addr];
let man = xor_pair_u64_2(sp_u64_2[addr], tmp);
sp_u64_2[addr] = man;
let addr: usize = (b[0] & 0x1F_FFF0) as usize / 16;
let tmp = add_pair_u64_2(a, mul_pair_u64_2(b, sp_u64_2[addr]));
a = xor_pair_u64_2(sp_u64_2[addr], tmp);
sp_u64_2[addr] = tmp;
}
for (i, sp_u64_2_chunk) in sp_u64_2.iter().enumerate() {
let u8_slice = unsafe {
std::slice::from_raw_parts(sp_u64_2_chunk.as_ptr() as *const u8, 16)
};
scratchpad[i * 16..(i + 1) * 16].copy_from_slice(u8_slice);
}
let round_keys_buffer = derive_key(&keccak_hash[32..64]);
let final_block = &mut keccak_hash[64..192];
for scratchpad_chunk in scratchpad.chunks_exact(128) {
xor(final_block, scratchpad_chunk);
for block in final_block.chunks_exact_mut(16) {
for key in round_keys_buffer.chunks_exact(16) {
aes_round(block, key);
}
}
}
let mut keccak_state = [0u64; 25];
for (index, chunk) in keccak_hash.chunks_exact(8).enumerate() {
keccak_state[index] = u64::from_le_bytes(chunk.try_into().unwrap());
}
tiny_keccak::keccakf(&mut keccak_state);
for (index, chunk) in keccak_state.iter().enumerate() {
keccak_hash[index * 8..(index + 1) * 8].copy_from_slice(&chunk.to_le_bytes());
}
let hash_function = keccak_hash[0] & 0x03;
let final_byte = match hash_function {
0 => blake256_hash(keccak_hash),
1 => groestl256_hash(keccak_hash),
2 => jh256_hash(keccak_hash),
3 => skein256_hash(keccak_hash),
x => unreachable!("Hash function {} not implemented", x),
};
let mut final_hex = String::new();
for byte in final_byte.iter() {
final_hex.push_str(&format!("{:02x}", byte));
}
final_hex
}