Skip to content

Commit

Permalink
feat(rust): Add ethereum kzg settings (#443)
Browse files Browse the repository at this point in the history
  • Loading branch information
rakita authored Jun 27, 2024
1 parent 305dbb7 commit 69616da
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 5 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 13 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
83 changes: 83 additions & 0 deletions bindings/rust/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
}
Expand All @@ -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<KzgErrors> 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<Vec<u8>, Error> {
let trimmed_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
Expand Down Expand Up @@ -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<Self, Error> {
let mut lines = trusted_setup.lines();

// load number of points
let n_g1 = lines
.next()
.ok_or(KzgErrors::FileFormatError)?
.parse::<usize>()
.map_err(|_| KzgErrors::ParseError)?;
let n_g2 = lines
.next()
.ok_or(KzgErrors::FileFormatError)?
.parse::<usize>()
.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
Expand Down Expand Up @@ -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");
Expand Down
Binary file not shown.
Binary file not shown.
80 changes: 80 additions & 0 deletions bindings/rust/src/ethereum_kzg_settings/mod.rs
Original file line number Diff line number Diff line change
@@ -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<KzgSettings> {
ethereum_kzg_settings_inner().clone()
}

fn ethereum_kzg_settings_inner() -> &'static Arc<KzgSettings> {
static DEFAULT: OnceBox<Arc<KzgSettings>> = 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::<G1Points>());
unsafe { &*BYTES.as_ptr().cast::<G1Points>() }
};

/// Default G2 points.
const ETH_G2_POINTS: &G2Points = {
const BYTES: &[u8] = include_bytes!("./g2_points.bin");
assert!(BYTES.len() == core::mem::size_of::<G2Points>());
unsafe { &*BYTES.as_ptr().cast::<G2Points>() }
};

#[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, &eth_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, &eth_commitment, &eth_settings)
.unwrap()
.to_bytes();
assert_eq!(ts_proof, eth_proof);
}
}
8 changes: 8 additions & 0 deletions bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 69616da

Please sign in to comment.