drillx/
lib.rs

1pub use equix;
2use equix::SolutionArray;
3pub use serde::{Deserialize, Serialize};
4#[cfg(not(feature = "solana"))]
5use sha3::Digest;
6
7/// Generates a new drillx hash from a challenge and nonce.
8#[inline(always)]
9pub fn hash(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<Hash, DrillxError> {
10    let digest = digest(challenge, nonce)?;
11    Ok(Hash {
12        d: digest,
13        h: hashv(&digest, nonce),
14    })
15}
16
17/// Generates a new drillx hash from a challenge and nonce using pre-allocated memory.
18#[inline(always)]
19pub fn hash_with_memory(
20    memory: &mut equix::SolverMemory,
21    challenge: &[u8; 32],
22    nonce: &[u8; 8],
23) -> Result<Hash, DrillxError> {
24    let digest = digest_with_memory(memory, challenge, nonce)?;
25    Ok(Hash {
26        d: digest,
27        h: hashv(&digest, nonce),
28    })
29}
30
31/// Generates drillx hashes from a challenge and nonce using pre-allocated memory.
32#[inline(always)]
33pub fn hashes_with_memory(
34    memory: &mut equix::SolverMemory,
35    challenge: &[u8; 32],
36    nonce: &[u8; 8],
37) -> Vec<Hash> {
38    let mut hashes: Vec<Hash> = Vec::with_capacity(7);
39    if let Ok(solutions) = digests_with_memory(memory, challenge, nonce) {
40        for solution in solutions {
41            let digest = solution.to_bytes();
42            hashes.push(Hash {
43                d: digest,
44                h: hashv(&digest, nonce),
45            });
46        }
47    }
48    hashes
49}
50
51/// Concatenates a challenge and a nonce into a single buffer.
52#[inline(always)]
53pub fn seed(challenge: &[u8; 32], nonce: &[u8; 8]) -> [u8; 40] {
54    let mut result = [0; 40];
55    result[00..32].copy_from_slice(challenge);
56    result[32..40].copy_from_slice(nonce);
57    result
58}
59
60/// Constructs a keccak digest from a challenge and nonce using equix hashes.
61#[inline(always)]
62fn digest(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<[u8; 16], DrillxError> {
63    let seed = seed(challenge, nonce);
64    let solutions = equix::solve(&seed).map_err(|_| DrillxError::BadEquix)?;
65    if solutions.is_empty() {
66        return Err(DrillxError::NoSolutions);
67    }
68    // SAFETY: The equix solver guarantees that the first solution is always valid
69    let solution = unsafe { solutions.get_unchecked(0) };
70    Ok(solution.to_bytes())
71}
72
73/// Constructs a keccak digest from a challenge and nonce using equix hashes and pre-allocated memory.
74#[inline(always)]
75fn digest_with_memory(
76    memory: &mut equix::SolverMemory,
77    challenge: &[u8; 32],
78    nonce: &[u8; 8],
79) -> Result<[u8; 16], DrillxError> {
80    let seed = seed(challenge, nonce);
81    let equix = equix::EquiXBuilder::new()
82        .runtime(equix::RuntimeOption::TryCompile)
83        .build(&seed)
84        .map_err(|_| DrillxError::BadEquix)?;
85    let solutions = equix.solve_with_memory(memory);
86    if solutions.is_empty() {
87        return Err(DrillxError::NoSolutions);
88    }
89    // SAFETY: The equix solver guarantees that the first solution is always valid
90    let solution = unsafe { solutions.get_unchecked(0) };
91    Ok(solution.to_bytes())
92}
93
94/// Constructs a keccak digest from a challenge and nonce using equix hashes and pre-allocated memory.
95#[inline(always)]
96fn digests_with_memory(
97    memory: &mut equix::SolverMemory,
98    challenge: &[u8; 32],
99    nonce: &[u8; 8],
100) -> Result<SolutionArray, DrillxError> {
101    let seed = seed(challenge, nonce);
102    let equix = equix::EquiXBuilder::new()
103        .runtime(equix::RuntimeOption::TryCompile)
104        .build(&seed)
105        .map_err(|_| DrillxError::BadEquix)?;
106    Ok(equix.solve_with_memory(memory))
107}
108
109/// Sorts the provided digest as a list of u16 values.
110#[inline(always)]
111fn sorted(mut digest: [u8; 16]) -> [u8; 16] {
112    unsafe {
113        let u16_slice: &mut [u16; 8] = core::mem::transmute(&mut digest);
114        u16_slice.sort_unstable();
115        digest
116    }
117}
118
119/// Returns a keccak hash of the provided digest and nonce.
120/// The digest is sorted prior to hashing to prevent malleability.
121/// Delegates the hash to a syscall if compiled for the solana runtime.
122#[cfg(feature = "solana")]
123#[inline(always)]
124fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
125    solana_program::keccak::hashv(&[sorted(*digest).as_slice(), &nonce.as_slice()]).to_bytes()
126}
127
128/// Calculates a hash from the provided digest and nonce.
129/// The digest is sorted prior to hashing to prevent malleability.
130#[cfg(not(feature = "solana"))]
131#[inline(always)]
132fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
133    // let mut hasher = blake3::Hasher::new();
134    let mut hasher = sha3::Keccak256::new();
135    hasher.update(&sorted(*digest));
136    hasher.update(nonce);
137    hasher.finalize().into()
138}
139
140/// Returns true if the digest if valid equihash construction from the challenge and nonce.
141pub fn is_valid_digest(challenge: &[u8; 32], nonce: &[u8; 8], digest: &[u8; 16]) -> bool {
142    let seed = seed(challenge, nonce);
143    equix::verify_bytes(&seed, digest).is_ok()
144}
145
146/// Returns the number of leading zeros on a 32 byte buffer.
147pub fn difficulty(hash: [u8; 32]) -> u32 {
148    let mut count = 0;
149    for &byte in &hash {
150        let lz = byte.leading_zeros();
151        count += lz;
152        if lz < 8 {
153            break;
154        }
155    }
156    count
157}
158
159/// The result of a drillx hash
160#[derive(Default)]
161pub struct Hash {
162    pub d: [u8; 16], // digest
163    pub h: [u8; 32], // hash
164}
165
166impl Hash {
167    /// The leading number of zeros on the hash
168    pub fn difficulty(&self) -> u32 {
169        difficulty(self.h)
170    }
171}
172
173/// A drillx solution which can be efficiently validated on-chain
174#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
175pub struct Solution {
176    pub d: [u8; 16], // digest
177    pub n: [u8; 8],  // nonce
178}
179
180impl Solution {
181    /// Builds a new verifiable solution from a hash and nonce
182    pub fn new(digest: [u8; 16], nonce: [u8; 8]) -> Solution {
183        Solution {
184            d: digest,
185            n: nonce,
186        }
187    }
188
189    /// Returns true if the solution is valid
190    pub fn is_valid(&self, challenge: &[u8; 32]) -> bool {
191        is_valid_digest(challenge, &self.n, &self.d)
192    }
193
194    /// Calculates the result hash for a given solution
195    pub fn to_hash(&self) -> Hash {
196        let mut d = self.d;
197        Hash {
198            d: self.d,
199            h: hashv(&mut d, &self.n),
200        }
201    }
202
203    pub fn from_bytes(bytes: [u8; 24]) -> Self {
204        let mut d = [0u8; 16];
205        let mut n = [0u8; 8];
206        d.copy_from_slice(&bytes[..16]);
207        n.copy_from_slice(&bytes[16..]);
208        Solution { d, n }
209    }
210
211    pub fn to_bytes(&self) -> [u8; 24] {
212        let mut bytes = [0; 24];
213        bytes[..16].copy_from_slice(&self.d);
214        bytes[16..].copy_from_slice(&self.n);
215        bytes
216    }
217}
218
219#[derive(Debug)]
220pub enum DrillxError {
221    BadEquix,
222    NoSolutions,
223}
224
225impl std::fmt::Display for DrillxError {
226    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
227        match *self {
228            DrillxError::BadEquix => write!(f, "Failed equix"),
229            DrillxError::NoSolutions => write!(f, "No solutions"),
230        }
231    }
232}
233
234impl std::error::Error for DrillxError {
235    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
236        None
237    }
238}