Skip to main content

pow_buster/solver/
safe.rs

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
11/// Safe decimal nonce single block solver.
12///
13///
14/// Current implementation: generic sha2 crate fallback.
15pub 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
110/// Safe decimal nonce double block solver.
111///
112///
113/// Current implementation: generic sha2 crate fallback.
114pub 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'; // TODO: fix this
171            }
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
229/// SHA-NI GoAway solver.
230///
231///
232/// Current implementation: generic sha2 crate fallback.
233pub 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
311/// Safe binary nonce solver.
312///
313/// Output: nonce in little endian order
314///
315/// Current implementation: generic sha2 crate fallback.
316pub 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
409/// Safe Cerberus solver.
410///
411///
412/// Current implementation: scalar fallback.
413pub 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}