Skip to content

Commit

Permalink
equihash: Add Rust API for Tromp solver
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Jan 4, 2024
1 parent 605dc30 commit f3ea80e
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 8 deletions.
10 changes: 5 additions & 5 deletions components/equihash/src/blake2b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// file COPYING or https://www.opensource.org/licenses/mit-license.php .

use blake2b_simd::{State, PERSONALBYTES};
use libc::{c_uchar, size_t};

use std::ptr;
use std::slice;

#[no_mangle]
pub extern "C" fn blake2b_init(

Check warning on line 11 in components/equihash/src/blake2b.rs

View check run for this annotation

Codecov / codecov/patch

components/equihash/src/blake2b.rs#L11

Added line #L11 was not covered by tests
output_len: size_t,
personalization: *const [c_uchar; PERSONALBYTES],
output_len: usize,
personalization: *const [u8; PERSONALBYTES],
) -> *mut State {
let personalization = unsafe { personalization.as_ref().unwrap() };

Check warning on line 15 in components/equihash/src/blake2b.rs

View check run for this annotation

Codecov / codecov/patch

components/equihash/src/blake2b.rs#L15

Added line #L15 was not covered by tests

Expand All @@ -37,15 +37,15 @@ pub extern "C" fn blake2b_free(state: *mut State) {
}

#[no_mangle]
pub extern "C" fn blake2b_update(state: *mut State, input: *const c_uchar, input_len: size_t) {
pub extern "C" fn blake2b_update(state: *mut State, input: *const u8, input_len: usize) {
let state = unsafe { state.as_mut().unwrap() };
let input = unsafe { slice::from_raw_parts(input, input_len) };

state.update(input);
}

#[no_mangle]
pub extern "C" fn blake2b_finalize(state: *mut State, output: *mut c_uchar, output_len: size_t) {
pub extern "C" fn blake2b_finalize(state: *mut State, output: *mut u8, output_len: usize) {
let state = unsafe { state.as_mut().unwrap() };
let output = unsafe { slice::from_raw_parts_mut(output, output_len) };

Expand Down
3 changes: 3 additions & 0 deletions components/equihash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ mod verify;
mod test_vectors;

pub use verify::{is_valid_solution, Error};

mod blake2b;
pub mod tromp;
131 changes: 131 additions & 0 deletions components/equihash/src/tromp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use std::marker::{PhantomData, PhantomPinned};
use std::slice;

use blake2b_simd::State;

use crate::{blake2b, verify};

#[repr(C)]
struct CEqui {
_f: [u8; 0],
_m: PhantomData<(*mut u8, PhantomPinned)>,
}

#[link(name = "equitromp")]
extern "C" {
#[allow(improper_ctypes)]
fn equi_new(
n_threads: u32,
blake2b_clone: extern "C" fn(state: *const State) -> *mut State,
blake2b_free: extern "C" fn(state: *mut State),
blake2b_update: extern "C" fn(state: *mut State, input: *const u8, input_len: usize),
blake2b_finalize: extern "C" fn(state: *mut State, output: *mut u8, output_len: usize),
) -> *mut CEqui;
fn equi_free(eq: *mut CEqui);
#[allow(improper_ctypes)]
fn equi_setstate(eq: *mut CEqui, ctx: *const State);
fn equi_clearslots(eq: *mut CEqui);
fn equi_digit0(eq: *mut CEqui, id: u32);
fn equi_digitodd(eq: *mut CEqui, r: u32, id: u32);
fn equi_digiteven(eq: *mut CEqui, r: u32, id: u32);
fn equi_digitK(eq: *mut CEqui, id: u32);
fn equi_nsols(eq: *const CEqui) -> usize;
fn equi_sols(eq: *const CEqui) -> *const *const u32;
}

unsafe fn worker(p: verify::Params, curr_state: &State) -> Vec<Vec<u32>> {
// Create solver and initialize it.
let eq = equi_new(
1,
blake2b::blake2b_clone,
blake2b::blake2b_free,
blake2b::blake2b_update,
blake2b::blake2b_finalize,
);
equi_setstate(eq, curr_state);

// Initialization done, start algo driver.
equi_digit0(eq, 0);
equi_clearslots(eq);
for r in 1..p.k {
if (r & 1) != 0 {
equi_digitodd(eq, r, 0)
} else {
equi_digiteven(eq, r, 0)
};
equi_clearslots(eq);
}
equi_digitK(eq, 0);

let solutions = {
let nsols = equi_nsols(eq);
let sols = equi_sols(eq);
let solutions = slice::from_raw_parts(sols, nsols);
let solution_len = 1 << p.k;

solutions
.iter()
.map(|solution| slice::from_raw_parts(*solution, solution_len).to_vec())
.collect::<Vec<_>>()
};

equi_free(eq);

solutions
}

pub fn solve_200_9<const N: usize>(
input: &[u8],
mut next_nonce: impl FnMut() -> Option<[u8; N]>,
) -> Vec<Vec<u32>> {
let p = verify::Params::new(200, 9).expect("should be valid");
let mut state = verify::initialise_state(p.n, p.k, p.hash_output());
state.update(input);

loop {

Check warning on line 85 in components/equihash/src/tromp.rs

View check run for this annotation

Codecov / codecov/patch

components/equihash/src/tromp.rs#L85

Added line #L85 was not covered by tests
let nonce = match next_nonce() {
Some(nonce) => nonce,
None => break vec![],
};

let mut curr_state = state.clone();
curr_state.update(&nonce);

let solutions = unsafe { worker(p, &curr_state) };
if !solutions.is_empty() {
break solutions;

Check warning on line 96 in components/equihash/src/tromp.rs

View check run for this annotation

Codecov / codecov/patch

components/equihash/src/tromp.rs#L96

Added line #L96 was not covered by tests
}
}
}

#[cfg(test)]
mod tests {
use super::solve_200_9;

#[test]
fn run_solver() {
let input = b"Equihash is an asymmetric PoW based on the Generalised Birthday problem.";
let mut nonce = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];

let solutions = solve_200_9(input, || {
nonce[0] += 1;
if nonce[0] == 0 {
None
} else {
Some(nonce)
}
});

if solutions.is_empty() {
println!("Found no solutions");
} else {
println!("Found {} solutions:", solutions.len());
for solution in solutions {
println!("- {:?}", solution);
}
}
}
}
6 changes: 3 additions & 3 deletions components/equihash/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct Node {
}

impl Params {
fn new(n: u32, k: u32) -> Result<Self, Error> {
pub(crate) fn new(n: u32, k: u32) -> Result<Self, Error> {
// We place the following requirements on the parameters:
// - n is a multiple of 8, so the hash output has an exact byte length.
// - k >= 3 so the encoded solutions have an exact byte length.
Expand All @@ -36,7 +36,7 @@ impl Params {
fn indices_per_hash_output(&self) -> u32 {
512 / self.n
}
fn hash_output(&self) -> u8 {
pub(crate) fn hash_output(&self) -> u8 {
(self.indices_per_hash_output() * self.n / 8) as u8
}
fn collision_bit_length(&self) -> usize {
Expand Down Expand Up @@ -148,7 +148,7 @@ impl fmt::Display for Kind {
}
}

fn initialise_state(n: u32, k: u32, digest_len: u8) -> Blake2bState {
pub(crate) fn initialise_state(n: u32, k: u32, digest_len: u8) -> Blake2bState {
let mut personalization: Vec<u8> = Vec::from("ZcashPoW");
personalization.write_u32::<LittleEndian>(n).unwrap();
personalization.write_u32::<LittleEndian>(k).unwrap();
Expand Down
10 changes: 10 additions & 0 deletions components/equihash/tromp/equi_miner.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ typedef struct equi equi;
memset(eq->nslots, 0, NBUCKETS * sizeof(au32)); // only nslots[0] needs zeroing
eq->nsols = 0;
}
void equi_clearslots(equi *eq) {
eq->xfull = eq->bfull = eq->hfull = 0;
}
u32 getslot(equi *eq, const u32 r, const u32 bucketi) {
#ifdef EQUIHASH_TROMP_ATOMIC
return std::atomic_fetch_add_explicit(&eq->nslots[r&1][bucketi], 1U, std::memory_order_relaxed);
Expand Down Expand Up @@ -637,6 +640,13 @@ nc++, candidate(eq, tree_from_bid(bucketid, s0, s1));
//printf(" %d candidates ", nc);
}

size_t equi_nsols(const equi *eq) {
return eq->nsols;
}
proof *equi_sols(const equi *eq) {
return eq->sols;
}

typedef struct {
u32 id;
pthread_t thread;
Expand Down

0 comments on commit f3ea80e

Please sign in to comment.