/ bin / fud / fud / src / equix.rs
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  
 25  use std::convert::AsRef;
 26  
 27  use blake2::{digest::consts::U4, Blake2b, Digest};
 28  pub use equix::{EquiXBuilder, HashError, RuntimeOption, Solution, SolverMemory};
 29  
 30  use darkfi::{Error, Result};
 31  
 32  /// Algorithm personalization string
 33  const P_STRING: &[u8] = b"DarkFi Equi-X\0";
 34  
 35  /// Length of the personalization string, in bytes
 36  const P_STRING_LEN: usize = 14;
 37  
 38  /// Length of the nonce value generated by clients and included in the solution
 39  pub const NONCE_LEN: usize = 16;
 40  
 41  /// A challenge string
 42  #[derive(Debug, Clone, Eq, PartialEq)]
 43  pub struct Challenge(Vec<u8>);
 44  
 45  impl 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  
 98  impl AsRef<[u8]> for Challenge {
 99      fn as_ref(&self) -> &[u8] {
100          self.0.as_ref()
101      }
102  }
103  
104  pub 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  
115  impl 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  }