pub use equix;
pub use serde::{Deserialize, Serialize};
#[inline(always)]
pub fn hash(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<Hash, DrillxError> {
let digest = digest(challenge, nonce)?;
Ok(Hash {
d: digest,
h: hashv(&digest, nonce),
})
}
#[inline(always)]
pub fn hash_with_memory(
memory: &mut equix::SolverMemory,
challenge: &[u8; 32],
nonce: &[u8; 8],
) -> Result<Hash, DrillxError> {
let digest = digest_with_memory(memory, challenge, nonce)?;
Ok(Hash {
d: digest,
h: hashv(&digest, nonce),
})
}
#[inline(always)]
pub fn seed(challenge: &[u8; 32], nonce: &[u8; 8]) -> [u8; 40] {
let mut result = [0; 40];
result[00..32].copy_from_slice(challenge);
result[32..40].copy_from_slice(nonce);
result
}
#[inline(always)]
fn digest(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<[u8; 16], DrillxError> {
let seed = seed(challenge, nonce);
let solutions = equix::solve(&seed).map_err(|_| DrillxError::BadEquix)?;
if solutions.is_empty() {
return Err(DrillxError::NoSolutions);
}
let solution = unsafe { solutions.get_unchecked(0) };
Ok(solution.to_bytes())
}
#[inline(always)]
fn digest_with_memory(
memory: &mut equix::SolverMemory,
challenge: &[u8; 32],
nonce: &[u8; 8],
) -> Result<[u8; 16], DrillxError> {
let seed = seed(challenge, nonce);
let equix = equix::EquiXBuilder::new()
.runtime(equix::RuntimeOption::TryCompile)
.build(&seed)
.map_err(|_| DrillxError::BadEquix)?;
let solutions = equix.solve_with_memory(memory);
if solutions.is_empty() {
return Err(DrillxError::NoSolutions);
}
let solution = unsafe { solutions.get_unchecked(0) };
Ok(solution.to_bytes())
}
#[inline(always)]
fn sorted(mut digest: [u8; 16]) -> [u8; 16] {
unsafe {
let u16_slice: &mut [u16; 8] = core::mem::transmute(&mut digest);
u16_slice.sort_unstable();
digest
}
}
#[cfg(feature = "solana")]
#[inline(always)]
fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
solana_program::blake3::hashv(&[sorted(*digest).as_slice(), &nonce.as_slice()]).to_bytes()
}
#[cfg(not(feature = "solana"))]
#[inline(always)]
fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&sorted(*digest));
hasher.update(nonce);
hasher.finalize().into()
}
pub fn is_valid_digest(challenge: &[u8; 32], nonce: &[u8; 8], digest: &[u8; 16]) -> bool {
let seed = seed(challenge, nonce);
equix::verify_bytes(&seed, digest).is_ok()
}
pub fn difficulty(hash: [u8; 32]) -> u32 {
let mut count = 0;
for &byte in &hash {
let lz = byte.leading_zeros();
count += lz;
if lz < 8 {
break;
}
}
count
}
#[derive(Default)]
pub struct Hash {
pub d: [u8; 16], pub h: [u8; 32], }
impl Hash {
pub fn difficulty(&self) -> u32 {
difficulty(self.h)
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Solution {
pub d: [u8; 16], pub n: [u8; 8], }
impl Solution {
pub fn new(digest: [u8; 16], nonce: [u8; 8]) -> Solution {
Solution {
d: digest,
n: nonce,
}
}
pub fn is_valid(&self, challenge: &[u8; 32]) -> bool {
is_valid_digest(challenge, &self.n, &self.d)
}
pub fn to_hash(&self) -> Hash {
let mut d = self.d;
Hash {
d: self.d,
h: hashv(&mut d, &self.n),
}
}
}
#[derive(Debug)]
pub enum DrillxError {
BadEquix,
NoSolutions,
}
impl std::fmt::Display for DrillxError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
DrillxError::BadEquix => write!(f, "Failed equix"),
DrillxError::NoSolutions => write!(f, "No solutions"),
}
}
}
impl std::error::Error for DrillxError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}