Implement poc, pol and signature circuits. Abstract utils into their own crate. Abstract CircuitWitnessInput.

This commit is contained in:
Alejandro Cabeza Romero 2026-05-05 16:24:21 +02:00
parent 3ae7ff2258
commit 9af51ddf7e
No known key found for this signature in database
GPG Key ID: DA3D14AE478030FD
24 changed files with 765 additions and 55 deletions

41
rust/Cargo.lock generated
View File

@ -151,12 +151,46 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "logos-blockchain-circuits-poc-sys"
version = "0.4.2"
dependencies = [
"flate2",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-utils",
"tar",
"ureq",
]
[[package]]
name = "logos-blockchain-circuits-pol-sys"
version = "0.4.2"
dependencies = [
"flate2",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-utils",
"tar",
"ureq",
]
[[package]]
name = "logos-blockchain-circuits-poq-sys"
version = "0.4.2"
dependencies = [
"flate2",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-utils",
"tar",
"ureq",
]
[[package]]
name = "logos-blockchain-circuits-signature-sys"
version = "0.4.2"
dependencies = [
"flate2",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-utils",
"tar",
"ureq",
]
@ -168,6 +202,13 @@ dependencies = [
"libc",
]
[[package]]
name = "logos-blockchain-circuits-utils"
version = "0.4.2"
dependencies = [
"logos-blockchain-circuits-types",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"

View File

@ -10,15 +10,23 @@ version = "0.4.2"
[workspace]
members = [
"logos-blockchain-circuits-poc-sys",
"logos-blockchain-circuits-pol-sys",
"logos-blockchain-circuits-poq-sys",
"logos-blockchain-circuits-types"
"logos-blockchain-circuits-signature-sys",
"logos-blockchain-circuits-types",
"logos-blockchain-circuits-utils"
]
resolver = "3"
[workspace.dependencies]
# Internal
lbc-poq-sys = { default-features = false, package = "logos-blockchain-circuits-poq-sys", path = "./logos-blockchain-circuits-poq-sys" }
lbc-types = { default-features = false, package = "logos-blockchain-circuits-types", path = "./logos-blockchain-circuits-types" }
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-utils = { default-features = false, package = "logos-blockchain-circuits-utils", path = "./logos-blockchain-circuits-utils" }
# External
flate2 = "1"

View File

@ -0,0 +1,19 @@
[package]
name = "logos-blockchain-circuits-poc-sys"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-utils = { workspace = true }
[build-dependencies]
flate2 = { workspace = true }
tar = { workspace = true }
ureq = { workspace = true }

View File

@ -0,0 +1,89 @@
use std::path::{Path, PathBuf};
use ureq::Body;
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}")
}
fn get_artifact_url(version: &str, os: &str, arch: &str) -> String {
let artifact = get_artifact_name(version, os, arch);
let artifact_tar_gz = format!("{artifact}.tar.gz");
format!(
"https://github.com/logos-blockchain/logos-blockchain-circuits/releases/download/v{version}/{artifact_tar_gz}"
)
}
fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
let url = get_artifact_url(version, os, arch);
// TODO: Verify checksum.
ureq::get(&url).call().unwrap_or_else(|error| {
panic!(
"Failed to download a prebuilt library for {os}-{arch} v{version}: {error}. \
Set {LIB_VAR_NAME} to point to a local build instead."
)
})
}
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.");
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());
}
unpacked_library_directory
}
fn provision_library() -> PathBuf {
let version = env!("CARGO_PKG_VERSION");
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
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);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
return expected_library_directory;
}
let response = fetch_library(version, &os, &arch);
unpack_library(response, version, &os, &arch, &out_dir)
}
fn main() {
println!("cargo:rerun-if-env-changed={LIB_VAR_NAME}");
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
|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());
}
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");
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}");
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=gmp");
}

View File

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

View File

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

View File

@ -0,0 +1,87 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_types::inputs::CircuitDat;
use lbc_utils::string::path_as_null_terminated_string;
use crate::ffi::{poc_generate_witness, poc_generate_witness_from_files};
pub(crate) const RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POC_LIB_DIR"), "/witness_generator.dat"));
pub struct PocDat;
impl CircuitDat for PocDat {
const DAT: &'static [u8] = RAW_CIRCUIT_DAT;
}
pub type PocWitnessInput<'a> = lbc_types::inputs::CircuitWitnessInput<'a, PocDat>;
pub fn generate_witness(
input: PocWitnessInput,
) -> Result<Bytes, Error> {
let input: lbc_types::WitnessInput = input.into();
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
)
};
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
}
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()
}
#[cfg(test)]
mod tests {
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()),
)
});
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)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
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());
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -0,0 +1,19 @@
[package]
name = "logos-blockchain-circuits-pol-sys"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-utils = { workspace = true }
[build-dependencies]
flate2 = { workspace = true }
tar = { workspace = true }
ureq = { workspace = true }

View File

@ -0,0 +1,89 @@
use std::path::{Path, PathBuf};
use ureq::Body;
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}")
}
fn get_artifact_url(version: &str, os: &str, arch: &str) -> String {
let artifact = get_artifact_name(version, os, arch);
let artifact_tar_gz = format!("{artifact}.tar.gz");
format!(
"https://github.com/logos-blockchain/logos-blockchain-circuits/releases/download/v{version}/{artifact_tar_gz}"
)
}
fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
let url = get_artifact_url(version, os, arch);
// TODO: Verify checksum.
ureq::get(&url).call().unwrap_or_else(|error| {
panic!(
"Failed to download a prebuilt library for {os}-{arch} v{version}: {error}. \
Set {LIB_VAR_NAME} to point to a local build instead."
)
})
}
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.");
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());
}
unpacked_library_directory
}
fn provision_library() -> PathBuf {
let version = env!("CARGO_PKG_VERSION");
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
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);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
return expected_library_directory;
}
let response = fetch_library(version, &os, &arch);
unpack_library(response, version, &os, &arch, &out_dir)
}
fn main() {
println!("cargo:rerun-if-env-changed={LIB_VAR_NAME}");
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
|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());
}
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");
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}");
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=gmp");
}

View File

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

View File

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

View File

@ -0,0 +1,87 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_types::inputs::CircuitDat;
use lbc_utils::string::path_as_null_terminated_string;
use crate::ffi::{pol_generate_witness, pol_generate_witness_from_files};
pub(crate) const RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POL_LIB_DIR"), "/witness_generator.dat"));
pub struct PolDat;
impl CircuitDat for PolDat {
const DAT: &'static [u8] = RAW_CIRCUIT_DAT;
}
pub type PolWitnessInput<'a> = lbc_types::inputs::CircuitWitnessInput<'a, PolDat>;
pub fn generate_witness(
input: PolWitnessInput,
) -> Result<Bytes, Error> {
let input: lbc_types::WitnessInput = input.into();
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
)
};
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
}
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()
}
#[cfg(test)]
mod tests {
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()),
)
});
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)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
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());
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -11,6 +11,7 @@ version.workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-utils = { workspace = true }
[build-dependencies]
flate2 = { workspace = true }

View File

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

View File

@ -1,58 +1,20 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_types::inputs::CircuitDat;
use lbc_utils::string::path_as_null_terminated_string;
use crate::ffi::{poq_generate_witness, poq_generate_witness_from_files};
mod helpers {
use std::path::Path;
use lbc_types::native::Error;
pub(crate) const RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_POQ_LIB_DIR"), "/witness_generator.dat"));
pub fn into_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()))))?;
into_null_terminated_string(path)
}
pub struct PoqDat;
impl CircuitDat for PoqDat {
const DAT: &'static [u8] = RAW_CIRCUIT_DAT;
}
pub mod inputs {
use lbc_types::native::Error;
use lbc_types::WitnessInput;
pub(crate) static DAT: &[u8] = include_bytes!(concat!(env!("LBC_POQ_LIB_DIR"), "/witness_generator.dat"));
pub struct PoqWitnessInput<'a> {
inner: WitnessInput<'a>
}
impl<'a> PoqWitnessInput<'a> {
pub fn new(inputs_json: String) -> Result<Self, Error> {
let inner = WitnessInput::new(DAT, inputs_json)?;
Ok(Self { inner })
}
}
impl<'a> From<PoqWitnessInput<'a>> for WitnessInput<'a> {
fn from(value: PoqWitnessInput<'a>) -> Self {
value.inner
}
}
impl<'a> From<WitnessInput<'a>> for PoqWitnessInput<'a> {
fn from(value: WitnessInput<'a>) -> Self {
Self { inner: value }
}
}
}
pub type PoqWitnessInput<'a> = lbc_types::inputs::CircuitWitnessInput<'a, PoqDat>;
pub fn generate_witness(
input: inputs::PoqWitnessInput,
input: PoqWitnessInput,
) -> Result<Bytes, Error> {
let input: lbc_types::WitnessInput = input.into();
let ffi_input_guard = input.as_ffi();
@ -75,9 +37,9 @@ pub fn generate_witness_from_files(
inputs: &Path,
output: &Path,
) -> Result<(), Error> {
let c_dat = helpers::path_as_null_terminated_string(dat)?;
let c_inputs = helpers::path_as_null_terminated_string(inputs)?;
let c_output = helpers::path_as_null_terminated_string(output)?;
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 (
@ -92,7 +54,7 @@ pub fn generate_witness_from_files(
mod tests {
use std::path::PathBuf;
use std::sync::LazyLock;
use super::{generate_witness, generate_witness_from_files, inputs};
use super::{generate_witness, generate_witness_from_files, PoqWitnessInput};
static LIB_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
const ENV_VAR: &str = "LBC_POQ_LIB_DIR";
@ -116,7 +78,7 @@ mod tests {
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
let input = inputs::PoqWitnessInput::new(inputs_json).expect("Failed to construct the input for the witness generator.");
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());

View File

@ -0,0 +1,19 @@
[package]
name = "logos-blockchain-circuits-signature-sys"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
lbc-types = { workspace = true }
lbc-utils = { workspace = true }
[build-dependencies]
flate2 = { workspace = true }
tar = { workspace = true }
ureq = { workspace = true }

View File

@ -0,0 +1,89 @@
use std::path::{Path, PathBuf};
use ureq::Body;
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}")
}
fn get_artifact_url(version: &str, os: &str, arch: &str) -> String {
let artifact = get_artifact_name(version, os, arch);
let artifact_tar_gz = format!("{artifact}.tar.gz");
format!(
"https://github.com/logos-blockchain/logos-blockchain-circuits/releases/download/v{version}/{artifact_tar_gz}"
)
}
fn fetch_library(version: &str, os: &str, arch: &str) -> Response<Body> {
let url = get_artifact_url(version, os, arch);
// TODO: Verify checksum.
ureq::get(&url).call().unwrap_or_else(|error| {
panic!(
"Failed to download a prebuilt library for {os}-{arch} v{version}: {error}. \
Set {LIB_VAR_NAME} to point to a local build instead."
)
})
}
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.");
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());
}
unpacked_library_directory
}
fn provision_library() -> PathBuf {
let version = env!("CARGO_PKG_VERSION");
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
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);
if expected_library_directory.exists() {
println!("Found an existing library at {}. Reusing it.", expected_library_directory.display());
return expected_library_directory;
}
let response = fetch_library(version, &os, &arch);
unpack_library(response, version, &os, &arch, &out_dir)
}
fn main() {
println!("cargo:rerun-if-env-changed={LIB_VAR_NAME}");
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=build.rs");
let lib_dir = std::env::var(LIB_VAR_NAME).map(
|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());
}
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");
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}");
println!("cargo:rustc-link-lib=stdc++");
println!("cargo:rustc-link-lib=gmp");
}

View File

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

View File

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

View File

@ -0,0 +1,87 @@
use std::path::Path;
use lbc_types::{ffi, native::{Bytes, Error}};
use lbc_types::inputs::CircuitDat;
use lbc_utils::string::path_as_null_terminated_string;
use crate::ffi::{signature_generate_witness, signature_generate_witness_from_files};
pub(crate) const RAW_CIRCUIT_DAT: &[u8] = include_bytes!(concat!(env!("LBC_SIGNATURE_LIB_DIR"), "/witness_generator.dat"));
pub struct SignatureDat;
impl CircuitDat for SignatureDat {
const DAT: &'static [u8] = RAW_CIRCUIT_DAT;
}
pub type SignatureWitnessInput<'a> = lbc_types::inputs::CircuitWitnessInput<'a, SignatureDat>;
pub fn generate_witness(
input: SignatureWitnessInput,
) -> Result<Bytes, Error> {
let input: lbc_types::WitnessInput = input.into();
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 {
signature_generate_witness(
ffi_input as *const ffi::WitnessInput,
&mut ffi_output_bytes as *mut ffi::Bytes
)
};
status.try_into().map(|()| { Bytes::from(ffi_output_bytes) })
}
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 {
signature_generate_witness_from_files(
c_dat.as_ptr(),
c_inputs.as_ptr(),
c_output.as_ptr(),
)
}.try_into()
}
#[cfg(test)]
mod tests {
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()),
)
});
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)
.expect("generate_witness_from_files failed.");
let inputs_json = std::fs::read_to_string(&*INPUTS)
.expect(format!("Failed to read {}.", INPUTS.display()).as_str());
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());
assert_eq!(output.as_slice(), expected.as_slice());
}
}

View File

@ -8,3 +8,38 @@ pub mod native;
pub mod ffi;
pub use native::witness_input::WitnessInput;
pub mod inputs {
use crate::native::Error;
use crate::WitnessInput;
pub trait CircuitDat {
const DAT: &'static [u8];
}
// TODO: Remove in favour on native::WitnessInput.
pub struct CircuitWitnessInput<'input, Dat> {
inner: WitnessInput<'input>,
_phantom: std::marker::PhantomData<Dat>
}
impl<'input, Dat: CircuitDat> CircuitWitnessInput<'input, Dat> {
pub fn new(inputs_json: String) -> Result<Self, Error> {
let inner = WitnessInput::new(Dat::DAT, inputs_json)?;
Ok(Self { inner, _phantom: Default::default() })
}
}
impl<'input, Dat> From<CircuitWitnessInput<'input, Dat>> for WitnessInput<'input> {
fn from(value: CircuitWitnessInput<'input, Dat>) -> Self {
value.inner
}
}
impl<'input, Dat> From<WitnessInput<'input>> for CircuitWitnessInput<'input, Dat> {
fn from(value: WitnessInput<'input>) -> Self {
Self { inner: value, _phantom: Default::default() }
}
}
}

View File

@ -0,0 +1,13 @@
[package]
name = "logos-blockchain-circuits-utils"
categories.workspace = true
description.workspace = true
edition.workspace = true
keywords.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true
[dependencies]
lbc-types = { workspace = true}

View File

@ -0,0 +1 @@
pub mod string;

View File

@ -0,0 +1,16 @@
use std::path::Path;
use lbc_types::native::Error;
pub fn into_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()))))?;
into_null_terminated_string(path)
}