use crate::pow::v1::{
err::SolutionErrorV1, types::Effort, types::Instance, types::NONCE_LEN, types::Nonce,
types::SEED_LEN, types::Seed,
};
use arrayvec::{ArrayVec, CapacityError};
use blake2::{Blake2b, Digest, digest::consts::U4};
const P_STRING: &[u8] = b"Tor hs intro v1\0";
const P_STRING_LEN: usize = 16;
const ID_LEN: usize = 32;
const SEED_OFFSET: usize = P_STRING_LEN + ID_LEN;
const NONCE_OFFSET: usize = SEED_OFFSET + SEED_LEN;
const EFFORT_OFFSET: usize = NONCE_OFFSET + NONCE_LEN;
const EFFORT_LEN: usize = 4;
const CHALLENGE_LEN: usize = EFFORT_OFFSET + EFFORT_LEN;
#[derive(derive_more::AsRef, Debug, Clone, Eq, PartialEq)]
pub(super) struct Challenge([u8; CHALLENGE_LEN]);
impl Challenge {
pub(super) fn new(instance: &Instance, effort: Effort, nonce: &Nonce) -> Self {
let mut result = ArrayVec::<u8, CHALLENGE_LEN>::new();
(|| -> Result<(), CapacityError> {
result.try_extend_from_slice(P_STRING)?;
result.try_extend_from_slice(instance.service().as_ref())?;
assert_eq!(result.len(), SEED_OFFSET);
result.try_extend_from_slice(instance.seed().as_ref())?;
assert_eq!(result.len(), NONCE_OFFSET);
result.try_extend_from_slice(nonce.as_ref())?;
assert_eq!(result.len(), EFFORT_OFFSET);
result.try_extend_from_slice(&effort.as_ref().to_be_bytes())
})()
.expect("CHALLENGE_LEN holds a full challenge string");
Self(
result
.into_inner()
.expect("challenge buffer is fully written"),
)
}
pub(super) fn seed(&self) -> Seed {
let array: [u8; SEED_LEN] = self.0[SEED_OFFSET..(SEED_OFFSET + SEED_LEN)]
.try_into()
.expect("slice length correct");
array.into()
}
pub(super) fn nonce(&self) -> Nonce {
let array: [u8; NONCE_LEN] = self.0[NONCE_OFFSET..(NONCE_OFFSET + NONCE_LEN)]
.try_into()
.expect("slice length correct");
array.into()
}
pub(super) fn effort(&self) -> Effort {
u32::from_be_bytes(
self.0[EFFORT_OFFSET..(EFFORT_OFFSET + EFFORT_LEN)]
.try_into()
.expect("slice length correct"),
)
.into()
}
pub(super) fn increment_nonce(&mut self) {
fn inc_le_bytes(slice: &mut [u8]) {
for byte in slice {
let (value, overflow) = (*byte).overflowing_add(1);
*byte = value;
if !overflow {
break;
}
}
}
inc_le_bytes(&mut self.0[NONCE_OFFSET..(NONCE_OFFSET + NONCE_LEN)]);
}
pub(super) fn check_effort(
&self,
proof: &equix::SolutionByteArray,
) -> Result<(), SolutionErrorV1> {
let mut hasher = Blake2b::<U4>::new();
hasher.update(self.as_ref());
hasher.update(proof.as_ref());
let value = u32::from_be_bytes(hasher.finalize().into());
match value.checked_mul(*self.effort().as_ref()) {
Some(_) => Ok(()),
None => Err(SolutionErrorV1::Effort),
}
}
}