1use bytemuck::{Pod, Zeroable};
2
3pub const SOLUTION_SIZE: usize = 145; #[repr(C)]
6#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
7pub struct Solution {
8 pub bump: u8, pub seeds: [u8; 16], pub nonces: [u8; 128], }
12
13impl Solution {
14 pub fn new(bump: u8, seeds: [u8; 16], nonces: [u8; 128]) -> Self {
15 Solution {
16 bump,
17 seeds,
18 nonces,
19 }
20 }
21
22 #[inline]
24 pub fn difficulty(&self) -> u32 {
25 let bytes = serialize(self);
26 let h = compute_hash(&[&bytes]);
27 get_difficulty(h)
28 }
29
30 pub fn to_bytes(&self) -> [u8; SOLUTION_SIZE] {
32 serialize(self)
33 }
34
35 pub fn from_bytes(data: &[u8; SOLUTION_SIZE]) -> Self {
37 deserialize(data)
38 }
39
40 pub fn unpack(&self, pubkey: &[u8; 32]) -> [u8; 128] {
42 unpack(pubkey, self)
43 }
44}
45
46#[repr(C)]
48pub struct SeedTable {
49 pub nonces: Box<[[u8; 256]]>, pub present: Box<[[u8; 32]]>, }
54
55pub struct SolverMemory {
57 pub tables: Box<[Box<SeedTable>]>,
58}
59
60#[inline(always)]
61fn bit_test(bits: &[u8; 32], target: u8) -> bool {
62 let idx = (target >> 3) as usize;
63 let mask = 1u8 << (target & 7);
64 (bits[idx] & mask) != 0
65}
66
67#[inline(always)]
68fn bit_set(bits: &mut [u8; 32], target: u8) {
69 let idx = (target >> 3) as usize;
70 let mask = 1u8 << (target & 7);
71 bits[idx] |= mask;
72}
73
74#[inline(always)]
75fn h0(pubkey: &[u8; 32], bump: u8, seed: u8, nonce: u8) -> u8 {
76 let bump_b = [bump];
77 let seed_b = [seed];
78 let nonce_b = [nonce];
79 compute_hash(&[pubkey, &bump_b, &seed_b, &nonce_b])[0]
80}
81
82#[inline(always)]
83fn compute_hash(inputs: &[&[u8]]) -> [u8; 32] {
84 #[cfg(feature = "solana")]
85 {
86 solana_program::blake3::hashv(inputs).to_bytes()
87 }
88 #[cfg(not(feature = "solana"))]
89 {
90 let mut hasher = blake3::Hasher::new();
91 for input in inputs {
92 hasher.update(input);
93 }
94 hasher.finalize().into()
95 }
96}
97
98#[inline]
99fn get_difficulty(hash: [u8; 32]) -> u32 {
100 let mut count = 0u32;
101 for &b in &hash {
102 let lz = b.leading_zeros();
103 count += lz;
104 if lz < 8 {
105 break;
106 }
107 }
108 count
109}
110
111#[inline]
112pub fn serialize(solution: &Solution) -> [u8; SOLUTION_SIZE] {
113 let mut out = [0u8; SOLUTION_SIZE];
114 out.copy_from_slice(bytemuck::bytes_of(solution));
115 out
116}
117
118#[inline]
119pub fn deserialize(bytes_in: &[u8; SOLUTION_SIZE]) -> Solution {
120 let mut s = Solution {
121 bump: 0,
122 seeds: [0; 16],
123 nonces: [0; 128],
124 };
125 bytemuck::bytes_of_mut(&mut s).copy_from_slice(bytes_in);
126 s
127}
128
129pub fn build_one_bump(pubkey: &[u8; 32], bump: u8) -> Box<SeedTable> {
131 let mut table = Box::new(SeedTable {
133 nonces: vec![[0u8; 256]; 256].into_boxed_slice(),
134 present: vec![[0u8; 32]; 256].into_boxed_slice(),
135 });
136
137 for seed in 0u8..=u8::MAX {
138 let present_row: &mut [u8; 32] = &mut table.present[seed as usize];
139 let nonces_row: &mut [u8; 256] = &mut table.nonces[seed as usize];
140
141 for nonce in 0u8..=u8::MAX {
142 let t = h0(pubkey, bump, seed, nonce);
143 if !bit_test(present_row, t) {
144 bit_set(present_row, t);
145 nonces_row[t as usize] = nonce;
146 }
147 }
148 }
149
150 table
151}
152
153pub fn build_memory(pubkey: &[u8; 32]) -> SolverMemory {
155 let mut vec_tables: Vec<Box<SeedTable>> = Vec::with_capacity(256);
156 for bump in 0u8..=u8::MAX {
157 vec_tables.push(build_one_bump(pubkey, bump));
158 }
159 SolverMemory {
160 tables: vec_tables.into_boxed_slice(),
161 }
162}
163
164#[derive(Clone, Copy)]
166struct SeedCandidate {
167 seed: u8,
168 nonces8: [u8; 8],
169}
170
171fn build_group_candidates(data: &[u8; 128], g: usize, table: &SeedTable) -> Vec<SeedCandidate> {
173 let cs = g * 8;
174 let need = [
175 data[cs + 0], data[cs + 1], data[cs + 2], data[cs + 3],
176 data[cs + 4], data[cs + 5], data[cs + 6], data[cs + 7],
177 ];
178
179 let mut out = Vec::with_capacity(8);
180
181 for seed in 0u8..=u8::MAX {
182 let present = &table.present[seed as usize];
183 if !(bit_test(present, need[0]) &&
184 bit_test(present, need[1]) &&
185 bit_test(present, need[2]) &&
186 bit_test(present, need[3]) &&
187 bit_test(present, need[4]) &&
188 bit_test(present, need[5]) &&
189 bit_test(present, need[6]) &&
190 bit_test(present, need[7])) {
191 continue;
192 }
193
194 let row = &table.nonces[seed as usize];
195 out.push(SeedCandidate {
196 seed,
197 nonces8: [
198 row[need[0] as usize],
199 row[need[1] as usize],
200 row[need[2] as usize],
201 row[need[3] as usize],
202 row[need[4] as usize],
203 row[need[5] as usize],
204 row[need[6] as usize],
205 row[need[7] as usize],
206 ],
207 });
208 }
209
210 out
211}
212
213struct MixedRadix {
215 radices: [usize; 16],
216 idx: [usize; 16],
217 first: bool,
218 done: bool,
219}
220
221impl MixedRadix {
222 fn new(radices: [usize; 16]) -> Option<Self> {
223 if radices.iter().any(|&r| r == 0) {
224 return None;
225 }
226 Some(Self {
227 radices,
228 idx: [0; 16],
229 first: true,
230 done: false,
231 })
232 }
233}
234
235impl Iterator for MixedRadix {
236 type Item = [usize; 16];
237 fn next(&mut self) -> Option<Self::Item> {
238 if self.done {
239 return None;
240 }
241 if self.first {
242 self.first = false;
243 return Some(self.idx);
244 }
245 for pos in 0..16 {
246 self.idx[pos] += 1;
247 if self.idx[pos] < self.radices[pos] {
248 return Some(self.idx);
249 } else {
250 self.idx[pos] = 0;
251 }
252 }
253 self.done = true;
254 None
255 }
256}
257
258pub fn solve_one_bump(
260 data: &[u8; 128],
261 bump: u8,
262 table: &SeedTable,
263 difficulty: u32,
264) -> Option<Solution> {
265 let mut cands: [Vec<SeedCandidate>; 16] = core::array::from_fn(|_| Vec::new());
266 for g in 0..16 {
267 cands[g] = build_group_candidates(data, g, table);
268 if cands[g].is_empty() {
269 return None;
270 }
271 }
272
273 let mut order: [usize; 16] = core::array::from_fn(|i| i);
274 order.sort_by_key(|&g| cands[g].len());
275
276 let radices_ordered: [usize; 16] = core::array::from_fn(|i| cands[order[i]].len());
277 let Some(iter) = MixedRadix::new(radices_ordered) else { return None; };
278
279 for idxs_ordered in iter {
280 let mut seeds_out = [0u8; 16];
281 let mut nonces_out = [0u8; 128];
282
283 for (pos, &g) in order.iter().enumerate() {
284 let choice = cands[g][idxs_ordered[pos]];
285 seeds_out[g] = choice.seed;
286 let cs = g * 8;
287 nonces_out[cs..cs + 8].copy_from_slice(&choice.nonces8);
288 }
289
290 let solution = Solution { bump, seeds: seeds_out, nonces: nonces_out };
291 if solution.difficulty() >= difficulty {
292 return Some(solution);
293 }
294 }
295
296 None
297}
298
299pub fn solve_with_memory(
301 data: &[u8; 128],
302 mem: &SolverMemory,
303 difficulty: u32,
304) -> Option<Solution> {
305 for bump in 0u8..=u8::MAX {
306 let table: &SeedTable = &mem.tables[bump as usize];
307 if let Some(solution) = solve_one_bump(data, bump, table, difficulty) {
308 return Some(solution);
309 }
310 }
311 None
312}
313
314pub fn solve(
316 pubkey: &[u8; 32],
317 data: &[u8; 128],
318 difficulty: u32,
319) -> Option<Solution> {
320 let mem = build_memory(pubkey);
321 solve_with_memory(data, &mem, difficulty)
322}
323
324pub fn unpack(pubkey: &[u8; 32], solution: &Solution) -> [u8; 128] {
326 let mut data = [0u8; 128];
327 for g in 0..16 {
328 let seed = solution.seeds[g];
329 let cs = g * 8;
330 for i in 0..8 {
331 let nonce = solution.nonces[cs + i];
332 data[cs + i] = h0(pubkey, solution.bump, seed, nonce);
333 }
334 }
335 data
336}
337
338pub fn verify(pubkey: &[u8; 32], data: &[u8; 128], solution: &Solution, difficulty: u32) -> bool {
340 if unpack(pubkey, solution) != *data {
341 return false;
342 }
343 solution.difficulty() >= difficulty
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use rand::RngCore;
350
351 const TEST_DIFFICULTY: u32 = 1;
352 const TEST_BUMP_TRIES: u8 = 7;
353
354 fn solve_lightweight(pubkey: &[u8; 32], data: &[u8; 128], difficulty: u32) -> Option<Solution> {
355 for bump in 0u8..=TEST_BUMP_TRIES {
356 let table = build_one_bump(pubkey, bump);
357 if let Some(solution) = solve_one_bump(data, bump, &table, difficulty) {
358 return Some(solution);
359 }
360 }
361 None
362 }
363
364 #[test]
365 fn test_solve_and_verify() {
366 let mut rng = rand::thread_rng();
367 let mut pubkey = [0u8; 32];
368 let mut data = [0u8; 128];
369 rng.fill_bytes(&mut pubkey);
370 rng.fill_bytes(&mut data);
371
372 let solution = solve_lightweight(&pubkey, &data, TEST_DIFFICULTY).expect("solve failed");
373 assert!(verify(&pubkey, &data, &solution, TEST_DIFFICULTY));
374 }
375
376 #[test]
377 fn test_serialize_deserialize_roundtrip() {
378 let solution = Solution { bump: 3, seeds: [9; 16], nonces: [7; 128] };
379 let ser = serialize(&solution);
380 let de = deserialize(&ser);
381 assert_eq!(solution.bump, de.bump);
382 assert_eq!(solution.seeds, de.seeds);
383 assert_eq!(solution.nonces, de.nonces);
384 }
385
386 #[test]
387 fn test_verify_failure_wrong_data() {
388 let mut rng = rand::thread_rng();
389 let mut pubkey = [0u8; 32];
390 let mut data = [0u8; 128];
391 rng.fill_bytes(&mut pubkey);
392 rng.fill_bytes(&mut data);
393
394 let solution = solve_lightweight(&pubkey, &data, TEST_DIFFICULTY).expect("solve failed");
395 let mut wrong = data;
396 wrong[0] = wrong[0].wrapping_add(1);
397 assert!(!verify(&pubkey, &wrong, &solution, TEST_DIFFICULTY));
398 }
399
400 #[test]
401 fn test_verify_failure_wrong_pubkey() {
402 let mut rng = rand::thread_rng();
403 let mut pubkey = [0u8; 32];
404 let mut data = [0u8; 128];
405 rng.fill_bytes(&mut pubkey);
406 rng.fill_bytes(&mut data);
407
408 let solution = solve_lightweight(&pubkey, &data, TEST_DIFFICULTY).expect("solve failed");
409 let mut other = pubkey;
410 other[0] ^= 1;
411 assert!(!verify(&other, &data, &solution, TEST_DIFFICULTY));
412 }
413
414 #[test]
415 fn test_unpack_roundtrip() {
416 let mut rng = rand::thread_rng();
417 let mut pubkey = [0u8; 32];
418 let mut data = [0u8; 128];
419 rng.fill_bytes(&mut pubkey);
420 rng.fill_bytes(&mut data);
421
422 let solution = solve_lightweight(&pubkey, &data, TEST_DIFFICULTY).expect("solve failed");
423 assert_eq!(unpack(&pubkey, &solution), data);
424 }
425}