diff --git a/Cargo.lock b/Cargo.lock index 65860b854..42c266610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,7 @@ dependencies = [ "glob", "hex", "libc", + "once_cell", "rand", "serde", "serde_json", @@ -476,9 +477,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" diff --git a/Cargo.toml b/Cargo.toml index fb397398f..f355f8cef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,24 @@ description = "A minimal implementation of the Polynomial Commitments API for EI links = "ckzg" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -include = ["src", "inc", "bindings/rust/src", "bindings/rust/build.rs", "blst/bindings/*.h"] +include = [ + "src", + "inc", + "bindings/rust/src", + "bindings/rust/build.rs", + "blst/bindings/*.h", +] build = "bindings/rust/build.rs" [lib] path = "bindings/rust/src/lib.rs" [features] -default = ["std", "portable"] -std = ["hex/std", "libc/std", "serde?/std"] +default = ["std", "portable", "ethereum_kzg_settings"] +std = ["hex/std", "libc/std", "serde?/std", "once_cell?/std"] serde = ["dep:serde"] generate-bindings = ["dep:bindgen"] +ethereum_kzg_settings = ["dep:once_cell"] # This is a standalone feature so that crates that disable default features can # enable blst/portable without having to add it as a dependency @@ -36,6 +43,9 @@ serde = { version = "1.0", optional = true, default-features = false, features = "alloc", "derive", ] } +once_cell = { version = "1.19", default-features = false, features = [ + "alloc", +], optional = true } [dev-dependencies] criterion = "0.5.1" diff --git a/bindings/rust/src/bindings/mod.rs b/bindings/rust/src/bindings/mod.rs index e31cd0b18..5d7dfb436 100644 --- a/bindings/rust/src/bindings/mod.rs +++ b/bindings/rust/src/bindings/mod.rs @@ -22,6 +22,9 @@ use std::path::Path; pub const BYTES_PER_G1_POINT: usize = 48; pub const BYTES_PER_G2_POINT: usize = 96; +/// Number of G1 points required for the kzg trusted setup. +pub const NUM_G1_POINTS: usize = 4096; + /// Number of G2 points required for the kzg trusted setup. /// 65 is fixed and is used for providing multiproofs up to 64 field elements. pub const NUM_G2_POINTS: usize = 65; @@ -58,6 +61,8 @@ pub enum Error { InvalidTrustedSetup(String), /// Paired arguments have different lengths. MismatchLength(String), + /// Loading the trusted setup failed. + LoadingTrustedSetupFailed(KzgErrors), /// The underlying c-kzg library returned an error. CError(C_KZG_RET), } @@ -74,11 +79,36 @@ impl fmt::Display for Error { | Self::InvalidKzgCommitment(s) | Self::InvalidTrustedSetup(s) | Self::MismatchLength(s) => f.write_str(s), + Self::LoadingTrustedSetupFailed(s) => write!(f, "KzgErrors: {:?}", s), Self::CError(s) => fmt::Debug::fmt(s, f), } } } +impl From for Error { + fn from(e: KzgErrors) -> Self { + Error::LoadingTrustedSetupFailed(e) + } +} + +#[derive(Debug)] +pub enum KzgErrors { + /// Failed to get current directory. + FailedCurrentDirectory, + /// The specified path does not exist. + PathNotExists, + /// Problems related to I/O. + IOError, + /// Not a valid file. + NotValidFile, + /// File is not properly formatted. + FileFormatError, + /// Not able to parse to usize. + ParseError, + /// Number of points does not match what is expected. + MismatchedNumberOfPoints, +} + /// Converts a hex string (with or without the 0x prefix) to bytes. pub fn hex_to_bytes(hex_str: &str) -> Result, Error> { let trimmed_str = hex_str.strip_prefix("0x").unwrap_or(hex_str); @@ -154,6 +184,51 @@ impl KZGSettings { Self::load_trusted_setup_file_inner(&file_path) } + /// Parses the contents of a KZG trusted setup file into a KzgSettings. + pub fn parse_kzg_trusted_setup(trusted_setup: &str) -> Result { + let mut lines = trusted_setup.lines(); + + // load number of points + let n_g1 = lines + .next() + .ok_or(KzgErrors::FileFormatError)? + .parse::() + .map_err(|_| KzgErrors::ParseError)?; + let n_g2 = lines + .next() + .ok_or(KzgErrors::FileFormatError)? + .parse::() + .map_err(|_| KzgErrors::ParseError)?; + + if n_g1 != NUM_G1_POINTS { + return Err(KzgErrors::MismatchedNumberOfPoints.into()); + } + + if n_g2 != NUM_G2_POINTS { + return Err(KzgErrors::MismatchedNumberOfPoints.into()); + } + + // load g1 points + let mut g1_points = alloc::boxed::Box::new([[0; BYTES_PER_G1_POINT]; NUM_G1_POINTS]); + for bytes in g1_points.iter_mut() { + let line = lines.next().ok_or(KzgErrors::FileFormatError)?; + hex::decode_to_slice(line, bytes).map_err(|_| KzgErrors::ParseError)?; + } + + // load g2 points + let mut g2_points = alloc::boxed::Box::new([[0; BYTES_PER_G2_POINT]; NUM_G2_POINTS]); + for bytes in g2_points.iter_mut() { + let line = lines.next().ok_or(KzgErrors::FileFormatError)?; + hex::decode_to_slice(line, bytes).map_err(|_| KzgErrors::ParseError)?; + } + + if lines.next().is_some() { + return Err(KzgErrors::FileFormatError.into()); + } + + Self::load_trusted_setup(g1_points.as_ref(), g2_points.as_ref()) + } + /// Loads the trusted setup parameters from a file. The file format is as follows: /// /// FIELD_ELEMENTS_PER_BLOB @@ -697,6 +772,14 @@ mod tests { } } + #[test] + fn test_parse_kzg_trusted_setup() { + let trusted_setup_file = Path::new("src/trusted_setup.txt"); + assert!(trusted_setup_file.exists()); + let trusted_setup = fs::read_to_string(trusted_setup_file).unwrap(); + let _ = KZGSettings::parse_kzg_trusted_setup(&trusted_setup).unwrap(); + } + #[test] fn test_compute_kzg_proof() { let trusted_setup_file = Path::new("src/trusted_setup.txt"); diff --git a/bindings/rust/src/ethereum_kzg_settings/g1_points.bin b/bindings/rust/src/ethereum_kzg_settings/g1_points.bin new file mode 100644 index 000000000..2ac35953a Binary files /dev/null and b/bindings/rust/src/ethereum_kzg_settings/g1_points.bin differ diff --git a/bindings/rust/src/ethereum_kzg_settings/g2_points.bin b/bindings/rust/src/ethereum_kzg_settings/g2_points.bin new file mode 100644 index 000000000..ca5625282 Binary files /dev/null and b/bindings/rust/src/ethereum_kzg_settings/g2_points.bin differ diff --git a/bindings/rust/src/ethereum_kzg_settings/mod.rs b/bindings/rust/src/ethereum_kzg_settings/mod.rs new file mode 100644 index 000000000..ccb462a45 --- /dev/null +++ b/bindings/rust/src/ethereum_kzg_settings/mod.rs @@ -0,0 +1,80 @@ +use crate::{ + bindings::{BYTES_PER_G1_POINT, BYTES_PER_G2_POINT, NUM_G1_POINTS, NUM_G2_POINTS}, + KzgSettings, +}; +use alloc::{boxed::Box, sync::Arc}; +use once_cell::race::OnceBox; + +/// Returns default Ethereum mainnet KZG settings. +/// +/// If you need a cloneable settings use `ethereum_kzg_settings_arc` instead. +pub fn ethereum_kzg_settings() -> &'static KzgSettings { + ethereum_kzg_settings_inner().as_ref() +} + +/// Returns default Ethereum mainnet KZG settings as an `Arc`. +/// +/// It is useful for sharing the settings in multiple places. +pub fn ethereum_kzg_settings_arc() -> Arc { + ethereum_kzg_settings_inner().clone() +} + +fn ethereum_kzg_settings_inner() -> &'static Arc { + static DEFAULT: OnceBox> = OnceBox::new(); + DEFAULT.get_or_init(|| { + let settings = + KzgSettings::load_trusted_setup(ETH_G1_POINTS.as_ref(), ETH_G2_POINTS.as_ref()) + .expect("failed to load default trusted setup"); + Box::new(Arc::new(settings)) + }) +} + +type G1Points = [[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS]; +type G2Points = [[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS]; + +/// Default G1 points. +const ETH_G1_POINTS: &G1Points = { + const BYTES: &[u8] = include_bytes!("./g1_points.bin"); + assert!(BYTES.len() == core::mem::size_of::()); + unsafe { &*BYTES.as_ptr().cast::() } +}; + +/// Default G2 points. +const ETH_G2_POINTS: &G2Points = { + const BYTES: &[u8] = include_bytes!("./g2_points.bin"); + assert!(BYTES.len() == core::mem::size_of::()); + unsafe { &*BYTES.as_ptr().cast::() } +}; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{bindings::BYTES_PER_BLOB, Blob, KzgCommitment, KzgProof, KzgSettings}; + use std::path::Path; + + #[test] + pub fn compare_default_with_file() { + let ts_settings = + KzgSettings::load_trusted_setup_file(Path::new("src/trusted_setup.txt")).unwrap(); + let eth_settings = ethereum_kzg_settings(); + let blob = Blob::new([1u8; BYTES_PER_BLOB]); + + // generate commitment + let ts_commitment = KzgCommitment::blob_to_kzg_commitment(&blob, &ts_settings) + .unwrap() + .to_bytes(); + let eth_commitment = KzgCommitment::blob_to_kzg_commitment(&blob, ð_settings) + .unwrap() + .to_bytes(); + assert_eq!(ts_commitment, eth_commitment); + + // generate proof + let ts_proof = KzgProof::compute_blob_kzg_proof(&blob, &ts_commitment, &ts_settings) + .unwrap() + .to_bytes(); + let eth_proof = KzgProof::compute_blob_kzg_proof(&blob, ð_commitment, ð_settings) + .unwrap() + .to_bytes(); + assert_eq!(ts_proof, eth_proof); + } +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 7c54cb518..bbc79f0e5 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -10,11 +10,19 @@ extern crate blst; mod bindings; +#[cfg(feature = "ethereum_kzg_settings")] +mod ethereum_kzg_settings; + // Expose relevant types with idiomatic names. pub use bindings::{ KZGCommitment as KzgCommitment, KZGProof as KzgProof, KZGSettings as KzgSettings, C_KZG_RET as CkzgError, }; + +#[cfg(feature = "ethereum_kzg_settings")] +// Expose the default settings. +pub use ethereum_kzg_settings::{ethereum_kzg_settings, ethereum_kzg_settings_arc}; + // Expose the constants. pub use bindings::{ BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_FIELD_ELEMENT, BYTES_PER_G1_POINT,