drillx_mod/
lib.rs

1pub use equix;
2#[cfg(not(feature = "solana"))]
3use sha3::Digest;
4
5/// Generates a new Drillx hash from a challenge and nonce.
6#[inline(always)]
7pub fn hash(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<Hash, DrillxError> {
8    let digest = digest(challenge, nonce)?;
9    Ok(Hash {
10        d: digest,
11        h: hashv(&digest, nonce),
12    })
13}
14
15/// Generates a new Drillx hash using pre-allocated memory.
16#[inline(always)]
17pub fn hash_with_memory(
18    memory: &mut equix::SolverMemory,
19    challenge: &[u8; 32],
20    nonce: &[u8; 8],
21) -> Result<Hash, DrillxError> {
22    let digest = digest_with_memory(memory, challenge, nonce)?;
23    Ok(Hash {
24        d: digest,
25        h: hashv(&digest, nonce),
26    })
27}
28
29/// Concatenates a challenge and nonce into a single buffer.
30#[inline(always)]
31pub fn seed(challenge: &[u8; 32], nonce: &[u8; 8]) -> [u8; 40] {
32    let mut result = [0; 40];
33    result[..32].copy_from_slice(challenge);
34    result[32..].copy_from_slice(nonce);
35    result
36}
37
38/// Constructs a Keccak digest from a challenge and nonce.
39#[inline(always)]
40fn digest(challenge: &[u8; 32], nonce: &[u8; 8]) -> Result<[u8; 16], DrillxError> {
41    let seed = seed(challenge, nonce);
42    let solutions = equix::solve(&seed).map_err(|_| DrillxError::BadEquix)?;
43    if solutions.is_empty() {
44        return Err(DrillxError::NoSolutions);
45    }
46    // SAFETY: The equix solver guarantees that the first solution is always valid
47    let solution = unsafe { solutions.get_unchecked(0) };
48    Ok(solution.to_bytes())
49}
50
51/// Constructs a Keccak digest using pre-allocated memory.
52#[inline(always)]
53fn digest_with_memory(
54    memory: &mut equix::SolverMemory,
55    challenge: &[u8; 32],
56    nonce: &[u8; 8],
57) -> Result<[u8; 16], DrillxError> {
58    let seed = seed(challenge, nonce);
59    let equix = equix::EquiXBuilder::new()
60        .runtime(equix::RuntimeOption::TryCompile)
61        .build(&seed)
62        .map_err(|_| DrillxError::BadEquix)?;
63    let solutions = equix.solve_with_memory(memory);
64    if solutions.is_empty() {
65        return Err(DrillxError::NoSolutions);
66    }
67    // SAFETY: The equix solver guarantees that the first solution is always valid
68    let solution = unsafe { solutions.get_unchecked(0) };
69    Ok(solution.to_bytes())
70}
71
72/// Sorts a digest as a list of `u16` values.
73#[inline(always)]
74fn sorted(digest: [u8; 16]) -> [u8; 16] {
75    let mut sorted_digest = digest;
76    unsafe {
77        let u16_slice: &mut [u16; 8] = core::mem::transmute(&mut sorted_digest);
78        u16_slice.sort_unstable();
79    }
80    sorted_digest
81}
82
83/// Returns a Keccak hash of the digest and nonce.
84#[cfg(feature = "solana")]
85#[inline(always)]
86fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
87    solana_program::keccak::hashv(&[sorted(*digest).as_slice(), nonce.as_slice()]).to_bytes()
88}
89
90/// Calculates a hash using SHA3-256.
91#[cfg(not(feature = "solana"))]
92#[inline(always)]
93fn hashv(digest: &[u8; 16], nonce: &[u8; 8]) -> [u8; 32] {
94    let mut hasher = sha3::Keccak256::new();
95    hasher.update(&sorted(*digest));
96    hasher.update(nonce);
97    hasher.finalize().into()
98}
99
100/// Checks if the digest is a valid Equihash construction.
101pub fn is_valid_digest(challenge: &[u8; 32], nonce: &[u8; 8], digest: &[u8; 16]) -> bool {
102    let seed = seed(challenge, nonce);
103    equix::verify_bytes(&seed, digest).is_ok()
104}
105
106/// Returns the number of leading zeros in a 32-byte buffer.
107pub fn difficulty(hash: [u8; 32]) -> u32 {
108    let mut count = 0;
109    for &byte in &hash {
110        let lz = byte.leading_zeros();
111        count += lz;
112        if lz < 8 {
113            break;
114        }
115    }
116    count
117}
118
119/// The result of a Drillx hash.
120#[derive(Default)]
121pub struct Hash {
122    pub d: [u8; 16], // digest
123    pub h: [u8; 32], // hash
124}
125
126impl Hash {
127    /// The leading number of zeros in the hash.
128    pub fn difficulty(&self) -> u32 {
129        difficulty(self.h)
130    }
131}
132
133/// A Drillx solution that can be efficiently validated on-chain.
134#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
135pub struct Solution {
136    pub d: [u8; 16], // digest
137    pub n: [u8; 8],  // nonce
138}
139
140impl Solution {
141    /// Builds a new verifiable solution from a digest and nonce.
142    pub fn new(digest: [u8; 16], nonce: [u8; 8]) -> Solution {
143        Solution { d: digest, n: nonce }
144    }
145
146    /// Checks if the solution is valid.
147    pub fn is_valid(&self, challenge: &[u8; 32]) -> bool {
148        is_valid_digest(challenge, &self.n, &self.d)
149    }
150
151    /// Calculates the result hash for the solution.
152    pub fn to_hash(&self) -> Hash {
153        Hash {
154            d: self.d,
155            h: hashv(&self.d, &self.n),
156        }
157    }
158}
159
160#[derive(Debug)]
161pub enum DrillxError {
162    BadEquix,
163    NoSolutions,
164}
165
166impl std::fmt::Display for DrillxError {
167    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
168        match *self {
169            DrillxError::BadEquix => write!(f, "Failed Equix"),
170            DrillxError::NoSolutions => write!(f, "No solutions"),
171        }
172    }
173}
174
175impl std::error::Error for DrillxError {
176    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
177        None
178    }
179}