1use sha2::digest::generic_array::GenericArray;
2
3use crate::{
4 Align16, Align64, decompose_blocks_mut,
5 message::{
6 BinaryMessage, CerberusMessage, DecimalMessage, DoubleBlockMessage, GoAwayMessage,
7 SingleBlockMessage,
8 },
9};
10
11pub struct SingleBlockSolver {
16 pub(super) message: SingleBlockMessage,
17
18 pub(super) attempted_nonces: u64,
19
20 pub(super) limit: u64,
21}
22
23impl From<SingleBlockMessage> for SingleBlockSolver {
24 fn from(message: SingleBlockMessage) -> Self {
25 Self {
26 message,
27 attempted_nonces: 0,
28 limit: u64::MAX,
29 }
30 }
31}
32
33impl SingleBlockSolver {
34 fn solve_impl<const TYPE: u8, const NO_TRAILING_ZEROS: bool>(
35 &mut self,
36 target: u64,
37 mask: u64,
38 ) -> Option<(u64, [u32; 8])> {
39 let mut message_be = Align64(sha2::digest::generic_array::GenericArray::default());
40 for i in 0..16 {
41 message_be.0[i * 4..i * 4 + 4].copy_from_slice(&self.message.message[i].to_be_bytes());
42 }
43 let target = target & mask;
44
45 for nonzero_digit in 1..=9 {
46 for key in 0..100_000_000 {
47 let mut key_copy = key;
48
49 if NO_TRAILING_ZEROS {
50 for i in (0..8).rev() {
51 message_be.0[self.message.digit_index + i] = (key_copy % 10) as u8 + b'0';
52 key_copy /= 10;
53 }
54 message_be.0[self.message.digit_index + 8] = b'0' + nonzero_digit as u8;
55 } else {
56 for i in (1..9).rev() {
57 message_be.0[self.message.digit_index + i] = (key_copy % 10) as u8 + b'0';
58 key_copy /= 10;
59 }
60 message_be.0[self.message.digit_index] = b'0' + nonzero_digit as u8;
61 }
62
63 let mut state = self.message.prefix_state;
64 sha2::compress256(&mut state, core::array::from_ref(&*message_be));
65 self.attempted_nonces += 1;
66
67 let pass = if TYPE == crate::solver::SOLVE_TYPE_GT {
68 (state[0] as u64) << 32 | (state[1] as u64) > target
69 } else if TYPE == crate::solver::SOLVE_TYPE_LT {
70 (state[0] as u64) << 32 | (state[1] as u64) < target
71 } else {
72 ((state[0] as u64) << 32 | (state[1] as u64)) & mask == target & mask
73 };
74
75 if pass {
76 let mut transformed_key = key;
77 if NO_TRAILING_ZEROS {
78 transformed_key *= 10;
79 transformed_key += nonzero_digit;
80 } else {
81 transformed_key += 100_000_000 * nonzero_digit;
82 }
83 return Some((transformed_key + self.message.nonce_addend, state));
84 }
85 }
86 }
87
88 None
89 }
90}
91
92impl crate::solver::Solver for SingleBlockSolver {
93 fn set_limit(&mut self, limit: u64) {
94 self.limit = limit;
95 }
96
97 fn get_attempted_nonces(&self) -> u64 {
98 self.attempted_nonces
99 }
100
101 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
102 if self.message.no_trailing_zeros {
103 self.solve_impl::<TYPE, true>(target, mask)
104 } else {
105 self.solve_impl::<TYPE, false>(target, mask)
106 }
107 }
108}
109
110pub struct DoubleBlockSolver {
115 pub(super) message: DoubleBlockMessage,
116 pub(super) attempted_nonces: u64,
117
118 pub(super) limit: u64,
119}
120
121impl From<DoubleBlockMessage> for DoubleBlockSolver {
122 fn from(message: DoubleBlockMessage) -> Self {
123 Self {
124 message,
125 attempted_nonces: 0,
126 limit: u64::MAX,
127 }
128 }
129}
130
131impl crate::solver::Solver for DoubleBlockSolver {
132 fn set_limit(&mut self, limit: u64) {
133 self.limit = limit;
134 }
135
136 fn get_attempted_nonces(&self) -> u64 {
137 self.attempted_nonces
138 }
139
140 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
141 if self.attempted_nonces >= self.limit {
142 return None;
143 }
144 let target = target & mask;
145
146 let mut buffer: sha2::digest::crypto_common::Block<sha2::Sha256> = Default::default();
147 for i in 0..16 {
148 buffer[i * 4..i * 4 + 4].copy_from_slice(&self.message.message[i].to_be_bytes());
149 }
150
151 let mut buffer2: sha2::digest::crypto_common::Block<sha2::Sha256> = Default::default();
152 buffer2[56..].copy_from_slice(&(self.message.message_length * 8).to_be_bytes());
153
154 let mut terminal_message_schedule = [0; 64];
155 terminal_message_schedule[14] = ((self.message.message_length * 8) >> 32) as u32;
156 terminal_message_schedule[15] = (self.message.message_length * 8) as u32;
157 crate::sha256::do_message_schedule_k_w(&mut terminal_message_schedule);
158
159 for key in (if self.message.nonce_addend == 0 {
160 100_000_000
161 } else {
162 0
163 })..1_000_000_000
164 {
165 let mut key_copy = key;
166
167 for j in (0..9).rev() {
168 let digit = key_copy % 10;
169 key_copy /= 10;
170 buffer[DoubleBlockMessage::DIGIT_IDX as usize + j] = digit as u8 + b'0'; }
172
173 let mut state = self.message.prefix_state;
174 sha2::compress256(&mut state, &[buffer]);
175
176 let save_a = state[0];
177 let save_b = state[1];
178
179 crate::sha256::sha2_arx_without_constants::<0, 64>(
180 &mut state,
181 terminal_message_schedule,
182 );
183
184 state[0] = state[0].wrapping_add(save_a);
185 state[1] = state[1].wrapping_add(save_b);
186
187 let ab = (state[0] as u64) << 32 | (state[1] as u64);
188
189 self.attempted_nonces += 1;
190
191 let cmp_fn = |x: &u64, y: &u64| {
192 if TYPE == crate::solver::SOLVE_TYPE_GT {
193 x > y
194 } else if TYPE == crate::solver::SOLVE_TYPE_LT {
195 x < y
196 } else {
197 x & mask == y & mask
198 }
199 };
200 if cmp_fn(&ab, &target) {
201 crate::unlikely();
202
203 let mut state = self.message.prefix_state;
204 sha2::compress256(&mut state, &[buffer, buffer2]);
205 return Some((key as u64 + self.message.nonce_addend, *state));
206 }
207
208 self.attempted_nonces += 1;
209
210 if self.attempted_nonces >= self.limit {
211 return None;
212 }
213 }
214
215 crate::unlikely();
216
217 None
218 }
219}
220
221#[macro_use]
222#[path = "impl_decimal_solver.rs"]
223mod impl_decimal_solver;
224
225impl_decimal_solver!(
226 [SingleBlockSolver, DoubleBlockSolver] => DecimalSolver
227);
228
229pub struct GoAwaySolver {
234 pub(super) message: GoAwayMessage,
235 pub(super) attempted_nonces: u64,
236 pub(super) limit: u64,
237}
238
239impl From<GoAwayMessage> for GoAwaySolver {
240 fn from(message: GoAwayMessage) -> Self {
241 Self {
242 message,
243 attempted_nonces: 0,
244 limit: u64::MAX,
245 }
246 }
247}
248
249impl GoAwaySolver {
250 const MSG_LEN: u32 = 10 * 4 * 8;
251}
252
253impl crate::solver::Solver for GoAwaySolver {
254 fn set_limit(&mut self, limit: u64) {
255 self.limit = limit;
256 }
257
258 fn get_attempted_nonces(&self) -> u64 {
259 self.attempted_nonces
260 }
261
262 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
263 let target = target & mask;
264
265 let mut buffer =
266 Align16([sha2::digest::crypto_common::Block::<sha2::Sha256>::default(); 16]);
267 for i in 0..8 {
268 buffer[0][i * 4..i * 4 + 4].copy_from_slice(&self.message.challenge[i].to_be_bytes());
269 }
270 buffer[0][32..36].copy_from_slice(&self.message.high_word.to_be_bytes());
271 buffer[0][40] = 0x80;
272 buffer[0][60..64].copy_from_slice(&(Self::MSG_LEN).to_be_bytes());
273
274 for key in 0u32.. {
275 buffer[0][36..40].copy_from_slice(&key.to_be_bytes());
276
277 let mut state = crate::sha256::IV;
278 sha2::compress256(&mut state, &*buffer);
279
280 state[0] = state[0].wrapping_add(crate::sha256::IV[0]);
281 state[1] = state[1].wrapping_add(crate::sha256::IV[1]);
282
283 let state_ab = (state[0] as u64) << 32 | (state[1] as u64);
284 self.attempted_nonces += 1;
285
286 let cmp_fn = |x: &u64, y: &u64| {
287 if TYPE == crate::solver::SOLVE_TYPE_GT {
288 x > y
289 } else if TYPE == crate::solver::SOLVE_TYPE_LT {
290 x < y
291 } else {
292 x & mask == y & mask
293 }
294 };
295 if cmp_fn(&state_ab, &target) {
296 crate::unlikely();
297
298 return Some(((self.message.high_word as u64) << 32 | key as u64, state));
299 }
300
301 if self.attempted_nonces >= self.limit {
302 return None;
303 }
304 }
305 crate::unlikely();
306
307 None
308 }
309}
310
311pub struct BinarySolver {
317 pub(super) message: BinaryMessage,
318 pub(super) attempted_nonces: u64,
319 pub(super) limit: u64,
320}
321
322impl From<BinaryMessage> for BinarySolver {
323 fn from(message: BinaryMessage) -> Self {
324 Self {
325 message,
326 attempted_nonces: 0,
327 limit: u64::MAX,
328 }
329 }
330}
331
332impl crate::solver::Solver for BinarySolver {
333 fn set_limit(&mut self, limit: u64) {
334 self.limit = limit;
335 }
336
337 fn get_attempted_nonces(&self) -> u64 {
338 self.attempted_nonces
339 }
340
341 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
342 let salt = &self.message.salt_residual[..self.message.salt_residual_len];
343 let mut blocks = [GenericArray::default(); 2];
344 blocks[0][..salt.len()].copy_from_slice(salt);
345 let mut ptr = salt.len();
346 let mut cur_block = 0;
347
348 for _ in 0..self.message.nonce_byte_count.get() {
349 blocks[cur_block][ptr] = 0;
350 ptr += 1;
351 if ptr == 64 {
352 cur_block = 1;
353 ptr = 0;
354 }
355 }
356 blocks[cur_block][ptr] = 0x80;
357 ptr += 1;
358 if ptr + 8 > 64 {
359 cur_block = 1;
360 }
361 blocks[cur_block][(64 - 8)..]
362 .copy_from_slice(&(self.message.message_length as u64 * 8).to_be_bytes());
363
364 let used_blocks = &mut blocks[..=cur_block];
365
366 for x in 0..(self
367 .limit
368 .min(256u64.saturating_pow(self.message.nonce_byte_count.get() as u32))
369 .max(1))
370 {
371 let mut state = self.message.prefix_state;
372 let nonce_bytes = &x.to_le_bytes()[..self.message.nonce_byte_count.get() as usize];
373 for i in 0..self.message.nonce_byte_count.get() as usize {
374 unsafe {
375 used_blocks
376 .as_mut_ptr()
377 .cast::<u8>()
378 .add(self.message.salt_residual_len + i)
379 .write(nonce_bytes[i]);
380 }
381 }
382
383 sha2::compress256(&mut state, &used_blocks);
384
385 let cmp_fn = |x: u64, y: u64| {
386 if TYPE == crate::solver::SOLVE_TYPE_GT {
387 x > y
388 } else if TYPE == crate::solver::SOLVE_TYPE_LT {
389 x < y
390 } else {
391 x & mask == y & mask
392 }
393 };
394 if cmp_fn((state[0] as u64) << 32 | (state[1] as u64), target) {
395 return Some((x, state.0));
396 }
397
398 self.attempted_nonces += 1;
399
400 if self.attempted_nonces >= self.limit {
401 return None;
402 }
403 }
404
405 None
406 }
407}
408
409pub struct CerberusSolver {
414 message: CerberusMessage,
415 attempted_nonces: u64,
416 limit: u64,
417}
418
419impl From<CerberusMessage> for CerberusSolver {
420 fn from(message: CerberusMessage) -> Self {
421 Self {
422 message,
423 attempted_nonces: 0,
424 limit: !0,
425 }
426 }
427}
428
429impl crate::solver::Solver for CerberusSolver {
430 fn set_limit(&mut self, limit: u64) {
431 self.limit = limit;
432 }
433
434 fn get_attempted_nonces(&self) -> u64 {
435 self.attempted_nonces
436 }
437
438 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
439 debug_assert_eq!(target, 0);
440
441 let remaining_limit = self.limit.saturating_sub(self.attempted_nonces);
442
443 match &self.message {
444 CerberusMessage::Decimal(message) => {
445 let mut msg = core::array::from_fn(|i| {
446 u32::from_le_bytes([
447 message.salt_residual[i * 4],
448 message.salt_residual[i * 4 + 1],
449 message.salt_residual[i * 4 + 2],
450 message.salt_residual[i * 4 + 3],
451 ])
452 });
453 assert!(
454 message.salt_residual_len + 8 < message.salt_residual.len(),
455 "there must be at least 9 bytes of headroom for the nonce"
456 );
457 for nonce in 0u64..remaining_limit {
458 let mut nonce_copy = nonce;
459 for i in (0..9).rev() {
460 let msg = decompose_blocks_mut(&mut msg);
461 #[cfg(target_endian = "little")]
462 unsafe {
463 *msg.get_unchecked_mut(message.salt_residual_len + i) =
464 (nonce_copy % 10) as u8 + b'0';
465 }
466 #[cfg(target_endian = "big")]
467 {
468 *msg.get_unchecked_mut(message.salt_residual_len + i) =
469 (nonce_copy % 10) as u8 + b'0';
470 }
471 nonce_copy /= 10;
472 }
473 debug_assert_eq!(nonce_copy, 0);
474
475 let hash = crate::blake3::compress8(
476 &message.prefix_state,
477 &msg,
478 0,
479 message.salt_residual_len as u32 + 9,
480 message.flags,
481 );
482 self.attempted_nonces += 1;
483 if ((hash[0] as u64) << 32 | (hash[1] as u64)) & mask == 0 {
484 crate::unlikely();
485
486 return Some(((nonce + message.nonce_addend) as u64, hash));
487 }
488 }
489 }
490 CerberusMessage::Binary(message) => {
491 let mut msg = [0; 16];
492 msg[0] = message.first_word;
493 for nonce in 0..(remaining_limit.min(u32::MAX as u64) as u32) {
494 msg[1] = nonce;
495
496 let hash = crate::blake3::compress8(
497 &message.midstate,
498 &msg,
499 0,
500 8,
501 crate::blake3::FLAG_CHUNK_END | crate::blake3::FLAG_ROOT,
502 );
503 self.attempted_nonces += 1;
504 if ((hash[0] as u64) << 32 | (hash[1] as u64)) & mask == 0 {
505 crate::unlikely();
506
507 return Some((msg[1] as u64 | ((msg[0] as u64) << 32), hash));
508 }
509 }
510 }
511 };
512
513 None
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use crate::message::{CerberusBinaryMessage, CerberusDecimalMessage};
520
521 use super::*;
522
523 #[test]
524 fn test_solve_decimal() {
525 crate::solver::tests::test_decimal_validator::<DecimalSolver, _>(|prefix, search_space| {
526 if let Some(solver) = SingleBlockMessage::new(prefix, search_space).map(Into::into) {
527 Some(DecimalSolver::SingleBlock(solver))
528 } else {
529 DoubleBlockMessage::new(prefix, search_space).map(Into::into)
530 }
531 });
532 }
533
534 #[test]
535 fn test_solve_cerberus_decimal() {
536 for i in 0..=1 {
537 crate::solver::tests::test_cerberus_decimal_validator::<CerberusSolver, _>(|prefix| {
538 Some(CerberusMessage::Decimal(CerberusDecimalMessage::new(prefix, i)?).into())
539 });
540 }
541 }
542 #[test]
543 fn test_solve_cerberus_binary() {
544 for i in 0..=1 {
545 crate::solver::tests::test_cerberus_binary_validator::<CerberusSolver, _>(|prefix| {
546 Some(CerberusMessage::Binary(CerberusBinaryMessage::new(prefix, i)).into())
547 });
548 }
549 }
550
551 #[test]
552 fn test_solve_decimal_f64() {
553 crate::solver::tests::test_decimal_validator_f64_safe::<DecimalSolver, _>(
554 |prefix, search_space| {
555 if let Some((solver, p)) =
556 SingleBlockMessage::new_f64(prefix, search_space).map(|(x, p)| (x.into(), p))
557 {
558 Some((DecimalSolver::SingleBlock(solver), p))
559 } else {
560 DoubleBlockMessage::new(prefix, search_space)
561 .map(|x| (DecimalSolver::DoubleBlock(x.into()), None))
562 }
563 },
564 );
565 }
566
567 #[test]
568 fn test_solve_binary() {
569 crate::solver::tests::test_binary_validator::<BinarySolver, _>(
570 |prefix, nonce_byte_count| {
571 BinarySolver::from(BinaryMessage::new(prefix, nonce_byte_count))
572 },
573 )
574 }
575
576 #[test]
577 fn test_solve_goaway() {
578 crate::solver::tests::test_goaway_validator::<GoAwaySolver, _>(|prefix| {
579 GoAwaySolver::from(GoAwayMessage::new(
580 core::array::from_fn(|i| {
581 u32::from_be_bytes([
582 prefix[i * 4],
583 prefix[i * 4 + 1],
584 prefix[i * 4 + 2],
585 prefix[i * 4 + 3],
586 ])
587 }),
588 0,
589 ))
590 });
591 }
592}