diff --git a/.gitignore b/.gitignore index 370b496..6d15fee 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ inc/blst_aux.h* .vscode/ .clang-format *bindings/*/*.so +*bindings/rust/target *bindings/csharp/*.exe *bindings/csharp/*.dll __pycache__ diff --git a/bindings/rust/Cargo.lock b/bindings/rust/Cargo.lock new file mode 100644 index 0000000..a417c39 --- /dev/null +++ b/bindings/rust/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "c-kzg" +version = "0.1.0" +dependencies = [ + "hex", + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 0000000..ccf5151 --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "c-kzg" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc = "0.2" +hex = "0.4.2" + diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs new file mode 100644 index 0000000..4648ed2 --- /dev/null +++ b/bindings/rust/build.rs @@ -0,0 +1,40 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + let root_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../"); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + // Ensure libblst exists in `OUT_DIR` + // Assuming blst submodule exists + Command::new("make") + .current_dir(root_dir.join("src")) + .arg("blst") + .status() + .unwrap(); + std::fs::copy(root_dir.join("lib/libblst.a"), &out_dir.join("libblst.a")).unwrap(); + + // Ensure libckzg exists in `OUT_DIR` + Command::new("make") + .current_dir(root_dir.join("src")) + .arg("all") + .status() + .unwrap(); + + Command::new("ar") + .current_dir(&root_dir.join("src")) + .args(["crus", "libckzg.a", "c_kzg_4844.o"]) + .status() + .unwrap(); + std::fs::copy(root_dir.join("src/libckzg.a"), &out_dir.join("libckzg.a")).unwrap(); + + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rustc-link-lib=static=ckzg"); + println!("cargo:rustc-link-lib=static=blst"); + println!( + "cargo:rerun-if-changed={}", + root_dir.join("src/c_kzg_4844.c").display() + ); +} diff --git a/bindings/rust/src/bindings.rs b/bindings/rust/src/bindings.rs new file mode 100644 index 0000000..8aaf402 --- /dev/null +++ b/bindings/rust/src/bindings.rs @@ -0,0 +1,274 @@ +/* automatically generated by rust-bindgen 0.61.0 */ + +use libc::FILE; + +pub const BYTES_PER_COMMITMENT: usize = 48; +pub const BYTES_PER_PROOF: usize = 48; +pub const FIELD_ELEMENTS_PER_BLOB: usize = 4096; +pub const BYTES_PER_FIELD_ELEMENT: usize = 32; +pub const BYTES_PER_BLOB: usize = 131072; + +pub type byte = u8; +pub type limb_t = u64; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_scalar { + pub b: [byte; 32usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_fr { + pub l: [limb_t; 4usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp { + pub l: [limb_t; 6usize], +} +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp2 { + pub fp: [blst_fp; 2usize], +} +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp6 { + pub fp2: [blst_fp2; 3usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_fp12 { + pub fp6: [blst_fp6; 2usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_p1 { + pub x: blst_fp, + pub y: blst_fp, + pub z: blst_fp, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_p1_affine { + pub x: blst_fp, + pub y: blst_fp, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_p2 { + pub x: blst_fp2, + pub y: blst_fp2, + pub z: blst_fp2, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct blst_p2_affine { + pub x: blst_fp2, + pub y: blst_fp2, +} + +pub const FIAT_SHAMIR_PROTOCOL_DOMAIN: [u8; 16usize] = [ + 70, 83, 66, 76, 79, 66, 86, 69, 82, 73, 70, 89, 95, 86, 49, 95, +]; +pub type g1_t = blst_p1; +pub type g2_t = blst_p2; +pub type fr_t = blst_fr; +pub type KZGCommitment = g1_t; +pub type KZGProof = g1_t; +pub type BLSFieldElement = fr_t; +pub type Blob = [u8; 131072usize]; +#[repr(u32)] +#[doc = " The common return type for all routines in which something can go wrong."] +#[doc = ""] +#[doc = " @warning In the case of @p C_KZG_OK or @p C_KZG_BADARGS, the caller can assume that all memory allocated by the"] +#[doc = " called routines has been deallocated. However, in the case of @p C_KZG_ERROR or @p C_KZG_MALLOC being returned, these"] +#[doc = " are unrecoverable and memory may have been leaked."] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum C_KZG_RET { + #[doc = "< Success!"] + C_KZG_OK = 0, + #[doc = "< The supplied data is invalid in some way"] + C_KZG_BADARGS = 1, + #[doc = "< Internal error - this should never occur and may indicate a bug in the library"] + C_KZG_ERROR = 2, + #[doc = "< Could not allocate memory"] + C_KZG_MALLOC = 3, +} +#[doc = " Stores the setup and parameters needed for performing FFTs."] +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FFTSettings { + #[doc = "< The maximum size of FFT these settings support, a power of 2."] + pub max_width: u64, + #[doc = "< Ascending powers of the root of unity, size `width + 1`."] + pub expanded_roots_of_unity: *mut fr_t, + #[doc = "< Descending powers of the root of unity, size `width + 1`."] + pub reverse_roots_of_unity: *mut fr_t, + #[doc = "< Powers of the root of unity in bit-reversal permutation, size `width`."] + pub roots_of_unity: *mut fr_t, +} +#[test] +fn bindgen_test_layout_FFTSettings() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(FFTSettings)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(FFTSettings)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).max_width) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(FFTSettings), + "::", + stringify!(max_width) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).expanded_roots_of_unity) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(FFTSettings), + "::", + stringify!(expanded_roots_of_unity) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).reverse_roots_of_unity) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(FFTSettings), + "::", + stringify!(reverse_roots_of_unity) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).roots_of_unity) as usize - ptr as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(FFTSettings), + "::", + stringify!(roots_of_unity) + ) + ); +} +#[doc = " Stores the setup and parameters needed for computing KZG proofs."] +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct KZGSettings { + #[doc = "< The corresponding settings for performing FFTs"] + pub fs: *const FFTSettings, + #[doc = "< G1 group elements from the trusted setup, in Lagrange form bit-reversal permutation"] + pub g1_values: *mut g1_t, + #[doc = "< G2 group elements from the trusted setup; both arrays have FIELD_ELEMENTS_PER_BLOB elements"] + pub g2_values: *mut g2_t, +} +#[test] +fn bindgen_test_layout_KZGSettings() { + const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(KZGSettings)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(KZGSettings)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).fs) as usize - ptr as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(KZGSettings), + "::", + stringify!(fs) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).g1_values) as usize - ptr as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(KZGSettings), + "::", + stringify!(g1_values) + ) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).g2_values) as usize - ptr as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(KZGSettings), + "::", + stringify!(g2_values) + ) + ); +} +extern "C" { + #[doc = " Interface functions"] + pub fn bytes_to_g1(out: *mut g1_t, in_: *const u8) -> C_KZG_RET; +} +extern "C" { + pub fn bytes_from_g1(out: *mut u8, in_: *const g1_t); +} +extern "C" { + pub fn bytes_to_bls_field(out: *mut BLSFieldElement, in_: *const u8); +} +extern "C" { + pub fn load_trusted_setup(out: *mut KZGSettings, in_: *mut FILE) -> C_KZG_RET; +} +extern "C" { + pub fn free_trusted_setup(s: *mut KZGSettings); +} +extern "C" { + pub fn compute_aggregate_kzg_proof( + out: *mut KZGProof, + blobs: *const Blob, + n: usize, + s: *const KZGSettings, + ) -> C_KZG_RET; +} +extern "C" { + pub fn verify_aggregate_kzg_proof( + out: *mut bool, + blobs: *const Blob, + expected_kzg_commitments: *const KZGCommitment, + n: usize, + kzg_aggregated_proof: *const KZGProof, + s: *const KZGSettings, + ) -> C_KZG_RET; +} +extern "C" { + pub fn blob_to_kzg_commitment(out: *mut KZGCommitment, blob: *mut u8, s: *const KZGSettings); +} +extern "C" { + pub fn verify_kzg_proof( + out: *mut bool, + polynomial_kzg: *const KZGCommitment, + z: *const BLSFieldElement, + y: *const BLSFieldElement, + kzg_proof: *const KZGProof, + s: *const KZGSettings, + ) -> C_KZG_RET; +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 0000000..914b9f5 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1,233 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +mod bindings; +use bindings::{g1_t, Blob, C_KZG_RET}; +use libc::fopen; +use std::ffi::CString; +use std::mem::MaybeUninit; +use std::os::unix::prelude::OsStrExt; +use std::path::PathBuf; + +pub use bindings::{ + BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_FIELD_ELEMENT, BYTES_PER_PROOF, + FIAT_SHAMIR_PROTOCOL_DOMAIN, FIELD_ELEMENTS_PER_BLOB, +}; + +const BYTES_PER_G1_POINT: usize = 48; + +#[derive(Debug)] +// TODO(add separate error type for commitments and proof) +pub enum Error { + /// The KZG proof is invalid. + InvalidKZGProof(String), + /// The KZG commitment is invalid. + InvalidKZGCommitment(String), + /// The underlying c-kzg library returned an error. + CError(C_KZG_RET), +} + +pub fn bytes_to_g1(bytes: &[u8]) -> Result { + let mut g1_point = MaybeUninit::::uninit(); + unsafe { + let res = bindings::bytes_to_g1(g1_point.as_mut_ptr(), bytes.as_ptr()); + if let C_KZG_RET::C_KZG_OK = res { + Ok(g1_point.assume_init()) + } else { + Err(Error::CError(res)) + } + } +} + +pub fn bytes_from_g1(g1_point: g1_t) -> [u8; BYTES_PER_G1_POINT] { + let mut bytes = [0; 48]; + unsafe { bindings::bytes_from_g1(bytes.as_mut_ptr(), &g1_point) } + bytes +} + +#[derive(Debug, Clone, Copy)] +pub struct BLSFieldElement(bindings::BLSFieldElement); + +impl BLSFieldElement { + pub fn bytes_to_bls_field(bytes: [u8; BYTES_PER_FIELD_ELEMENT as usize]) -> Self { + let mut bls_field_element = MaybeUninit::::uninit(); + unsafe { + bindings::bytes_to_bls_field(bls_field_element.as_mut_ptr(), bytes.as_ptr()); + Self(bls_field_element.assume_init()) + } + } +} + +pub struct KZGSettings(bindings::KZGSettings); +impl KZGSettings { + pub fn load_trusted_setup(file_path: PathBuf) -> Result { + let file_path = CString::new(file_path.as_os_str().as_bytes()).unwrap(); + let mut kzg_settings = MaybeUninit::::uninit(); + unsafe { + let file_ptr = fopen(file_path.as_ptr(), &('r' as libc::c_char)); + let res = bindings::load_trusted_setup(kzg_settings.as_mut_ptr(), file_ptr); + if let C_KZG_RET::C_KZG_OK = res { + Ok(Self(kzg_settings.assume_init())) + } else { + Err(res) + } + } + } +} + +impl Drop for KZGSettings { + fn drop(&mut self) { + unsafe { bindings::free_trusted_setup(&mut self.0) } + } +} + +pub struct KZGProof(bindings::KZGProof); + +impl KZGProof { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != BYTES_PER_PROOF { + return Err(Error::InvalidKZGProof(format!( + "Invalid byte length. Expected {} got {}", + BYTES_PER_PROOF, + bytes.len(), + ))); + } + let mut proof_bytes = [0; BYTES_PER_PROOF]; + proof_bytes.copy_from_slice(bytes); + Ok(Self(bytes_to_g1(bytes)?)) + } + + pub fn to_bytes(&self) -> Vec { + bytes_from_g1(self.0).to_vec() + } + + pub fn as_hex_string(&self) -> String { + hex::encode(self.to_bytes()) + } + + pub fn compute_aggregate_kzg_proof( + blobs: &[Blob], + kzg_settings: &KZGSettings, + ) -> Result { + let mut kzg_proof = MaybeUninit::::uninit(); + unsafe { + let res = bindings::compute_aggregate_kzg_proof( + kzg_proof.as_mut_ptr(), + blobs.as_ptr(), + blobs.len(), + &kzg_settings.0, + ); + if let C_KZG_RET::C_KZG_OK = res { + Ok(Self(kzg_proof.assume_init())) + } else { + Err(Error::CError(res)) + } + } + } + + pub fn verify_aggregate_kzg_proof( + &self, + blobs: &[Blob], + expected_kzg_commitments: &[KZGCommitment], + kzg_settings: &KZGSettings, + ) -> Result { + let mut verified: MaybeUninit = MaybeUninit::uninit(); + unsafe { + let res = bindings::verify_aggregate_kzg_proof( + verified.as_mut_ptr(), + blobs.as_ptr(), + expected_kzg_commitments + .iter() + .map(|c| c.0) + .collect::>() + .as_ptr(), + blobs.len(), + &self.0, + &kzg_settings.0, + ); + if let C_KZG_RET::C_KZG_OK = res { + Ok(verified.assume_init()) + } else { + Err(Error::CError(res)) + } + } + } + + pub fn verify_kzg_proof( + &self, + kzg_commitment: KZGCommitment, + z: BLSFieldElement, + y: BLSFieldElement, + kzg_settings: &KZGSettings, + ) -> Result { + let mut verified: MaybeUninit = MaybeUninit::uninit(); + unsafe { + let res = bindings::verify_kzg_proof( + verified.as_mut_ptr(), + &kzg_commitment.0, + &z.0, + &y.0, + &self.0, + &kzg_settings.0, + ); + if let C_KZG_RET::C_KZG_OK = res { + Ok(verified.assume_init()) + } else { + Err(Error::CError(res)) + } + } + } +} + +pub struct KZGCommitment(bindings::KZGCommitment); + +impl KZGCommitment { + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != BYTES_PER_COMMITMENT { + return Err(Error::InvalidKZGCommitment(format!( + "Invalid byte length. Expected {} got {}", + BYTES_PER_PROOF, + bytes.len(), + ))); + } + let mut proof_bytes = [0; BYTES_PER_COMMITMENT]; + proof_bytes.copy_from_slice(bytes); + Ok(Self(bytes_to_g1(bytes)?)) + } + + pub fn to_bytes(&self) -> Vec { + bytes_from_g1(self.0).to_vec() + } + + pub fn as_hex_string(&self) -> String { + hex::encode(self.to_bytes()) + } + + pub fn blob_to_kzg_commitment(mut blob: Blob, kzg_settings: &KZGSettings) -> Self { + let mut kzg_commitment: MaybeUninit = MaybeUninit::uninit(); + unsafe { + bindings::blob_to_kzg_commitment( + kzg_commitment.as_mut_ptr(), + blob.as_mut_ptr(), + &kzg_settings.0, + ); + Self(kzg_commitment.assume_init()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_load() { + { + let a = KZGSettings::load_trusted_setup(PathBuf::from( + "/home/pawan/eth2/c-kzg/src/trusted_setup.txt", + )); + assert!(a.is_ok()); + } + } +} diff --git a/src/Makefile b/src/Makefile index 3eb3a79..51acff2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ INCLUDE_DIRS = ../inc -CFLAGS += -O2 +CFLAGS += -O2 -fPIE all: c_kzg_4844.o lib