Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

equihash: Add Rust API for Tromp solver #1083

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

12 changes: 12 additions & 0 deletions components/equihash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.56.1"

[features]
default = []

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

[dependencies]
blake2b_simd = "1"
byteorder = "1"

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

[dev-dependencies]
hex = "0.4"

[lib]
bench = false
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");
}
59 changes: 59 additions & 0 deletions components/equihash/src/blake2b.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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::ptr;
use std::slice;

#[no_mangle]
pub 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 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 extern "C" fn blake2b_free(state: *mut State) {
if !state.is_null() {
drop(unsafe { Box::from_raw(state) });
}
}

#[no_mangle]
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 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 @@ -28,3 +28,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 @@ -5,6 +5,49 @@ use byteorder::{BigEndian, ReadBytesExt};

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;
arya2 marked this conversation as resolved.
Show resolved Hide resolved
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
Comment on lines +37 to +38
Copy link

@arya2 arya2 Jul 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an unnecessary change from the c++ logic, it should panic in minimal_from_indices() before it gets here when using parameters that would be affected by the change.

Suggested change
)
.wrapping_shl(8 * (in_width - x - 1) as u32); // Big-endian
) << (8 * (in_width - x - 1) as u32); // Big-endian

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible this is a problem if it's different than what the original code was doing?

}
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 @@ -50,6 +93,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;
arya2 marked this conversation as resolved.
Show resolved Hide resolved
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)
}

/// Returns `None` if the parameters are invalid for this minimal encoding.
pub(crate) fn indices_from_minimal(p: Params, minimal: &[u8]) -> Option<Vec<u32>> {
let c_bit_len = p.collision_bit_length();
Expand All @@ -76,11 +144,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 @@ -149,10 +220,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
Loading