Migrate Equihash Rust FFI to `cxx`

This integrates `cxxbridge` into the build system, adding its generated
source files to `libzcash`. We currently need to manually specify each
Rust file containing a bridge description.
This commit is contained in:
Jack Grigg 2022-05-26 02:36:15 +00:00
parent 21f1bbf4aa
commit 54aeb2c408
9 changed files with 82 additions and 69 deletions

1
.gitignore vendored
View File

@ -83,6 +83,7 @@ Makefile
# Rust
.cargo/.configured-for-*
.cargo/config
src/rust/gen/
target/
# Unit-tests

View File

@ -15,7 +15,9 @@ BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config
BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS)
BITCOIN_CONFIG_INCLUDES += -I$(srcdir)/rust/include
BITCOIN_CONFIG_INCLUDES += -I$(srcdir)/rust/gen/include
BITCOIN_INCLUDES += -I$(srcdir)/rust/include
BITCOIN_INCLUDES += -I$(srcdir)/rust/gen/include
BITCOIN_INCLUDES += -I$(srcdir)/secp256k1/include
BITCOIN_INCLUDES += -I$(srcdir)/univalue/include
@ -42,6 +44,20 @@ if ENABLE_WALLET
LIBBITCOIN_WALLET=libbitcoin_wallet.a
endif
# TODO: Figure out how to avoid an explicit file list.
CXXBRIDGE_RS = rust/src/equihash.rs
CXXBRIDGE_H = rust/gen/include/rust/equihash.h
CXXBRIDGE_CPP = rust/gen/src/equihash.cpp
# We add a rust/cxx.h include to indicate that we provide this (via the rustcxx depends
# package), so that cxxbridge doesn't include it within the generated headers and code.
CXXBRIDGE_OPTS = -i rust/cxx.h
$(CXXBRIDGE_RS): ;
$(CXXBRIDGE_H) $(CXXBRIDGE_CPP): $(CXXBRIDGE_RS)
@$(MKDIR_P) $(@D)
$(AM_V_GEN)$(CXXBRIDGE) $(CXXBRIDGE_OPTS) $< -o $@
# We pass through CC etc. flags so they are available to Rust dependencies that internally
# compile C or C++ code with the `cc` crate.
#
@ -572,6 +588,7 @@ zcash_tx_LDADD += $(BOOST_LIBS)
# zcash protocol primitives #
libzcash_a_SOURCES = \
$(CXXBRIDGE_CPP) \
zcash/IncrementalMerkleTree.cpp \
zcash/NoteEncryption.cpp \
zcash/Address.cpp \
@ -617,7 +634,7 @@ endif
libzcash_script_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS)
libzcash_script_la_LIBADD = $(LIBSECP256K1)
libzcash_script_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/rust/include -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL
libzcash_script_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/rust/include -I$(srcdir)/rust/gen/include -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL
libzcash_script_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
endif
@ -629,6 +646,8 @@ CTAES_DIST += crypto/ctaes/ctaes.h
CTAES_DIST += crypto/ctaes/README.md
CTAES_DIST += crypto/ctaes/test.c
BUILT_SOURCES = $(CXXBRIDGE_H)
CLEANFILES = *.gcda *.gcno */*.gcno wallet/*/*.gcno $(bin_SCRIPTS)
DISTCLEANFILES = obj/build.h
@ -640,6 +659,7 @@ clean-local:
-$(MAKE) -C secp256k1 clean
-$(MAKE) -C univalue clean
rm -f leveldb/*/*.gcno leveldb/helpers/memenv/*.gcno
rm -f rust/gen
rm -f fuzz.cpp
rm -rf fuzzing/*/output
-rm -f config.h

View File

@ -15,6 +15,7 @@
#include "uint256.h"
#include <librustzcash.h>
#include <rust/equihash.h>
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
{
@ -114,11 +115,11 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const Consensus::Params&
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << I;
return librustzcash_eh_isvalid(
return equihash::is_valid(
n, k,
(unsigned char*)&ss[0], ss.size(),
pblock->nNonce.begin(), pblock->nNonce.size(),
pblock->nSolution.data(), pblock->nSolution.size());
{(const unsigned char*)ss.data(), ss.size()},
{pblock->nNonce.begin(), pblock->nNonce.size()},
{pblock->nSolution.data(), pblock->nSolution.size()});
}
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)

View File

@ -38,19 +38,6 @@ extern "C" {
size_t sprout_path_len
);
/// Validates the provided Equihash solution against
/// the given parameters, input and nonce.
bool librustzcash_eh_isvalid(
uint32_t n,
uint32_t k,
const unsigned char* input,
size_t input_len,
const unsigned char* nonce,
size_t nonce_len,
const unsigned char* soln,
size_t soln_len
);
/// Writes the "uncommitted" note value for empty leaves
/// of the merkle tree. `result` must be a valid pointer
/// to 32 bytes which will be written.

32
src/rust/src/equihash.rs Normal file
View File

@ -0,0 +1,32 @@
use tracing::error;
use zcash_primitives::block::equihash;
#[cxx::bridge]
mod ffi {
#[namespace = "equihash"]
extern "Rust" {
fn is_valid(n: u32, k: u32, input: &[u8], nonce: &[u8], soln: &[u8]) -> bool;
}
}
/// Validates the provided Equihash solution against the given parameters, input
/// and nonce.
fn is_valid(n: u32, k: u32, input: &[u8], nonce: &[u8], soln: &[u8]) -> bool {
let expected_soln_len = (1 << k) * ((n / (k + 1)) as usize + 1) / 8;
if (k >= n) || (n % 8 != 0) || (soln.len() != expected_soln_len) {
error!(
"equihash::is_valid: params wrong, n={}, k={}, soln_len={} expected={}",
n,
k,
soln.len(),
expected_soln_len,
);
return false;
}
if let Err(e) = equihash::is_valid_solution(n, k, input, nonce, soln) {
error!("equihash::is_valid: is_valid_solution: {}", e);
false
} else {
true
}
}

View File

@ -31,7 +31,7 @@ use std::path::{Path, PathBuf};
use std::slice;
use std::sync::Once;
use subtle::CtOption;
use tracing::{error, info};
use tracing::info;
#[cfg(not(target_os = "windows"))]
use std::ffi::OsStr;
@ -44,7 +44,6 @@ use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use zcash_primitives::{
block::equihash,
constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR},
merkle_tree::MerklePath,
sapling::{
@ -67,6 +66,7 @@ use zcash_proofs::{
mod blake2b;
mod ed25519;
mod equihash;
mod metrics_ffi;
mod streams_ffi;
mod tracing_ffi;
@ -528,38 +528,6 @@ pub extern "C" fn librustzcash_sapling_ka_derivepublic(
true
}
/// Validates the provided Equihash solution against the given parameters, input
/// and nonce.
#[no_mangle]
pub extern "C" fn librustzcash_eh_isvalid(
n: u32,
k: u32,
input: *const c_uchar,
input_len: size_t,
nonce: *const c_uchar,
nonce_len: size_t,
soln: *const c_uchar,
soln_len: size_t,
) -> bool {
let expected_soln_len = (1 << k) * ((n / (k + 1)) as usize + 1) / 8;
if (k >= n) || (n % 8 != 0) || (soln_len != expected_soln_len) {
error!(
"eh_isvalid: params wrong, n={}, k={}, soln_len={} expected={}",
n, k, soln_len, expected_soln_len,
);
return false;
}
let rs_input = unsafe { slice::from_raw_parts(input, input_len) };
let rs_nonce = unsafe { slice::from_raw_parts(nonce, nonce_len) };
let rs_soln = unsafe { slice::from_raw_parts(soln, soln_len) };
if let Err(e) = equihash::is_valid_solution(n, k, rs_input, rs_nonce, rs_soln) {
error!("eh_isvalid: is_valid_solution: {}", e);
false
} else {
true
}
}
/// Creates a Sapling verification context. Please free this when you're done.
#[no_mangle]
pub extern "C" fn librustzcash_sapling_verification_ctx_init(

View File

@ -13,7 +13,7 @@
#include "test/test_bitcoin.h"
#include "uint256.h"
#include "librustzcash.h"
#include <rust/equihash.h>
#include <sstream>
#include <set>
@ -95,11 +95,11 @@ void TestEquihashValidator(unsigned int n, unsigned int k, const std::string &I,
PrintSolution(strm, soln);
BOOST_TEST_MESSAGE(strm.str());
bool isValid = librustzcash_eh_isvalid(
bool isValid = equihash::is_valid(
n, k,
(unsigned char*)&I[0], I.size(),
V.begin(), V.size(),
minimal.data(), minimal.size());
{(const unsigned char*)I.data(), I.size()},
{V.begin(), V.size()},
{minimal.data(), minimal.size()});
BOOST_CHECK(isValid == expected);
}
@ -213,22 +213,25 @@ BOOST_AUTO_TEST_CASE(validator_allbitsmatter) {
size_t cBitLen { n/(k+1) };
std::vector<unsigned char> sol_char = GetMinimalFromIndices(soln, cBitLen);
rust::Slice<const uint8_t> input{(unsigned char*)&I[0], I.size()};
rust::Slice<const uint8_t> nonce{V.begin(), V.size()};
// Prove that the solution is valid.
BOOST_CHECK(librustzcash_eh_isvalid(
BOOST_CHECK(equihash::is_valid(
n, k,
(unsigned char*)&I[0], I.size(),
V.begin(), V.size(),
sol_char.data(), sol_char.size()));
input,
nonce,
{sol_char.data(), sol_char.size()}));
// Changing any single bit of the encoded solution should make it invalid.
for (size_t i = 0; i < sol_char.size() * 8; i++) {
std::vector<unsigned char> mutated = sol_char;
mutated.at(i/8) ^= (1 << (i % 8));
BOOST_CHECK(!librustzcash_eh_isvalid(
BOOST_CHECK(!equihash::is_valid(
n, k,
(unsigned char*)&I[0], I.size(),
V.begin(), V.size(),
mutated.data(), mutated.size()));
input,
nonce,
{mutated.data(), mutated.size()}));
}
}

View File

@ -246,11 +246,11 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
}
for (auto soln : solns) {
if (!librustzcash_eh_isvalid(
if (!equihash::is_valid(
n, k,
(unsigned char*)&ss[0], ss.size(),
pblock->nNonce.begin(), pblock->nNonce.size(),
soln.data(), soln.size())) continue;
{(const unsigned char*)ss.data(), ss.size()},
{pblock->nNonce.begin(), pblock->nNonce.size()},
{soln.data(), soln.size()})) continue;
pblock->nSolution = soln;
CValidationState state;

View File

@ -31,6 +31,7 @@ rm -rf test_bitcoin.coverage/ zcash-gtest.coverage/ total.coverage/
rm -rf cache
rm -rf target
rm -rf depends/work
rm -rf src/rust/gen
find src -type f -and \( -name '*.Po' -or -name '*.Plo' -or -name '*.o' -or -name '*.a' -or -name '*.lib' -or -name '*.la' -or -name '*.lo' -or -name '*.lai' -or -name '*.pc' -or -name '.dirstamp' -or -name '*.gcda' -or -name '*.gcno' -or -name '*.sage.py' -or -name '*.trs' \) -delete