1pub use equix;
2use equix::SolutionArray;
3pub use serde::{Deserialize, Serialize};
4#[cfg(not(feature = "solana"))]
5use sha3::Digest;
6
7#[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#[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#[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#[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#[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 let solution = unsafe { solutions.get_unchecked(0) };
70 Ok(solution.to_bytes())
71}
72
73#[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 let solution = unsafe { solutions.get_unchecked(0) };
91 Ok(solution.to_bytes())
92}
93
94#[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#[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#[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#[cfg(not(feature = "solana"))]
131#[inline(always)]
132fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
133 let mut hasher = sha3::Keccak256::new();
135 hasher.update(&sorted(*digest));
136 hasher.update(nonce);
137 hasher.finalize().into()
138}
139
140pub 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
146pub 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#[derive(Default)]
161pub struct Hash {
162 pub d: [u8; 16], pub h: [u8; 32], }
165
166impl Hash {
167 pub fn difficulty(&self) -> u32 {
169 difficulty(self.h)
170 }
171}
172
173#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
175pub struct Solution {
176 pub d: [u8; 16], pub n: [u8; 8], }
179
180impl Solution {
181 pub fn new(digest: [u8; 16], nonce: [u8; 8]) -> Solution {
183 Solution {
184 d: digest,
185 n: nonce,
186 }
187 }
188
189 pub fn is_valid(&self, challenge: &[u8; 32]) -> bool {
191 is_valid_digest(challenge, &self.n, &self.d)
192 }
193
194 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}