use super::target::{below, Words, BITS_OFFSET, NONCE_OFFSET};
use crate::chain::params::HEADER_BYTES;
use hemera::field::Goldilocks;
pub const RATE: usize = 8;
pub const LIMB_BYTES: usize = 7;
pub const LENGTH_LANE: usize = 10;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Job {
pub init: [u64; 16],
pub blocks: [[u64; RATE]; 4],
pub tail_lane: usize,
pub tail: u64,
pub target: Words,
pub header: [u8; HEADER_BYTES],
}
fn limb(b: &[u8]) -> u64 {
let mut w = [0u8; 8];
w[..b.len()].copy_from_slice(b);
u64::from_le_bytes(w)
}
impl Job {
pub fn new(header: &[u8; HEADER_BYTES], target: Words) -> Self {
let mut blocks = [[0u64; RATE]; 4];
for (b, block) in blocks.iter_mut().enumerate().take(3) {
for (i, l) in block.iter_mut().enumerate() {
let at = b * RATE * LIMB_BYTES + i * LIMB_BYTES;
*l = limb(&header[at..at + LIMB_BYTES]);
}
}
blocks[3][0] = 0x01;
blocks[0][3] &= 0x00ff_ffff;
blocks[0][4] = 0;
blocks[0][5] &= !0xff_ffff_ffff;
Job {
init: [0; 16],
blocks,
tail_lane: LENGTH_LANE,
tail: HEADER_BYTES as u64,
target,
header: *header,
}
}
pub fn header_with(&self, n: u128) -> [u8; HEADER_BYTES] {
let mut h = self.header;
h[NONCE_OFFSET..NONCE_OFFSET + 16].copy_from_slice(&n.to_le_bytes());
h
}
pub fn bits(&self) -> u32 {
u32::from_le_bytes(
self.header[BITS_OFFSET..BITS_OFFSET + 4]
.try_into()
.expect("4 bytes"),
)
}
}
pub const fn nonce_lanes(n: u128) -> (u64, u64, u64) {
(
((n as u64) & 0xffff_ffff) << 24,
((n >> 32) as u64) & 0x00ff_ffff_ffff_ffff,
((n >> 88) as u64) & 0xff_ffff_ffff,
)
}
pub fn seed(job: &Job, n: u128) -> [Goldilocks; 16] {
let (a3, a4, a5) = nonce_lanes(n);
let mut s: [Goldilocks; 16] = core::array::from_fn(ļææiļææ Goldilocks::new(job.init[i]));
for (l, b) in s.iter_mut().zip(job.blocks[0]) {
*l += Goldilocks::new(b);
}
s[3] += Goldilocks::new(a3);
s[4] += Goldilocks::new(a4);
s[5] += Goldilocks::new(a5);
s
}
pub fn absorb(job: &Job, s: &mut [Goldilocks; 16], b: usize) {
for (l, v) in s.iter_mut().zip(job.blocks[b]) {
*l += Goldilocks::new(v);
}
if b == 3 {
s[job.tail_lane] = Goldilocks::new(job.tail);
}
}
pub fn digest(s: &[Goldilocks; 16]) -> Words {
core::array::from_fn(ļææiļææ s[i].as_canonical_u64())
}
pub fn passes(job: &Job, s: &[Goldilocks; 16]) -> bool {
below(&digest(s), &job.target)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::header::hash32;
use crate::chain::mine::target::{from_bytes, pow2};
#[test]
fn single_state_sponge_is_hemera_hash() {
let mut header = [0u8; HEADER_BYTES];
for (i, b) in header.iter_mut().enumerate() {
*b = (i as u8).wrapping_mul(29).wrapping_add(3);
}
let job = Job::new(&header, pow2(232));
for n in [0u128, 7, 1 << 40, (1u128 << 64) + 9, u128::MAX] {
let mut s = seed(&job, n);
hemera::permutation::permute(&mut s);
for b in 1..4 {
absorb(&job, &mut s, b);
hemera::permutation::permute(&mut s);
}
assert_eq!(
digest(&s),
from_bytes(&hash32(&job.header_with(n))),
"nonce {n}"
);
}
}
#[test]
fn header_with_writes_only_the_nonce() {
let header = [0xabu8; HEADER_BYTES];
let job = Job::new(&header, pow2(232));
let h = job.header_with(0x0102_0304_0506_0708_090a_0b0c_0d0e_0f10);
assert_eq!(&h[..24], &header[..24]);
assert_eq!(&h[40..], &header[40..]);
assert_eq!(h[24], 0x10);
assert_eq!(h[39], 0x01);
assert_eq!(job.bits(), 0xabab_abab);
}
}