Add pre-commit hooks and workspace lint config for formatting, linting, and dependency auditing.

This commit is contained in:
Alejandro Cabeza Romero 2026-05-07 11:32:26 +02:00
parent 8bee8c3137
commit df0bcc16e7
No known key found for this signature in database
GPG Key ID: DA3D14AE478030FD
38 changed files with 630 additions and 297 deletions

32
.cargo-deny.toml Normal file
View File

@ -0,0 +1,32 @@
# Config file reference can be found at https://embarkstudios.github.io/cargo-deny/checks/cfg.html.
[graph]
all-features = true
exclude-dev = true
no-default-features = true
[advisories]
ignore = []
unused-ignored-advisory = "deny"
yanked = "deny"
[bans]
allow-wildcard-paths = false
multiple-versions = "allow"
[licenses]
allow = [
"Apache-2.0 WITH LLVM-exception",
"Apache-2.0",
"BSD-3-Clause",
"CDLA-Permissive-2.0",
"ISC",
"MIT",
"Zlib",
]
private = { ignore = false }
unused-allowed-license = "deny"
[sources]
unknown-git = "deny"
unknown-registry = "deny"

46
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,46 @@
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: eeee35a89e69d5772bdee97db1a6a898467b686e # 1.0
hooks:
- id: fmt
entry: cargo +nightly-2026-02-28 fmt --manifest-path rust/Cargo.toml --all
pass_filenames: false
- id: clippy
name: cargo clippy
entry: cargo clippy --manifest-path rust/Cargo.toml
args:
["--all", "--all-targets", "--all-features", "--", "-D", "warnings"]
pass_filenames: false
- repo: https://github.com/EmbarkStudios/cargo-deny
rev: 09faadcea2d0d1742492e6872b743d1e4d151a27 # 0.19.0
hooks:
- id: cargo-deny
args:
- --manifest-path
- rust/Cargo.toml
- --locked
- --all-features
- check
- --hide-inclusion-graph
- -c
- .cargo-deny.toml
- --show-stats
- -D
- warnings
- repo: https://github.com/ComPWA/taplo-pre-commit
rev: 6355f0e9a28a910b80c91f7f6521c03d0ea50fba # 0.9.3 # Can't update until: https://github.com/tamasfe/taplo/issues/805
hooks:
- id: taplo-format
- id: taplo-lint
- repo: https://github.com/bnjbvr/cargo-machete
rev: 78beac95c8fd7c25bdfb194415128523e41512d5 # 0.19.1
hooks:
- id: cargo-machete
args: ["rust/"]
- repo: local
hooks:
- id: cargo-hack-check
language: script
name: cargo hack check
entry: ./hooks/cargo-hack.sh
stages: [manual]

3
hooks/cargo-hack.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
RUSTFLAGS="-D warnings" cargo hack --feature-powerset --no-dev-deps --keep-going --all-targets check

View File

@ -1,3 +1,4 @@
cargo_root := justfile_directory() + "/rust"
src := justfile_directory() + "/src"
ci_makefile := justfile_directory() + "/.github/resources/witness-generator/Makefile"
circom_version := "2.2.2" # This version must match the version used in the CI
@ -5,6 +6,11 @@ circom_version := "2.2.2" # This version must match the version used in the CI
os := `uname -s`
sed_i := if os == "Darwin" { "sed -i ''" } else { "sed -i" }
# Shortcut to run cargo commands from the repo root (the Cargo workspace lives under ./rust/).
# Potentially temporary.
cargo +args:
cd {{cargo_root}} && cargo {{args}}
# Verify the installed circom matches the pinned version.
check-circom:
@circom --version | grep -qF "{{circom_version}}" || \

View File

@ -1,32 +1,184 @@
[workspace.package]
categories = ["cryptography", "external-ffi-bindings"]
categories = ["cryptography", "external-ffi-bindings"]
description = "Rust bindings for the Logos Blockchain Circuits, providing a safe and efficient interface for interacting with the underlying cryptographic circuits."
edition = "2024"
keywords = ["blockchain", "privacy"]
license = "MIT or Apache-2.0"
readme = "README.md"
repository = "https://github.com/logos-blockchain/logos-blockchain-circuits"
version = "0.4.2"
edition = "2024"
keywords = ["blockchain", "privacy"]
license = "MIT or Apache-2.0"
readme = "README.md"
repository = "https://github.com/logos-blockchain/logos-blockchain-circuits"
version = "0.4.2"
[workspace]
members = [
"logos-blockchain-circuits-poc-sys",
"logos-blockchain-circuits-pol-sys",
"logos-blockchain-circuits-poq-sys",
"logos-blockchain-circuits-signature-sys",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-common"
"logos-blockchain-circuits-poc-sys",
"logos-blockchain-circuits-pol-sys",
"logos-blockchain-circuits-poq-sys",
"logos-blockchain-circuits-signature-sys",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-common",
]
resolver = "3"
# Sourced from https://github.com/logos-co/nomos/blob/main/Cargo.toml
[workspace.lints.clippy]
# Cargo and allowed cargo warnings (new lints will warn by default)
cargo = { level = "warn", priority = -1 }
# TODO: We should tackle this lint at some point, to reduce dependencies duplication, reduce compilation times and bring down our binary size.
multiple_crate_versions = { level = "allow" }
# Nursery and allowed nursery warnings (new lints will warn by default)
nursery = { level = "warn", priority = -1 }
# Pedantic and allowed pedantic warnings (new lints will warn by default)
pedantic = { level = "warn", priority = -1 }
similar_names = { level = "allow" }
# Restriction and allowed restriction warnings (new lints will warn by default)
restriction = { level = "warn", priority = -1 }
absolute_paths = { level = "allow" }
alloc_instead_of_core = { level = "allow" }
arbitrary_source_item_ordering = { level = "allow" }
big_endian_bytes = { level = "allow" }
blanket_clippy_restriction_lints = { level = "allow" }
decimal_literal_representation = { level = "allow" }
default_numeric_fallback = { level = "allow" }
deref_by_slicing = { level = "allow" }
else_if_without_else = { level = "allow" }
exhaustive_enums = { level = "allow" }
exhaustive_structs = { level = "allow" }
exit = { level = "allow" }
expect_used = { level = "allow" }
field_scoped_visibility_modifiers = { level = "allow" }
float_arithmetic = { level = "allow" }
get_unwrap = { level = "allow" }
host_endian_bytes = { level = "allow" }
implicit_return = { level = "allow" }
integer_division_remainder_used = { level = "allow" }
iter_over_hash_type = { level = "allow" }
let_underscore_must_use = { level = "allow" }
let_underscore_untyped = { level = "allow" }
little_endian_bytes = { level = "allow" }
map_err_ignore = { level = "allow" }
min_ident_chars = { level = "allow" }
missing_asserts_for_indexing = { level = "allow" }
missing_docs_in_private_items = { level = "allow" }
missing_inline_in_public_items = { level = "allow" }
missing_trait_methods = { level = "allow" }
mixed_read_write_in_expression = { level = "allow" }
mod_module_files = { level = "allow" }
module_name_repetitions = { level = "allow" }
modulo_arithmetic = { level = "allow" }
panic = { level = "allow" }
panic_in_result_fn = { level = "allow" }
partial_pub_fields = { level = "allow" }
print_stderr = { level = "allow" }
print_stdout = { level = "allow" }
pub_use = { level = "allow" }
pub_with_shorthand = { level = "allow" }
question_mark_used = { level = "allow" }
self_named_module_files = { level = "allow" }
semicolon_inside_block = { level = "allow" }
single_call_fn = { level = "allow" }
single_char_lifetime_names = { level = "allow" }
std_instead_of_alloc = { level = "allow" }
std_instead_of_core = { level = "allow" }
struct_field_names = { level = "allow" }
unseparated_literal_suffix = { level = "allow" }
use_debug = { level = "allow" }
wildcard_enum_match_arm = { level = "allow" }
# TODO: Address these lints at some point. To allow them, move them to the section where they belong (e.g., pedantic), and keep allowing them. To enforce them, since all lints are "warn" by default, just remove them from this list.
arithmetic_side_effects = { level = "allow" }
as_conversions = { level = "allow" }
as_pointer_underscore = { level = "allow" }
as_underscore = { level = "allow" }
assertions_on_result_states = { level = "allow" }
cast_possible_truncation = { level = "allow" }
cast_possible_wrap = { level = "allow" }
cast_precision_loss = { level = "allow" }
cast_sign_loss = { level = "allow" }
doc_paragraphs_missing_punctuation = { level = "allow" }
error_impl_error = { level = "allow" }
impl_trait_in_params = { level = "allow" }
indexing_slicing = { level = "allow" }
infinite_loop = { level = "allow" }
integer_division = { level = "allow" }
large_stack_frames = { level = "allow" }
missing_assert_message = { level = "allow" }
missing_errors_doc = { level = "allow" }
missing_panics_doc = { level = "allow" }
pattern_type_mismatch = { level = "allow" }
redundant_test_prefix = { level = "allow" }
ref_patterns = { level = "allow" }
renamed_function_params = { level = "allow" }
same_name_method = { level = "allow" }
shadow_reuse = { level = "allow" }
shadow_same = { level = "allow" }
shadow_unrelated = { level = "allow" }
tests_outside_test_module = { level = "allow" }
todo = { level = "allow" }
unchecked_time_subtraction = { level = "allow" }
unimplemented = { level = "allow" }
unreachable = { level = "allow" }
unwrap_in_result = { level = "allow" }
unwrap_used = { level = "allow" }
[workspace.lints.rust]
# Explicitly allowed lints
unused_crate_dependencies = { level = "allow" } # Too many false positives especially around benchmarking and binaries, which do not have their own `dependencies` section yet. Plus, we have cargo-machete checking unused deps.
unused_results = { level = "allow" } # We have Clippy lints to warn on unused `must_use` results. This is too pedantic as it complains on EVERY unused result.
# Lints which are allow-by-default but have been changed to "warn"
ambiguous_negative_literals = { level = "warn" }
closure_returning_async_block = { level = "warn" }
deref_into_dyn_supertrait = { level = "warn" }
impl_trait_redundant_captures = { level = "warn" }
let_underscore_drop = { level = "warn" }
macro_use_extern_crate = { level = "warn" }
missing_unsafe_on_extern = { level = "warn" }
redundant_imports = { level = "warn" }
redundant_lifetimes = { level = "warn" }
single_use_lifetimes = { level = "warn" }
tail_expr_drop_order = { level = "warn" }
trivial_numeric_casts = { level = "warn" }
unit_bindings = { level = "warn" }
unsafe_attr_outside_unsafe = { level = "warn" }
unsafe_op_in_unsafe_fn = { level = "warn" }
unstable_features = { level = "warn" }
unused_extern_crates = { level = "warn" }
unused_import_braces = { level = "warn" }
unused_lifetimes = { level = "warn" }
unused_macro_rules = { level = "warn" }
unused_qualifications = { level = "warn" }
# TODO: Address these allow-by-default Rustc lints at some point. To allow them, move them to the list of explicitly allowed lints. To enforce them, move them to the list of explicitly warned ones.
absolute_paths_not_starting_with_crate = { level = "allow" }
elided_lifetimes_in_paths = { level = "allow" }
ffi_unwind_calls = { level = "allow" }
impl_trait_overcaptures = { level = "allow" }
linker_messages = { level = "allow" }
missing_copy_implementations = { level = "allow" }
missing_debug_implementations = { level = "allow" }
missing_docs = { level = "allow" }
trivial_casts = { level = "allow" }
unreachable_pub = { level = "allow" }
unsafe_code = { level = "allow" }
variant_size_differences = { level = "allow" }
[workspace.dependencies]
# Internal
lbc-poc-sys = { default-features = false, package = "logos-blockchain-circuits-poc-sys", path = "./logos-blockchain-circuits-poc-sys" }
lbc-pol-sys = { default-features = false, package = "logos-blockchain-circuits-pol-sys", path = "./logos-blockchain-circuits-pol-sys" }
lbc-poq-sys = { default-features = false, package = "logos-blockchain-circuits-poq-sys", path = "./logos-blockchain-circuits-poq-sys" }
lbc-poc-sys = { default-features = false, package = "logos-blockchain-circuits-poc-sys", path = "./logos-blockchain-circuits-poc-sys" }
lbc-pol-sys = { default-features = false, package = "logos-blockchain-circuits-pol-sys", path = "./logos-blockchain-circuits-pol-sys" }
lbc-poq-sys = { default-features = false, package = "logos-blockchain-circuits-poq-sys", path = "./logos-blockchain-circuits-poq-sys" }
lbc-signature-sys = { default-features = false, package = "logos-blockchain-circuits-signature-sys", path = "./logos-blockchain-circuits-signature-sys" }
lbc-types = { default-features = false, package = "logos-blockchain-circuits-types", path = "./logos-blockchain-circuits-types" }
lbc-common = { default-features = false, package = "logos-blockchain-circuits-common", path = "./logos-blockchain-circuits-common" }
lbc-types = { default-features = false, package = "logos-blockchain-circuits-types", path = "./logos-blockchain-circuits-types" }
lbc-common = { default-features = false, package = "logos-blockchain-circuits-common", path = "./logos-blockchain-circuits-common" }
# External
flate2 = "^1"

View File

@ -9,5 +9,8 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
lbc-types = { workspace = true}
lbc-types = { workspace = true }

View File

@ -1,16 +1,20 @@
use std::path::Path;
use lbc_types::native::Error;
use std::path::Path;
pub fn as_null_terminated_string(
string: &str,
) -> Result<std::ffi::CString, Error> {
std::ffi::CString::new(string)
.map_err(|error| Error::InvalidInput(Some(format!("Could not convert string to CString: {error}"))))
pub fn as_null_terminated_string(string: &str) -> Result<std::ffi::CString, Error> {
std::ffi::CString::new(string).map_err(|error| {
Error::InvalidInput(Some(format!(
"Could not convert string to CString: {error}"
)))
})
}
pub fn path_as_null_terminated_string(
path: &Path,
) -> Result<std::ffi::CString, Error> {
let path = path.to_str().ok_or(Error::InvalidInput(Some(format!("Could not convert the path to a string: {}", path.display()))))?;
pub fn path_as_null_terminated_string(path: &Path) -> Result<std::ffi::CString, Error> {
let path = path.to_str().ok_or_else(|| {
Error::InvalidInput(Some(format!(
"Could not convert the path to a string: {}",
path.display()
)))
})?;
as_null_terminated_string(path)
}

View File

@ -9,6 +9,9 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-common = { workspace = true }

View File

@ -5,7 +5,6 @@ use ureq::http::Response;
static CIRCUIT_NAME: &str = "poc";
static LIB_VAR_NAME: &str = "LBC_POC_LIB_DIR";
fn get_artifact_name(version: &str, os: &str, arch: &str) -> String {
format!("logos-blockchain-circuits-v{version}-{os}-{arch}")
}
@ -29,17 +28,27 @@ fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
})
}
fn unpack_library(response: Response<Body>, version: &str, os: &str, arch: &str, output_dir: &Path) -> PathBuf {
fn unpack_library(
response: Response<Body>,
version: &str,
os: &str,
arch: &str,
output_dir: &Path,
) -> PathBuf {
let gz_decoder = flate2::read::GzDecoder::new(response.into_body().into_reader());
let mut archive = tar::Archive::new(gz_decoder);
archive.unpack(output_dir).expect("Failed to unpack the downloaded archive.");
archive
.unpack(output_dir)
.expect("Failed to unpack the downloaded archive.");
let unpacked_artifact_path = output_dir.join(get_artifact_name(version, os, arch));
let unpacked_library_directory = unpacked_artifact_path.join(CIRCUIT_NAME);
if !unpacked_library_directory.exists() {
panic!("Failed to find the unpacked library at {}.", unpacked_library_directory.display());
}
assert!(
unpacked_library_directory.is_dir(),
"Failed to find the unpacked library at {}",
unpacked_library_directory.display()
);
unpacked_library_directory
}
@ -50,9 +59,14 @@ fn provision_library() -> PathBuf {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let expected_library_directory = out_dir.join(get_artifact_name(version, &os, &arch)).join(CIRCUIT_NAME);
let expected_library_directory = out_dir
.join(get_artifact_name(version, &os, &arch))
.join(CIRCUIT_NAME);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
println!(
"Found an existing library at {}. Reusing it.",
expected_library_directory.display()
);
return expected_library_directory;
}
@ -66,21 +80,23 @@ fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
let lib_dir = std::env::var(LIB_VAR_NAME).map_or_else(
|_| provision_library(),
|lib_dir| {
println!("Using a library directory from {LIB_VAR_NAME}: {lib_dir}");
let lib_dir_path = PathBuf::from(lib_dir);
if !lib_dir_path.exists() {
panic!("The library directory specified in {LIB_VAR_NAME} at {} does not exist.", lib_dir_path.display());
}
assert!(
lib_dir_path.is_dir(),
"The library directory specified in {LIB_VAR_NAME} at {} does not exist.",
lib_dir_path.display()
);
lib_dir_path
}
).unwrap_or_else(|_| {
provision_library()
});
},
);
let lib_dir = lib_dir.to_str().expect("Failed to convert the library directory path to a string");
let lib_dir = lib_dir
.to_str()
.expect("Failed to convert the library directory path to a string");
println!("cargo:rustc-env={LIB_VAR_NAME}={lib_dir}");
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib=static={CIRCUIT_NAME}");

View File

@ -1,5 +1,5 @@
use std::ffi::c_char;
use lbc_types::ffi::{Bytes, Status, WitnessInput};
use std::ffi::c_char;
unsafe extern "C" {
pub fn poc_generate_witness(input: *const WitnessInput, output: *mut Bytes) -> Status;

View File

@ -1,4 +1,4 @@
mod ffi;
pub mod native;
pub use native::{generate_witness, generate_witness_from_files, PocWitnessInput};
pub use native::{PocWitnessInput, generate_witness, generate_witness_from_files};

View File

@ -1,9 +1,13 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_common::string::path_as_null_terminated_string;
use crate::ffi::{poc_generate_witness, poc_generate_witness_from_files};
use lbc_common::string::path_as_null_terminated_string;
use lbc_types::{
ffi,
native::{Bytes, Error},
};
use std::path::Path;
static RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POC_LIB_DIR"), "/witness_generator.dat"));
static RAW_CIRCUIT_DAT: &[u8] =
include_bytes!(concat!(env!("LBC_POC_LIB_DIR"), "/witness_generator.dat"));
pub struct PocDat;
impl<'dat> lbc_types::CircuitDat<'dat> for PocDat {
@ -12,74 +16,67 @@ impl<'dat> lbc_types::CircuitDat<'dat> for PocDat {
pub type PocWitnessInput<'dat> = lbc_types::CircuitWitnessInput<'dat, PocDat>;
pub fn generate_witness(
input: PocWitnessInput,
) -> Result<Bytes, Error> {
pub fn generate_witness(input: &PocWitnessInput) -> Result<Bytes, Error> {
let ffi_input_guard = input.as_ffi();
let ffi_input = ffi_input_guard.as_ref();
let mut ffi_output_bytes = ffi::Bytes::null();
let status = unsafe {
poc_generate_witness(
ffi_input as *const ffi::WitnessInput,
&mut ffi_output_bytes as *mut ffi::Bytes
)
};
// SAFETY: ffi_input is a valid pointer and ffi_output_bytes is a locally initialized null Bytes.
let status =
unsafe { poc_generate_witness(std::ptr::from_ref(ffi_input), &raw mut ffi_output_bytes) };
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
status.try_into().map(|()| Bytes::from(ffi_output_bytes))
}
pub fn generate_witness_from_files(
dat: &Path,
inputs: &Path,
output: &Path,
) -> Result<(), Error> {
pub fn generate_witness_from_files(dat: &Path, inputs: &Path, output: &Path) -> Result<(), Error> {
let c_dat = path_as_null_terminated_string(dat)?;
let c_inputs = path_as_null_terminated_string(inputs)?;
let c_output = path_as_null_terminated_string(output)?;
unsafe {
poc_generate_witness_from_files(
c_dat.as_ptr(),
c_inputs.as_ptr(),
c_output.as_ptr(),
)
}.try_into()
// SAFETY: c_dat, c_inputs, and c_output are valid null-terminated C strings for the duration of the call.
unsafe { poc_generate_witness_from_files(c_dat.as_ptr(), c_inputs.as_ptr(), c_output.as_ptr()) }
.try_into()
}
#[cfg(test)]
mod tests {
use super::{PocWitnessInput, generate_witness, generate_witness_from_files};
use std::path::PathBuf;
use std::sync::LazyLock;
use super::{generate_witness, generate_witness_from_files, PocWitnessInput};
static LIB_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
const ENV_VAR: &str = "LBC_POC_LIB_DIR";
PathBuf::from(
std::env::var(ENV_VAR)
.expect(format!("Environment variable '{ENV_VAR}' must be available, as provided by the build script.").as_str()),
std::env::var(ENV_VAR).unwrap_or_else(
|_| panic!("Environment variable '{ENV_VAR}' must be available, as provided by the build script."),
)
)
});
static INPUTS: LazyLock<PathBuf> = LazyLock::new(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json")
});
static INPUTS: LazyLock<PathBuf> =
LazyLock::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json"));
#[test]
fn test_generate_witness() {
let dat = LIB_DIR.join("witness_generator");
let witness_output_path = std::env::temp_dir().join("poc_test_witness.wtns");
generate_witness_from_files(&dat, &*INPUTS, &witness_output_path)
generate_witness_from_files(&dat, &INPUTS, &witness_output_path)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
.unwrap_or_else(|_| panic!("Failed to read {}.", INPUTS.display()));
let input = PocWitnessInput::new(inputs_json).expect("Failed to construct the input for the witness generator.");
let output = generate_witness(input).expect("generate_witness failed.");
let input = PocWitnessInput::new(inputs_json)
.expect("Failed to construct the input for the witness generator.");
let output = generate_witness(&input).expect("generate_witness failed.");
let expected = std::fs::read(&witness_output_path).expect(format!("Failed to read the generated witness from {}.", witness_output_path.display()).as_str());
let expected = std::fs::read(&witness_output_path).unwrap_or_else(|_| {
panic!(
"Failed to read the generated witness from {}.",
witness_output_path.display()
)
});
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -9,6 +9,9 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-common = { workspace = true }

View File

@ -5,7 +5,6 @@ use ureq::http::Response;
static CIRCUIT_NAME: &str = "pol";
static LIB_VAR_NAME: &str = "LBC_POL_LIB_DIR";
fn get_artifact_name(version: &str, os: &str, arch: &str) -> String {
format!("logos-blockchain-circuits-v{version}-{os}-{arch}")
}
@ -29,17 +28,27 @@ fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
})
}
fn unpack_library(response: Response<Body>, version: &str, os: &str, arch: &str, output_dir: &Path) -> PathBuf {
fn unpack_library(
response: Response<Body>,
version: &str,
os: &str,
arch: &str,
output_dir: &Path,
) -> PathBuf {
let gz_decoder = flate2::read::GzDecoder::new(response.into_body().into_reader());
let mut archive = tar::Archive::new(gz_decoder);
archive.unpack(output_dir).expect("Failed to unpack the downloaded archive.");
archive
.unpack(output_dir)
.expect("Failed to unpack the downloaded archive.");
let unpacked_artifact_path = output_dir.join(get_artifact_name(version, os, arch));
let unpacked_library_directory = unpacked_artifact_path.join(CIRCUIT_NAME);
if !unpacked_library_directory.exists() {
panic!("Failed to find the unpacked library at {}.", unpacked_library_directory.display());
}
assert!(
unpacked_library_directory.is_dir(),
"Failed to find the unpacked library at {}",
unpacked_library_directory.display()
);
unpacked_library_directory
}
@ -50,9 +59,14 @@ fn provision_library() -> PathBuf {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let expected_library_directory = out_dir.join(get_artifact_name(version, &os, &arch)).join(CIRCUIT_NAME);
let expected_library_directory = out_dir
.join(get_artifact_name(version, &os, &arch))
.join(CIRCUIT_NAME);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
println!(
"Found an existing library at {}. Reusing it.",
expected_library_directory.display()
);
return expected_library_directory;
}
@ -66,21 +80,23 @@ fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
let lib_dir = std::env::var(LIB_VAR_NAME).map_or_else(
|_| provision_library(),
|lib_dir| {
println!("Using a library directory from {LIB_VAR_NAME}: {lib_dir}");
let lib_dir_path = PathBuf::from(lib_dir);
if !lib_dir_path.exists() {
panic!("The library directory specified in {LIB_VAR_NAME} at {} does not exist.", lib_dir_path.display());
}
assert!(
lib_dir_path.is_dir(),
"The library directory specified in {LIB_VAR_NAME} at {} does not exist.",
lib_dir_path.display()
);
lib_dir_path
}
).unwrap_or_else(|_| {
provision_library()
});
},
);
let lib_dir = lib_dir.to_str().expect("Failed to convert the library directory path to a string");
let lib_dir = lib_dir
.to_str()
.expect("Failed to convert the library directory path to a string");
println!("cargo:rustc-env={LIB_VAR_NAME}={lib_dir}");
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib=static={CIRCUIT_NAME}");

View File

@ -1,5 +1,5 @@
use std::ffi::c_char;
use lbc_types::ffi::{Bytes, Status, WitnessInput};
use std::ffi::c_char;
unsafe extern "C" {
pub fn pol_generate_witness(input: *const WitnessInput, output: *mut Bytes) -> Status;

View File

@ -1,4 +1,4 @@
mod ffi;
pub mod native;
pub use native::{generate_witness, generate_witness_from_files, PolWitnessInput};
pub use native::{PolWitnessInput, generate_witness, generate_witness_from_files};

View File

@ -1,9 +1,13 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_common::string::path_as_null_terminated_string;
use crate::ffi::{pol_generate_witness, pol_generate_witness_from_files};
use lbc_common::string::path_as_null_terminated_string;
use lbc_types::{
ffi,
native::{Bytes, Error},
};
use std::path::Path;
static RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POL_LIB_DIR"), "/witness_generator.dat"));
static RAW_CIRCUIT_DAT: &[u8] =
include_bytes!(concat!(env!("LBC_POL_LIB_DIR"), "/witness_generator.dat"));
pub struct PolDat;
impl<'dat> lbc_types::CircuitDat<'dat> for PolDat {
@ -12,74 +16,67 @@ impl<'dat> lbc_types::CircuitDat<'dat> for PolDat {
pub type PolWitnessInput<'dat> = lbc_types::CircuitWitnessInput<'dat, PolDat>;
pub fn generate_witness(
input: PolWitnessInput,
) -> Result<Bytes, Error> {
pub fn generate_witness(input: &PolWitnessInput) -> Result<Bytes, Error> {
let ffi_input_guard = input.as_ffi();
let ffi_input = ffi_input_guard.as_ref();
let mut ffi_output_bytes = ffi::Bytes::null();
let status = unsafe {
pol_generate_witness(
ffi_input as *const ffi::WitnessInput,
&mut ffi_output_bytes as *mut ffi::Bytes
)
};
// SAFETY: ffi_input is a valid pointer and ffi_output_bytes is a locally initialized null Bytes.
let status =
unsafe { pol_generate_witness(std::ptr::from_ref(ffi_input), &raw mut ffi_output_bytes) };
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
status.try_into().map(|()| Bytes::from(ffi_output_bytes))
}
pub fn generate_witness_from_files(
dat: &Path,
inputs: &Path,
output: &Path,
) -> Result<(), Error> {
pub fn generate_witness_from_files(dat: &Path, inputs: &Path, output: &Path) -> Result<(), Error> {
let c_dat = path_as_null_terminated_string(dat)?;
let c_inputs = path_as_null_terminated_string(inputs)?;
let c_output = path_as_null_terminated_string(output)?;
unsafe {
pol_generate_witness_from_files(
c_dat.as_ptr(),
c_inputs.as_ptr(),
c_output.as_ptr(),
)
}.try_into()
// SAFETY: c_dat, c_inputs, and c_output are valid null-terminated C strings for the duration of the call.
unsafe { pol_generate_witness_from_files(c_dat.as_ptr(), c_inputs.as_ptr(), c_output.as_ptr()) }
.try_into()
}
#[cfg(test)]
mod tests {
use super::{PolWitnessInput, generate_witness, generate_witness_from_files};
use std::path::PathBuf;
use std::sync::LazyLock;
use super::{generate_witness, generate_witness_from_files, PolWitnessInput};
static LIB_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
const ENV_VAR: &str = "LBC_POL_LIB_DIR";
PathBuf::from(
std::env::var(ENV_VAR)
.expect(format!("Environment variable '{ENV_VAR}' must be available, as provided by the build script.").as_str()),
std::env::var(ENV_VAR).unwrap_or_else(
|_| panic!("Environment variable '{ENV_VAR}' must be available, as provided by the build script."),
)
)
});
static INPUTS: LazyLock<PathBuf> = LazyLock::new(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json")
});
static INPUTS: LazyLock<PathBuf> =
LazyLock::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json"));
#[test]
fn test_generate_witness() {
let dat = LIB_DIR.join("witness_generator");
let witness_output_path = std::env::temp_dir().join("pol_test_witness.wtns");
generate_witness_from_files(&dat, &*INPUTS, &witness_output_path)
generate_witness_from_files(&dat, &INPUTS, &witness_output_path)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
.unwrap_or_else(|_| panic!("Failed to read {}.", INPUTS.display()));
let input = PolWitnessInput::new(inputs_json).expect("Failed to construct the input for the witness generator.");
let output = generate_witness(input).expect("generate_witness failed.");
let input = PolWitnessInput::new(inputs_json)
.expect("Failed to construct the input for the witness generator.");
let output = generate_witness(&input).expect("generate_witness failed.");
let expected = std::fs::read(&witness_output_path).expect(format!("Failed to read the generated witness from {}.", witness_output_path.display()).as_str());
let expected = std::fs::read(&witness_output_path).unwrap_or_else(|_| {
panic!(
"Failed to read the generated witness from {}.",
witness_output_path.display()
)
});
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -9,6 +9,9 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-common = { workspace = true }

View File

@ -5,7 +5,6 @@ use ureq::http::Response;
static CIRCUIT_NAME: &str = "poq";
static LIB_VAR_NAME: &str = "LBC_POQ_LIB_DIR";
fn get_artifact_name(version: &str, os: &str, arch: &str) -> String {
format!("logos-blockchain-circuits-v{version}-{os}-{arch}")
}
@ -29,17 +28,27 @@ fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
})
}
fn unpack_library(response: Response<Body>, version: &str, os: &str, arch: &str, output_dir: &Path) -> PathBuf {
fn unpack_library(
response: Response<Body>,
version: &str,
os: &str,
arch: &str,
output_dir: &Path,
) -> PathBuf {
let gz_decoder = flate2::read::GzDecoder::new(response.into_body().into_reader());
let mut archive = tar::Archive::new(gz_decoder);
archive.unpack(output_dir).expect("Failed to unpack the downloaded archive.");
archive
.unpack(output_dir)
.expect("Failed to unpack the downloaded archive.");
let unpacked_artifact_path = output_dir.join(get_artifact_name(version, os, arch));
let unpacked_library_directory = unpacked_artifact_path.join(CIRCUIT_NAME);
if !unpacked_library_directory.exists() {
panic!("Failed to find the unpacked library at {}.", unpacked_library_directory.display());
}
assert!(
unpacked_library_directory.is_dir(),
"Failed to find the unpacked library at {}",
unpacked_library_directory.display()
);
unpacked_library_directory
}
@ -50,9 +59,14 @@ fn provision_library() -> PathBuf {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let expected_library_directory = out_dir.join(get_artifact_name(version, &os, &arch)).join(CIRCUIT_NAME);
let expected_library_directory = out_dir
.join(get_artifact_name(version, &os, &arch))
.join(CIRCUIT_NAME);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
println!(
"Found an existing library at {}. Reusing it.",
expected_library_directory.display()
);
return expected_library_directory;
}
@ -77,21 +91,23 @@ fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
let lib_dir = std::env::var(LIB_VAR_NAME).map_or_else(
|_| provision_library(),
|lib_dir| {
println!("Using a library directory from {LIB_VAR_NAME}: {lib_dir}");
let lib_dir_path = PathBuf::from(lib_dir);
if !lib_dir_path.exists() {
panic!("The library directory specified in {LIB_VAR_NAME} at {} does not exist.", lib_dir_path.display());
}
assert!(
lib_dir_path.is_dir(),
"The library directory specified in {LIB_VAR_NAME} at {} does not exist.",
lib_dir_path.display()
);
lib_dir_path
}
).unwrap_or_else(|_| {
provision_library()
});
},
);
let lib_dir = lib_dir.to_str().expect("Failed to convert the library directory path to a string");
let lib_dir = lib_dir
.to_str()
.expect("Failed to convert the library directory path to a string");
println!("cargo:rustc-env={LIB_VAR_NAME}={lib_dir}");
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib=static={CIRCUIT_NAME}");

View File

@ -1,5 +1,5 @@
use std::ffi::c_char;
use lbc_types::ffi::{Bytes, Status, WitnessInput};
use std::ffi::c_char;
unsafe extern "C" {
pub fn poq_generate_witness(input: *const WitnessInput, output: *mut Bytes) -> Status;

View File

@ -1,4 +1,4 @@
mod ffi;
pub mod native;
pub use native::{generate_witness, generate_witness_from_files, PoqWitnessInput};
pub use native::{PoqWitnessInput, generate_witness, generate_witness_from_files};

View File

@ -1,9 +1,13 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_common::string::path_as_null_terminated_string;
use crate::ffi::{poq_generate_witness, poq_generate_witness_from_files};
use lbc_common::string::path_as_null_terminated_string;
use lbc_types::{
ffi,
native::{Bytes, Error},
};
use std::path::Path;
static RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POQ_LIB_DIR"), "/witness_generator.dat"));
static RAW_CIRCUIT_DAT: &[u8] =
include_bytes!(concat!(env!("LBC_POQ_LIB_DIR"), "/witness_generator.dat"));
pub struct PoqDat;
impl<'dat> lbc_types::CircuitDat<'dat> for PoqDat {
@ -12,74 +16,67 @@ impl<'dat> lbc_types::CircuitDat<'dat> for PoqDat {
pub type PoqWitnessInput<'dat> = lbc_types::CircuitWitnessInput<'dat, PoqDat>;
pub fn generate_witness(
input: PoqWitnessInput,
) -> Result<Bytes, Error> {
pub fn generate_witness(input: &PoqWitnessInput) -> Result<Bytes, Error> {
let ffi_input_guard = input.as_ffi();
let ffi_input = ffi_input_guard.as_ref();
let mut ffi_output_bytes = ffi::Bytes::null();
let status = unsafe {
poq_generate_witness(
ffi_input as *const ffi::WitnessInput,
&mut ffi_output_bytes as *mut ffi::Bytes
)
};
// SAFETY: ffi_input is a valid pointer and ffi_output_bytes is a locally initialized null Bytes.
let status =
unsafe { poq_generate_witness(std::ptr::from_ref(ffi_input), &raw mut ffi_output_bytes) };
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
status.try_into().map(|()| Bytes::from(ffi_output_bytes))
}
pub fn generate_witness_from_files(
dat: &Path,
inputs: &Path,
output: &Path,
) -> Result<(), Error> {
pub fn generate_witness_from_files(dat: &Path, inputs: &Path, output: &Path) -> Result<(), Error> {
let c_dat = path_as_null_terminated_string(dat)?;
let c_inputs = path_as_null_terminated_string(inputs)?;
let c_output = path_as_null_terminated_string(output)?;
unsafe {
poq_generate_witness_from_files (
c_dat.as_ptr(),
c_inputs.as_ptr(),
c_output.as_ptr(),
)
}.try_into()
// SAFETY: c_dat, c_inputs, and c_output are valid null-terminated C strings for the duration of the call.
unsafe { poq_generate_witness_from_files(c_dat.as_ptr(), c_inputs.as_ptr(), c_output.as_ptr()) }
.try_into()
}
#[cfg(test)]
mod tests {
use super::{PoqWitnessInput, generate_witness, generate_witness_from_files};
use std::path::PathBuf;
use std::sync::LazyLock;
use super::{generate_witness, generate_witness_from_files, PoqWitnessInput};
static LIB_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
const ENV_VAR: &str = "LBC_POQ_LIB_DIR";
PathBuf::from(
std::env::var(ENV_VAR)
.expect(format!("Environment variable '{ENV_VAR}' must be available, as provided by the build script.").as_str()),
std::env::var(ENV_VAR).unwrap_or_else(
|_| panic!("Environment variable '{ENV_VAR}' must be available, as provided by the build script."),
)
)
});
static INPUTS: LazyLock<PathBuf> = LazyLock::new(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json")
});
static INPUTS: LazyLock<PathBuf> =
LazyLock::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json"));
#[test]
fn test_generate_witness() {
let dat = LIB_DIR.join("witness_generator");
let witness_output_path = std::env::temp_dir().join("poq_test_witness.wtns");
generate_witness_from_files(&dat, &*INPUTS, &witness_output_path)
generate_witness_from_files(&dat, &INPUTS, &witness_output_path)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
.unwrap_or_else(|_| panic!("Failed to read {}.", INPUTS.display()));
let input = PoqWitnessInput::new(inputs_json).expect("Failed to construct the input for the witness generator.");
let output = generate_witness(input).expect("generate_witness failed.");
let input = PoqWitnessInput::new(inputs_json)
.expect("Failed to construct the input for the witness generator.");
let output = generate_witness(&input).expect("generate_witness failed.");
let expected = std::fs::read(&witness_output_path).expect(format!("Failed to read the generated witness from {}.", witness_output_path.display()).as_str());
let expected = std::fs::read(&witness_output_path).unwrap_or_else(|_| {
panic!(
"Failed to read the generated witness from {}.",
witness_output_path.display()
)
});
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -9,6 +9,9 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-common = { workspace = true }

View File

@ -5,7 +5,6 @@ use ureq::http::Response;
static CIRCUIT_NAME: &str = "signature";
static LIB_VAR_NAME: &str = "LBC_SIGNATURE_LIB_DIR";
fn get_artifact_name(version: &str, os: &str, arch: &str) -> String {
format!("logos-blockchain-circuits-v{version}-{os}-{arch}")
}
@ -29,17 +28,27 @@ fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
})
}
fn unpack_library(response: Response<Body>, version: &str, os: &str, arch: &str, output_dir: &Path) -> PathBuf {
fn unpack_library(
response: Response<Body>,
version: &str,
os: &str,
arch: &str,
output_dir: &Path,
) -> PathBuf {
let gz_decoder = flate2::read::GzDecoder::new(response.into_body().into_reader());
let mut archive = tar::Archive::new(gz_decoder);
archive.unpack(output_dir).expect("Failed to unpack the downloaded archive.");
archive
.unpack(output_dir)
.expect("Failed to unpack the downloaded archive.");
let unpacked_artifact_path = output_dir.join(get_artifact_name(version, os, arch));
let unpacked_library_directory = unpacked_artifact_path.join(CIRCUIT_NAME);
if !unpacked_library_directory.exists() {
panic!("Failed to find the unpacked library at {}.", unpacked_library_directory.display());
}
assert!(
unpacked_library_directory.is_dir(),
"Failed to find the unpacked library at {}",
unpacked_library_directory.display()
);
unpacked_library_directory
}
@ -50,9 +59,14 @@ fn provision_library() -> PathBuf {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let expected_library_directory = out_dir.join(get_artifact_name(version, &os, &arch)).join(CIRCUIT_NAME);
let expected_library_directory = out_dir
.join(get_artifact_name(version, &os, &arch))
.join(CIRCUIT_NAME);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
println!(
"Found an existing library at {}. Reusing it.",
expected_library_directory.display()
);
return expected_library_directory;
}
@ -66,21 +80,23 @@ fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
let lib_dir = std::env::var(LIB_VAR_NAME).map_or_else(
|_| provision_library(),
|lib_dir| {
println!("Using a library directory from {LIB_VAR_NAME}: {lib_dir}");
let lib_dir_path = PathBuf::from(lib_dir);
if !lib_dir_path.exists() {
panic!("The library directory specified in {LIB_VAR_NAME} at {} does not exist.", lib_dir_path.display());
}
assert!(
lib_dir_path.is_dir(),
"The library directory specified in {LIB_VAR_NAME} at {} does not exist.",
lib_dir_path.display()
);
lib_dir_path
}
).unwrap_or_else(|_| {
provision_library()
});
},
);
let lib_dir = lib_dir.to_str().expect("Failed to convert the library directory path to a string");
let lib_dir = lib_dir
.to_str()
.expect("Failed to convert the library directory path to a string");
println!("cargo:rustc-env={LIB_VAR_NAME}={lib_dir}");
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib=static={CIRCUIT_NAME}");

View File

@ -1,5 +1,5 @@
use std::ffi::c_char;
use lbc_types::ffi::{Bytes, Status, WitnessInput};
use std::ffi::c_char;
unsafe extern "C" {
pub fn signature_generate_witness(input: *const WitnessInput, output: *mut Bytes) -> Status;

View File

@ -1,4 +1,4 @@
mod ffi;
pub mod native;
pub use native::{generate_witness, generate_witness_from_files, SignatureWitnessInput};
pub use native::{SignatureWitnessInput, generate_witness, generate_witness_from_files};

View File

@ -1,9 +1,15 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_common::string::path_as_null_terminated_string;
use crate::ffi::{signature_generate_witness, signature_generate_witness_from_files};
use lbc_common::string::path_as_null_terminated_string;
use lbc_types::{
ffi,
native::{Bytes, Error},
};
use std::path::Path;
static RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_SIGNATURE_LIB_DIR"), "/witness_generator.dat"));
static RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(
env!("LBC_SIGNATURE_LIB_DIR"),
"/witness_generator.dat"
));
pub struct SignatureDat;
impl<'dat> lbc_types::CircuitDat<'dat> for SignatureDat {
@ -12,74 +18,70 @@ impl<'dat> lbc_types::CircuitDat<'dat> for SignatureDat {
pub type SignatureWitnessInput<'dat> = lbc_types::CircuitWitnessInput<'dat, SignatureDat>;
pub fn generate_witness(
input: SignatureWitnessInput,
) -> Result<Bytes, Error> {
pub fn generate_witness(input: &SignatureWitnessInput) -> Result<Bytes, Error> {
let ffi_input_guard = input.as_ffi();
let ffi_input = ffi_input_guard.as_ref();
let mut ffi_output_bytes = ffi::Bytes::null();
// SAFETY: ffi_input is a valid pointer and ffi_output_bytes is a locally initialized null Bytes.
let status = unsafe {
signature_generate_witness(
ffi_input as *const ffi::WitnessInput,
&mut ffi_output_bytes as *mut ffi::Bytes
)
signature_generate_witness(std::ptr::from_ref(ffi_input), &raw mut ffi_output_bytes)
};
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
status.try_into().map(|()| Bytes::from(ffi_output_bytes))
}
pub fn generate_witness_from_files(
dat: &Path,
inputs: &Path,
output: &Path,
) -> Result<(), Error> {
pub fn generate_witness_from_files(dat: &Path, inputs: &Path, output: &Path) -> Result<(), Error> {
let c_dat = path_as_null_terminated_string(dat)?;
let c_inputs = path_as_null_terminated_string(inputs)?;
let c_output = path_as_null_terminated_string(output)?;
// SAFETY: c_dat, c_inputs, and c_output are valid null-terminated C strings for the duration of the call.
unsafe {
signature_generate_witness_from_files(
c_dat.as_ptr(),
c_inputs.as_ptr(),
c_output.as_ptr(),
)
}.try_into()
signature_generate_witness_from_files(c_dat.as_ptr(), c_inputs.as_ptr(), c_output.as_ptr())
}
.try_into()
}
#[cfg(test)]
mod tests {
use super::{SignatureWitnessInput, generate_witness, generate_witness_from_files};
use std::path::PathBuf;
use std::sync::LazyLock;
use super::{generate_witness, generate_witness_from_files, SignatureWitnessInput};
static LIB_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
const ENV_VAR: &str = "LBC_SIGNATURE_LIB_DIR";
PathBuf::from(
std::env::var(ENV_VAR)
.expect(format!("Environment variable '{ENV_VAR}' must be available, as provided by the build script.").as_str()),
std::env::var(ENV_VAR).unwrap_or_else(
|_| panic!("Environment variable '{ENV_VAR}' must be available, as provided by the build script."),
)
)
});
static INPUTS: LazyLock<PathBuf> = LazyLock::new(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json")
});
static INPUTS: LazyLock<PathBuf> =
LazyLock::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("sample.input.json"));
#[test]
fn test_generate_witness() {
let dat = LIB_DIR.join("witness_generator");
let witness_output_path = std::env::temp_dir().join("signature_test_witness.wtns");
generate_witness_from_files(&dat, &*INPUTS, &witness_output_path)
generate_witness_from_files(&dat, &INPUTS, &witness_output_path)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
.unwrap_or_else(|_| panic!("Failed to read {}.", INPUTS.display()));
let input = SignatureWitnessInput::new(inputs_json).expect("Failed to construct the input for the witness generator.");
let output = generate_witness(input).expect("generate_witness failed.");
let input = SignatureWitnessInput::new(inputs_json)
.expect("Failed to construct the input for the witness generator.");
let output = generate_witness(&input).expect("generate_witness failed.");
let expected = std::fs::read(&witness_output_path).expect(format!("Failed to read the generated witness from {}.", witness_output_path.display()).as_str());
let expected = std::fs::read(&witness_output_path).unwrap_or_else(|_| {
panic!(
"Failed to read the generated witness from {}.",
witness_output_path.display()
)
});
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -9,5 +9,8 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[lints]
workspace = true
[dependencies]
libc = { workspace = true }

View File

@ -9,7 +9,7 @@ mod inner {
}
impl<T> inner::Buffer<*const T> {
pub fn null() -> Self {
pub const fn null() -> Self {
Self {
data: std::ptr::null(),
size: 0,
@ -18,7 +18,7 @@ impl<T> inner::Buffer<*const T> {
}
impl<T> inner::Buffer<*mut T> {
pub fn null() -> Self {
pub const fn null() -> Self {
Self {
data: std::ptr::null_mut(),
size: 0,
@ -53,9 +53,11 @@ pub unsafe fn free_bytes(bytes: *mut Bytes) {
return;
}
// SAFETY: `bytes` is non-null (checked above).
let bytes = unsafe { &mut *bytes };
if !bytes.data.is_null() {
unsafe { free(bytes.data as *mut libc::c_void) };
// SAFETY: `bytes.data` is non-null (checked above).
unsafe { free(bytes.data.cast::<libc::c_void>()) };
}
bytes.data = std::ptr::null_mut();
bytes.size = 0;

View File

@ -3,10 +3,10 @@
//! These types map directly to the C header structs and are used at the FFI boundary. Prefer the
//! wrappers in [`crate::native`] for ordinary Rust code.
pub mod status;
pub mod bytes;
pub mod status;
pub mod witness_input;
pub use status::Status;
pub use bytes::{Bytes, ConstBytes, free_bytes};
pub use status::Status;
pub use witness_input::WitnessInput;

View File

@ -10,13 +10,13 @@ impl Code {
pub const DYN_ERROR: Self = Self(1);
pub const INVALID_INPUT: Self = Self(2);
pub const OUT_OF_MEMORY: Self = Self(3);
}
impl Code {
#[must_use]
pub fn is_ok(&self) -> bool {
self == &Self::OK
}
#[must_use]
pub fn is_error(&self) -> bool {
!self.is_ok()
}
@ -30,22 +30,26 @@ pub struct Status {
}
impl Status {
pub fn ok() -> Self {
Status {
#[must_use]
pub const fn ok() -> Self {
Self {
code: Code::OK,
message: [0; 256],
}
}
#[must_use]
pub fn is_ok(&self) -> bool {
self.code.is_ok()
}
#[must_use]
pub fn is_error(&self) -> bool {
self.code.is_error()
}
pub fn has_message(&self) -> bool {
#[must_use]
pub const fn has_message(&self) -> bool {
self.message[0] != 0
}
}

View File

@ -1,5 +1,5 @@
use std::ffi::c_char;
use crate::ffi::ConstBytes;
use std::ffi::c_char;
/// Input to a witness generator function.
///

View File

@ -4,7 +4,7 @@
//! [`native`] module re-exposes those through idiomatic Rust types that own their memory and
//! convert FFI return values into [`Result`]s.
pub mod native;
pub mod ffi;
pub mod native;
pub use native::{CircuitDat, CircuitWitnessInput};

View File

@ -41,11 +41,12 @@ impl From<ffi::Bytes> for Bytes {
let vec = if ffi_value.size == 0 || ffi_value.data.is_null() {
Vec::new()
} else {
unsafe {
std::slice::from_raw_parts(ffi_value.data, ffi_value.size).to_vec()
}
// SAFETY: `ffi_value.data` is non-null and `ffi_value.size > 0` (checked above),
// pointing to a valid C-allocated buffer of at least `size` bytes.
unsafe { std::slice::from_raw_parts(ffi_value.data, ffi_value.size).to_vec() }
};
unsafe { ffi::free_bytes(&mut ffi_value) };
// SAFETY: `ffi_value` is a local variable, so the raw pointer is valid for this call.
unsafe { ffi::free_bytes(&raw mut ffi_value) };
Self(vec)
}
}

View File

@ -1,5 +1,6 @@
use crate::native::{Error, WitnessInput};
use std::marker::PhantomData;
use std::ops::Deref;
use crate::native::{WitnessInput, Error};
pub trait CircuitDat<'dat> {
const DAT: &'dat [u8];
@ -7,13 +8,16 @@ pub trait CircuitDat<'dat> {
pub struct CircuitWitnessInput<'dat, Dat> {
inner: WitnessInput<'dat>,
_phantom: std::marker::PhantomData<Dat>
_phantom: PhantomData<Dat>,
}
impl<'dat, Dat: CircuitDat<'dat>> CircuitWitnessInput<'dat, Dat> {
pub fn new(inputs_json: String) -> Result<Self, Error> {
let inner = WitnessInput::new(Dat::DAT, inputs_json)?;
Ok(Self { inner, _phantom: Default::default() })
Ok(Self {
inner,
_phantom: PhantomData,
})
}
}

View File

@ -3,11 +3,11 @@
//! Use them in preference to the types in [`crate::ffi`].
pub mod bytes;
pub mod circuit_witness_input;
pub mod status;
pub mod witness_input;
pub mod circuit_witness_input;
pub use bytes::Bytes;
pub use status::{Result, Error};
pub use witness_input::WitnessInput;
pub use circuit_witness_input::{CircuitDat, CircuitWitnessInput};
pub use status::{Error, Result};
pub use witness_input::WitnessInput;

View File

@ -1,6 +1,6 @@
use crate::ffi::status::Code as FfiStatusCode;
use std::ffi::CStr;
use std::fmt::Display;
use crate::ffi::status::Code as FfiStatusCode;
pub type Result<T> = std::result::Result<T, Error>;
@ -17,9 +17,9 @@ impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (kind, message) = match self {
Error::InvalidInput(msg) => ("Invalid input", msg),
Error::OutOfMemory(msg) => ("Out of memory", msg),
Error::Other(msg) => ("Other error", msg),
Self::InvalidInput(msg) => ("Invalid input", msg),
Self::OutOfMemory(msg) => ("Out of memory", msg),
Self::Other(msg) => ("Other error", msg),
};
match message {
Some(message) => write!(f, "{kind}: {message}"),
@ -32,21 +32,21 @@ impl TryFrom<crate::ffi::Status> for () {
type Error = Error;
fn try_from(status: crate::ffi::Status) -> Result<()> {
let message: Option<String> = if status.has_message() {
let status_message = unsafe {
CStr::from_ptr(status.message.as_ptr())
};
Some(status_message.to_string_lossy().into_owned())
} else {
None
};
let message: Option<String> = status.has_message().then(|| {
// SAFETY: `status.message` is non-empty (checked by `has_message()`) and null-terminated as guaranteed by the C API.
let status_message = unsafe { CStr::from_ptr(status.message.as_ptr()) };
status_message.to_string_lossy().into_owned()
});
match status.code {
FfiStatusCode::OK => Ok(()),
FfiStatusCode::DYN_ERROR => Err(Error::Other(message)),
FfiStatusCode::INVALID_INPUT => Err(Error::InvalidInput(message)),
FfiStatusCode::OUT_OF_MEMORY => Err(Error::OutOfMemory(message)),
other => Err(Error::Other(Some(format!("Unknown status code: {}", other.0))))
other => Err(Error::Other(Some(format!(
"Unknown status code: {}",
other.0
)))),
}
}
}

View File

@ -1,6 +1,6 @@
use std::ffi::CString;
use crate::ffi;
use crate::native::Error;
use std::ffi::CString;
/// Input for witness generators
pub struct WitnessInput<'dat> {
@ -12,15 +12,16 @@ pub struct WitnessInput<'dat> {
impl<'dat> WitnessInput<'dat> {
pub fn new(dat: &'dat [u8], inputs_json: String) -> Result<Self, Error> {
let inputs_json = CString::new(inputs_json).map_err(
|error| Error::InvalidInput(Some(
format!("The parameter inputs_json could not be converted to CString: {}", error)
))
)?;
let inputs_json = CString::new(inputs_json).map_err(|error| {
Error::InvalidInput(Some(format!(
"The parameter inputs_json could not be converted to C string: {error}"
)))
})?;
Ok(Self { dat, inputs_json })
}
/// Borrows this value as a temporary FFI-compatible view.
#[must_use]
pub fn as_ffi(&'_ self) -> WitnessInputFfiGuard<'_> {
WitnessInputFfiGuard::new(self)
}
@ -34,7 +35,10 @@ pub struct WitnessInputFfiGuard<'dat> {
impl<'dat> WitnessInputFfiGuard<'dat> {
fn new(inner: &'dat WitnessInput) -> Self {
let dat = ffi::ConstBytes { data: inner.dat.as_ptr(), size: inner.dat.len() };
let dat = ffi::ConstBytes {
data: inner.dat.as_ptr(),
size: inner.dat.len(),
};
let inputs_json = inner.inputs_json.as_ptr();
let ffi = ffi::WitnessInput { dat, inputs_json };
Self {
@ -44,7 +48,7 @@ impl<'dat> WitnessInputFfiGuard<'dat> {
}
}
impl<'dat> AsRef<ffi::WitnessInput> for WitnessInputFfiGuard<'dat> {
impl AsRef<ffi::WitnessInput> for WitnessInputFfiGuard<'_> {
fn as_ref(&self) -> &ffi::WitnessInput {
&self.ffi
}