fud/
equix.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19//! Proof-of-Work using Equi-X
20//!
21//! <https://spec.torproject.org/hspow-spec/v1-equix.html>
22//! <https://github.com/tevador/equix/blob/master/devlog.md>
23//! <https://github.com/tevador/equix>
24
25use std::convert::AsRef;
26
27use blake2::{digest::consts::U4, Blake2b, Digest};
28pub use equix::{EquiXBuilder, HashError, RuntimeOption, Solution, SolverMemory};
29
30use darkfi::{Error, Result};
31
32/// Algorithm personalization string
33const P_STRING: &[u8] = b"DarkFi Equi-X\0";
34
35/// Length of the personalization string, in bytes
36const P_STRING_LEN: usize = 14;
37
38/// Length of the nonce value generated by clients and included in the solution
39pub const NONCE_LEN: usize = 16;
40
41/// A challenge string
42#[derive(Debug, Clone, Eq, PartialEq)]
43pub struct Challenge(Vec<u8>);
44
45impl Challenge {
46    /// Build a new [`Challenge`].
47    ///
48    /// Copies `input` and `nonce` values into
49    /// a new byte vector.
50    pub fn new(input: &[u8], nonce: &[u8; NONCE_LEN]) -> Self {
51        let mut result = Vec::<u8>::new();
52        result.extend_from_slice(P_STRING);
53        result.extend_from_slice(input.as_ref());
54        result.extend_from_slice(nonce.as_ref());
55
56        Self(result)
57    }
58
59    pub fn to_bytes(&self) -> Vec<u8> {
60        self.0.clone()
61    }
62
63    /// Clone the input portion of this challenge.
64    pub fn input(&self) -> Vec<u8> {
65        self.0[P_STRING_LEN..(self.0.len() - NONCE_LEN)].into()
66    }
67
68    /// Clone the nonce portion of this challenge.
69    pub fn nonce(&self) -> [u8; NONCE_LEN] {
70        self.0[(self.0.len() - NONCE_LEN)..].try_into().expect("slice length correct")
71    }
72
73    /// Increment the nonce value inside this challenge.
74    pub fn increment_nonce(&mut self) {
75        fn inc_le_bytes(slice: &mut [u8]) {
76            for byte in slice {
77                let (value, overflow) = (*byte).overflowing_add(1);
78                *byte = value;
79                if !overflow {
80                    break;
81                }
82            }
83        }
84        let len = self.0.len();
85        inc_le_bytes(&mut self.0[(len - NONCE_LEN)..]);
86    }
87
88    /// Verify that a solution proof passes the effort test.
89    pub fn check_effort(&self, proof: &equix::SolutionByteArray, effort: u32) -> bool {
90        let mut hasher = Blake2b::<U4>::new();
91        hasher.update(self.as_ref());
92        hasher.update(proof.as_ref());
93        let value = u32::from_be_bytes(hasher.finalize().into());
94        value.checked_mul(effort).is_some()
95    }
96}
97
98impl AsRef<[u8]> for Challenge {
99    fn as_ref(&self) -> &[u8] {
100        self.0.as_ref()
101    }
102}
103
104pub struct EquiXPow {
105    /// Target effort
106    pub effort: u32,
107    /// The next [`Challenge`] to try
108    pub challenge: Challenge,
109    /// Configuration settings for Equi-X
110    pub equix: EquiXBuilder,
111    /// Temporary memory for Equi-X to use
112    pub mem: SolverMemory,
113}
114
115impl EquiXPow {
116    pub fn run(&mut self) -> Result<Solution> {
117        loop {
118            if let Some(solution) = self.run_step()? {
119                return Ok(solution);
120            }
121        }
122    }
123
124    pub fn run_step(&mut self) -> Result<Option<Solution>> {
125        match self.equix.build(self.challenge.as_ref()) {
126            Ok(equix) => {
127                for candidate in equix.solve_with_memory(&mut self.mem) {
128                    if self.challenge.check_effort(&candidate.to_bytes(), self.effort) {
129                        return Ok(Some(candidate))
130                    }
131                }
132            }
133            Err(equix::Error::Hash(HashError::ProgramConstraints)) => (),
134            Err(e) => {
135                return Err(Error::Custom(e.to_string()));
136            }
137        };
138        self.challenge.increment_nonce();
139        Ok(None)
140    }
141
142    pub fn verify(&self, challenge: &Challenge, solution: &Solution) -> Result<()> {
143        if challenge.check_effort(&solution.to_bytes(), self.effort) {
144            return self
145                .equix
146                .verify(challenge.as_ref(), solution)
147                .map_err(|e| Error::Custom(e.to_string()))
148        }
149        Err(Error::Custom("Equi-X solution has insufficient effort".into()))
150    }
151}