#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <stdexcept>
inline uint32_t bytes_to_u32_le(uint8_t const* data)
{
return (static_cast<uint32_t>(data[0])) | (static_cast<uint32_t>(data[1]) << 8)
| (static_cast<uint32_t>(data[2]) << 16) | (static_cast<uint32_t>(data[3]) << 24);
}
inline uint32_t rotl32(uint32_t value, int count)
{
return ((value << count) & 0xFFFFFFFFU) | (value >> (32 - count));
}
inline uint32_t rotl8(uint32_t value) { return ((value << 8) & 0xFFFFFFFFU) | (value >> 24); }
inline uint32_t rotl16(uint32_t value) { return ((value << 16) & 0xFFFFFFFFU) | (value >> 16); }
class ChachaHash {
public:
ChachaHash(uint8_t const* plot_id_bytes, int k_size = 28) : k_size_(k_size)
{
if (!plot_id_bytes)
throw std::invalid_argument("plot_id_bytes pointer is null.");
uint8_t enc_key[32];
enc_key[0] = 1;
std::memcpy(enc_key + 1, plot_id_bytes, 31);
chacha8_keysetup_data(enc_key);
}
uint32_t generate_match_info(uint32_t x)
{
uint32_t x_group = x >> 4;
uint32_t out_hashes[16];
do_chacha16_range(x_group * 16, out_hashes);
return out_hashes[x & 15];
}
void do_chacha16_range(uint32_t x, uint32_t* out_hashes)
{
uint32_t datax[16];
for (int i = 0; i < 12; i++) {
datax[i] = chacha_input[i];
}
datax[12] = x / 16;
datax[13] = 0;
datax[14] = chacha_input[14];
datax[15] = chacha_input[15];
for (int i = 0; i < 4; i++) {
cpu_quarter_round(datax, 0, 4, 8, 12);
cpu_quarter_round(datax, 1, 5, 9, 13);
cpu_quarter_round(datax, 2, 6, 10, 14);
cpu_quarter_round(datax, 3, 7, 11, 15);
cpu_quarter_round(datax, 0, 5, 10, 15);
cpu_quarter_round(datax, 1, 6, 11, 12);
cpu_quarter_round(datax, 2, 7, 8, 13);
cpu_quarter_round(datax, 3, 4, 9, 14);
}
for (int i = 0; i < 16; i++) {
datax[i] = (datax[i] + chacha_input[i]) & 0xFFFFFFFFU;
}
if (k_size_ == 32) {
for (int i = 0; i < 16; i++) {
out_hashes[i] = datax[i];
}
}
else {
uint32_t mask = (1U << k_size_) - 1U;
for (int i = 0; i < 16; i++) {
out_hashes[i] = datax[i] & mask;
}
}
}
static void cpu_quarter_round(uint32_t* datax, int a, int b, int c, int d)
{
datax[a] = (datax[a] + datax[b]) & 0xFFFFFFFFU;
datax[d] = rotl16(datax[d] ^ datax[a]);
datax[c] = (datax[c] + datax[d]) & 0xFFFFFFFFU;
datax[b] = rotl32(datax[b] ^ datax[c], 12);
datax[a] = (datax[a] + datax[b]) & 0xFFFFFFFFU;
datax[d] = rotl8(datax[d] ^ datax[a]);
datax[c] = (datax[c] + datax[d]) & 0xFFFFFFFFU;
datax[b] = rotl32(datax[b] ^ datax[c], 7);
}
void chacha8_keysetup_data(uint8_t* plot_id)
{
char const* constants = "expand 32-byte k";
chacha_input[0] = bytes_to_u32_le(reinterpret_cast<uint8_t const*>(constants));
chacha_input[1] = bytes_to_u32_le(reinterpret_cast<uint8_t const*>(constants + 4));
chacha_input[2] = bytes_to_u32_le(reinterpret_cast<uint8_t const*>(constants + 8));
chacha_input[3] = bytes_to_u32_le(reinterpret_cast<uint8_t const*>(constants + 12));
chacha_input[4] = bytes_to_u32_le(plot_id + 0);
chacha_input[5] = bytes_to_u32_le(plot_id + 4);
chacha_input[6] = bytes_to_u32_le(plot_id + 8);
chacha_input[7] = bytes_to_u32_le(plot_id + 12);
chacha_input[8] = bytes_to_u32_le(plot_id + 16);
chacha_input[9] = bytes_to_u32_le(plot_id + 20);
chacha_input[10] = bytes_to_u32_le(plot_id + 24);
chacha_input[11] = bytes_to_u32_le(plot_id + 28);
chacha_input[12] = 0;
chacha_input[13] = 0;
chacha_input[14] = 0;
chacha_input[15] = 0;
}
private:
int k_size_; uint32_t chacha_input[16]; };