use std::{
fmt,
io::{self, Read, Write},
mem::size_of,
};
use serde::{Deserialize, Serialize};
use crate::{
config::Config,
hash::{double_sha512_2inputs, sha512, Hash512},
io::{LenBm, ReadFrom, WriteTo},
time::Time,
var_type::VarInt,
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Nonce(u64);
impl fmt::Display for Nonce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
impl From<u64> for Nonce {
fn from(value: u64) -> Self {
Self(value)
}
}
impl Nonce {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl WriteTo for Nonce {
fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
self.0.write_to(w)
}
}
impl ReadFrom for Nonce {
fn read_from(r: &mut dyn Read) -> io::Result<Self>
where
Self: Sized,
{
Ok(Self(u64::read_from(r)?))
}
}
impl LenBm for Nonce {
fn len_bm(&self) -> usize {
self.0.len_bm()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
pub struct NonceTrialsPerByte(u64);
impl fmt::Display for NonceTrialsPerByte {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u64> for NonceTrialsPerByte {
fn from(value: u64) -> Self {
Self(value)
}
}
impl NonceTrialsPerByte {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl WriteTo for NonceTrialsPerByte {
fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
let v: VarInt = self.0.into();
v.write_to(w)
}
}
impl ReadFrom for NonceTrialsPerByte {
fn read_from(r: &mut dyn Read) -> io::Result<Self>
where
Self: Sized,
{
let v = VarInt::read_from(r)?;
Ok(Self(v.as_u64()))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
pub struct PayloadLengthExtraBytes(u64);
impl fmt::Display for PayloadLengthExtraBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<u64> for PayloadLengthExtraBytes {
fn from(value: u64) -> Self {
Self(value)
}
}
impl PayloadLengthExtraBytes {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn as_u64(self) -> u64 {
self.0
}
}
impl WriteTo for PayloadLengthExtraBytes {
fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
let v: VarInt = self.0.into();
v.write_to(w)
}
}
impl ReadFrom for PayloadLengthExtraBytes {
fn read_from(r: &mut dyn Read) -> io::Result<Self>
where
Self: Sized,
{
let v = VarInt::read_from(r)?;
Ok(Self(v.as_u64()))
}
}
const MIN_TTL: u64 = 300;
fn effective_ttl(expires_time: Time, now: Time) -> u64 {
if expires_time < now {
MIN_TTL
} else {
let ttl = expires_time.as_secs() - now.as_secs();
u64::max(ttl, MIN_TTL)
}
}
fn effective_nonce_trials_per_byte(
config: &Config,
nonce_trials_per_byte: NonceTrialsPerByte,
) -> NonceTrialsPerByte {
NonceTrialsPerByte::max(nonce_trials_per_byte, config.nonce_trials_per_byte())
}
fn effective_payload_length_extra_bytes(
config: &Config,
payload_length_extra_bytes: PayloadLengthExtraBytes,
) -> PayloadLengthExtraBytes {
PayloadLengthExtraBytes::max(
payload_length_extra_bytes,
config.payload_length_extra_bytes(),
)
}
fn target_by_ttl(
payload_length: u64,
ttl: u64,
nonce_trials_per_byte: NonceTrialsPerByte,
payload_length_extra_bytes: PayloadLengthExtraBytes,
) -> u64 {
let length = payload_length + size_of::<u64>() as u64 + payload_length_extra_bytes.as_u64();
let denominator = nonce_trials_per_byte.as_u64() * (length + (ttl * length / 0x10000));
const NUMERATOR: u128 = 0x1_0000_0000_0000_0000;
(NUMERATOR / denominator as u128) as u64
}
fn initial_hash(payload: impl AsRef<[u8]>) -> Hash512 {
sha512(payload)
}
fn is_valid(initial_hash: Hash512, target: u64, nonce: Nonce) -> Result<(), ValidateError> {
let result_hash = double_sha512_2inputs(&nonce.as_u64().to_be_bytes(), initial_hash);
let mut trial_bytes = [0; 8];
trial_bytes.copy_from_slice(&result_hash[0..8]);
let trial_value = u64::from_be_bytes(trial_bytes);
if trial_value <= target {
Ok(())
} else {
Err(ValidateError::Insufficient {
target,
value: trial_value,
})
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ValidateError {
Insufficient { target: u64, value: u64 },
}
impl fmt::Display for ValidateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Insufficient { target, value } => write!(
f,
"value {:016x} is insufficient for target {:016x}",
value, target
),
}
}
}
impl std::error::Error for ValidateError {}
pub fn validate(
config: &Config,
payload: impl AsRef<[u8]>,
expires_time: Time,
nonce_trials_per_byte: NonceTrialsPerByte,
payload_length_extra_bytes: PayloadLengthExtraBytes,
nonce: Nonce,
now: Time,
) -> Result<(), ValidateError> {
let payload = payload.as_ref();
let target = target_by_ttl(
payload.len() as u64,
effective_ttl(expires_time, now),
effective_nonce_trials_per_byte(config, nonce_trials_per_byte),
effective_payload_length_extra_bytes(config, payload_length_extra_bytes),
);
is_valid(initial_hash(payload), target, nonce)
}