Skip to content

Commit

Permalink
Merge pull request #1083 from zcash/equihash-solver-tromp
Browse files Browse the repository at this point in the history
equihash: Add Rust API for Tromp solver
  • Loading branch information
str4d authored Feb 14, 2025
2 parents 0432867 + f3d5a5c commit 094e0eb
Show file tree
Hide file tree
Showing 13 changed files with 1,400 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/equihash/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `equihash::tromp` module behind the experimental `solver` feature flag.

## [0.2.0] - 2022-06-24
### Changed
Expand Down
17 changes: 13 additions & 4 deletions components/equihash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@ license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.56.1"

[features]
default = ["std"]
std = []

# Experimental tromp solver support, builds the C++ tromp solver and Rust FFI layer.
solver = ["dep:cc", "std"]

[dependencies]
core2.workspace = true
blake2b_simd.workspace = true

[build-dependencies]
cc = { version = "1", optional = true }

[dev-dependencies]
hex = "0.4"

[lib]
bench = false

[lints]
workspace = true

[features]
default = ["std"]
std = []
17 changes: 17 additions & 0 deletions components/equihash/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Build script for the equihash tromp solver in C.
fn main() {
#[cfg(feature = "solver")]
build_tromp_solver();
}

#[cfg(feature = "solver")]
fn build_tromp_solver() {
cc::Build::new()
.include("tromp/")
.file("tromp/equi_miner.c")
.compile("equitromp");

// Tell Cargo to only rerun this build script if the tromp C files or headers change.
println!("cargo:rerun-if-changed=tromp");
}
60 changes: 60 additions & 0 deletions components/equihash/src/blake2b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2020-2022 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .

// This module uses unsafe code for FFI into blake2b.
#![allow(unsafe_code)]

use blake2b_simd::{State, PERSONALBYTES};

use std::boxed::Box;
use std::ptr;
use std::slice;

#[no_mangle]
pub(crate) extern "C" fn blake2b_init(
output_len: usize,
personalization: *const [u8; PERSONALBYTES],
) -> *mut State {
let personalization = unsafe { personalization.as_ref().unwrap() };

Box::into_raw(Box::new(
blake2b_simd::Params::new()
.hash_length(output_len)
.personal(personalization)
.to_state(),
))
}

#[no_mangle]
pub(crate) extern "C" fn blake2b_clone(state: *const State) -> *mut State {
unsafe { state.as_ref() }
.map(|state| Box::into_raw(Box::new(state.clone())))
.unwrap_or(ptr::null_mut())
}

#[no_mangle]
pub(crate) extern "C" fn blake2b_free(state: *mut State) {
if !state.is_null() {
drop(unsafe { Box::from_raw(state) });
}
}

#[no_mangle]
pub(crate) 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(crate) 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) };

// Allow consuming only part of the output.
let hash = state.finalize();
assert!(output_len <= hash.as_bytes().len());
output.copy_from_slice(&hash.as_bytes()[..output_len]);
}
5 changes: 5 additions & 0 deletions components/equihash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ mod verify;
mod test_vectors;

pub use verify::{is_valid_solution, Error};

#[cfg(feature = "solver")]
mod blake2b;
#[cfg(feature = "solver")]
pub mod tromp;
82 changes: 76 additions & 6 deletions components/equihash/src/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@ use core2::io::{Cursor, Read};

use crate::params::Params;

// Rough translation of CompressArray() from:
// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L39-L76
#[cfg(any(feature = "solver", test))]
fn compress_array(array: &[u8], bit_len: usize, byte_pad: usize) -> Vec<u8> {
let index_bytes = (u32::BITS / 8) as usize;
assert!(bit_len >= 8);
assert!(8 * index_bytes >= 7 + bit_len);

let in_width: usize = (bit_len + 7) / 8 + byte_pad;
let out_len = bit_len * array.len() / (8 * in_width);

let mut out = Vec::with_capacity(out_len);
let bit_len_mask: u32 = (1 << (bit_len as u32)) - 1;

// The acc_bits least-significant bits of acc_value represent a bit sequence
// in big-endian order.
let mut acc_bits: usize = 0;
let mut acc_value: u32 = 0;

let mut j: usize = 0;
for _i in 0..out_len {
// When we have fewer than 8 bits left in the accumulator, read the next
// input element.
if acc_bits < 8 {
acc_value <<= bit_len;
for x in byte_pad..in_width {
acc_value |= (
// Apply bit_len_mask across byte boundaries
(array[j + x] & ((bit_len_mask >> (8 * (in_width - x - 1))) as u8)) as u32
)
.wrapping_shl(8 * (in_width - x - 1) as u32); // Big-endian
}
j += in_width;
acc_bits += bit_len;
}

acc_bits -= 8;
out.push((acc_value >> acc_bits) as u8);
}

out
}

pub(crate) fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec<u8> {
assert!(bit_len >= 8);
assert!(u32::BITS as usize >= 7 + bit_len);
Expand Down Expand Up @@ -49,6 +92,31 @@ pub(crate) fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec<u
vout
}

// Rough translation of GetMinimalFromIndices() from:
// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L130-L145
#[cfg(any(feature = "solver", test))]
pub(crate) fn minimal_from_indices(p: Params, indices: &[u32]) -> Vec<u8> {
let c_bit_len = p.collision_bit_length();
let index_bytes = (u32::BITS / 8) as usize;
let digit_bytes = ((c_bit_len + 1) + 7) / 8;
assert!(digit_bytes <= index_bytes);

let len_indices = indices.len() * index_bytes;
let byte_pad = index_bytes - digit_bytes;

// Rough translation of EhIndexToArray(index, array_pointer) from:
// https://github.com/zcash/zcash/blob/6fdd9f1b81d3b228326c9826fa10696fc516444b/src/crypto/equihash.cpp#L123-L128
//
// Big-endian so that lexicographic array comparison is equivalent to integer comparison.
let array: Vec<u8> = indices
.iter()
.flat_map(|index| index.to_be_bytes())
.collect();
assert_eq!(array.len(), len_indices);

compress_array(&array, c_bit_len + 1, byte_pad)
}

fn read_u32_be(csr: &mut Cursor<Vec<u8>>) -> core2::io::Result<u32> {
let mut n = [0; 4];
csr.read_exact(&mut n)?;
Expand Down Expand Up @@ -81,11 +149,14 @@ pub(crate) fn indices_from_minimal(p: Params, minimal: &[u8]) -> Option<Vec<u32>

#[cfg(test)]
mod tests {
use super::{expand_array, indices_from_minimal, Params};
use crate::minimal::minimal_from_indices;

use super::{compress_array, expand_array, indices_from_minimal, Params};

#[test]
fn array_expansion() {
fn array_compression_and_expansion() {
let check_array = |(bit_len, byte_pad), compact, expanded| {
assert_eq!(compress_array(expanded, bit_len, byte_pad), compact);
assert_eq!(expand_array(compact, bit_len, byte_pad), expanded);
};

Expand Down Expand Up @@ -154,10 +225,9 @@ mod tests {
#[test]
fn minimal_solution_repr() {
let check_repr = |minimal, indices| {
assert_eq!(
indices_from_minimal(Params { n: 80, k: 3 }, minimal).unwrap(),
indices,
);
let p = Params { n: 80, k: 3 };
assert_eq!(minimal_from_indices(p, indices), minimal);
assert_eq!(indices_from_minimal(p, minimal).unwrap(), indices);
};

// The solutions here are not intended to be valid.
Expand Down
Loading

0 comments on commit 094e0eb

Please sign in to comment.