Skip to content

Commit

Permalink
export global kzg settings
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed Aug 31, 2023
1 parent 37feea7 commit 9c048b4
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 68 deletions.
3 changes: 1 addition & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ opt-level = 3

[workspace.dependencies]
# "https://github.com/ethereum/c-kzg-4844"
c-kzg = { git = "https://github.com/danipopes/c-kzg-4844", branch = "export-ffi" }
c-kzg = { git = "https://github.com/danipopes/c-kzg-4844", branch = "export-ffi", default-features = false }
2 changes: 1 addition & 1 deletion crates/precompile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = "https://github.com/bluealloy/revm"
version = "2.0.3"

# Don't need to run build script outside of this repo
exclude = ["build.rs", "src/kzg/*.txt"]
exclude = ["build.rs", "src/blob/kzg_settings/*.txt"]

[dependencies]
revm-primitives = { path = "../primitives", version = "1.1.2", default-features = false }
Expand Down
84 changes: 50 additions & 34 deletions crates/precompile/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![allow(dead_code, unused_imports)]

use std::ffi::CString;
use std::fs;
use std::path::Path;
use std::slice;

const BYTES_PER_G1_POINT: usize = 48;
const BYTES_PER_G2_POINT: usize = 96;
Expand All @@ -9,17 +13,25 @@ fn main() {
}

fn generate_kzg_settings() {
let in_path = "src/blob/trusted_setup.txt";
let out_path = "src/blob/generated_settings.rs";
let in_path = "src/blob/kzg_settings/trusted_setup.txt";
let out_path = "src/blob/kzg_settings/generated.rs";

let in_path = Path::new(in_path);
println!("cargo:rerun-if-changed={}", in_path.display());
assert!(in_path.exists());
let contents = format_kzg_settings(in_path);
let contents = format_kzg_settings_small(in_path);
fs::write(out_path, contents).unwrap();
}

fn format_kzg_settings(in_path: &Path) -> String {
/// Pros:
/// - smaller generated file size (720K)
/// - smaller runtime static size (198K = `48*4096 + 96*65`)
/// - half the size of `trusted_setup.txt`, which would have to be included in the binary without
/// this build script
///
/// Cons:
/// - must call `load_trusted_setup` at least once
fn format_kzg_settings_small(in_path: &Path) -> String {
let contents = fs::read_to_string(in_path).unwrap();
let mut lines = contents.lines();

Expand Down Expand Up @@ -53,42 +65,46 @@ fn format_kzg_settings(in_path: &Path) -> String {
format!(
r#"// @generated by build.rs from {in_path:?}, do not modify manually.
pub use c_kzg::{{BYTES_PER_G1_POINT, BYTES_PER_G2_POINT}};
// Ensure that the build script constants are synced with the C bindings ones.
const _: [(); BYTES_PER_G1_POINT] = [(); {BYTES_PER_G1_POINT}];
const _: [(); BYTES_PER_G2_POINT] = [(); {BYTES_PER_G2_POINT}];
pub const NUM_G1_POINTS: usize = {n_g1};
pub const NUM_G2_POINTS: usize = {n_g2};
pub const G1_POINTS: &[[u8; c_kzg::BYTES_PER_G1_POINT]; NUM_G1_POINTS] = &{g1_points};
pub const G2_POINTS: &[[u8; c_kzg::BYTES_PER_G2_POINT]; NUM_G2_POINTS] = &{g2_points};
pub const G1_POINTS: &[[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS] = &{g1_points};
pub const G2_POINTS: &[[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS] = &{g2_points};
"#
)
}

// TODO: cannot dereference the KzgSettings pointers allocated in C (SIGSEGV)
/*
fn format_kzg_settings(in_path: &Path) -> String {
/// Pros:
/// - no need to call `load_trusted_setup` at runtime
///
/// Cons:
/// - larger generated file size (2M)
/// - larger runtime static size (722K = `32*4096 + 144*4096 + 288*65`)
/// - possibly unsafe? `as_ptr() as *mut _`
/// - depends on `c_kzg`, this might not matter much
fn format_kzg_settings_large(in_path: &Path) -> String {
const TRUSTED_SETUP_NUM_G1_POINTS: usize = c_kzg::FIELD_ELEMENTS_PER_BLOB;
const TRUSTED_SETUP_NUM_G2_POINTS: usize = 65;
let KzgSettings {
max_width,
roots_of_unity,
g1_values,
g2_values,
} = dbg!(KzgSettings::load_trusted_setup_file(in_path).unwrap());
assert_eq!(
core::mem::size_of::<c_kzg::sys::blst_p1>(),
c_kzg::BYTES_PER_G1_POINT * 3
);
assert_eq!(
core::mem::size_of::<c_kzg::sys::blst_p2>(),
c_kzg::BYTES_PER_G2_POINT * 3
);
// note: destructuring like `KzgSettings { ... }` leads to segfaults, I don't know why.
let c_path = CString::new(in_path.to_str().unwrap()).unwrap();
let s = c_kzg::KzgSettings::load_trusted_setup_file_inner(&c_path).unwrap();
let roots_of_unity = unsafe { slice::from_raw_parts(roots_of_unity, max_width as _) };
let max_width = s.max_width;
let roots_of_unity = unsafe { slice::from_raw_parts(s.roots_of_unity, max_width as usize) };
let g1_values = unsafe { slice::from_raw_parts(s.g1_values, TRUSTED_SETUP_NUM_G1_POINTS) };
let g2_values = unsafe { slice::from_raw_parts(s.g2_values, TRUSTED_SETUP_NUM_G2_POINTS) };
let g1_values = unsafe { slice::from_raw_parts(g1_values, TRUSTED_SETUP_NUM_G1_POINTS * 3) };
print_bytes(g1_values);
let g2_values = unsafe { slice::from_raw_parts(g2_values, TRUSTED_SETUP_NUM_G2_POINTS * 3) };
let roots_of_unity = format!("{roots_of_unity:?}").replace(' ', "");
let g1_values = format!("{g1_values:?}").replace(' ', "");
let g2_values = format!("{g2_values:?}").replace(' ', "");
format!(
r#"// @generated by build.rs from {in_path:?}, do not modify manually.
Expand All @@ -98,16 +114,16 @@ use c_kzg::{{sys::*, KzgSettings}};
pub const TRUSTED_SETUP_NUM_G1_POINTS: usize = c_kzg::FIELD_ELEMENTS_PER_BLOB;
pub const TRUSTED_SETUP_NUM_G2_POINTS: usize = 65;
pub const MAX_WIDTH: usize = {max_width};
pub static mut ROOTS_OF_UNITY: [blst_fr; MAX_WIDTH] = {roots_of_unity:?};
pub static mut G1_VALUES: [blst_p1; TRUSTED_SETUP_NUM_G1_POINTS] = {g1_values:?};
pub static mut G2_VALUES: [blst_p2; TRUSTED_SETUP_NUM_G2_POINTS] = {g2_values:?};
pub const MAX_WIDTH: u64 = {max_width};
pub const ROOTS_OF_UNITY: &[blst_fr; MAX_WIDTH as usize] = &{roots_of_unity};
pub const G1_VALUES: &[blst_p1; TRUSTED_SETUP_NUM_G1_POINTS] = &{g1_values};
pub const G2_VALUES: &[blst_p2; TRUSTED_SETUP_NUM_G2_POINTS] = &{g2_values};
pub static KZG_SETTINGS: KzgSettings = KzgSettings {{
max_width: MAX_WIDTH,
roots_of_unity: unsafe {{ ROOTS_OF_UNITY.as_mut_ptr() }},
g1_values: unsafe {{ G1_VALUES.as_mut_ptr() }},
g2_values: unsafe {{ G2_VALUES.as_mut_ptr() }},
roots_of_unity: ROOTS_OF_UNITY.as_ptr() as *mut _,
g1_values: G1_VALUES.as_ptr() as *mut _,
g2_values: G2_VALUES.as_ptr() as *mut _,
}};
"#
)
Expand Down
7 changes: 0 additions & 7 deletions crates/precompile/src/blob/generated_settings.rs

This file was deleted.

13 changes: 13 additions & 0 deletions crates/precompile/src/blob/kzg_settings/generated.rs

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions crates/precompile/src/blob/kzg_settings/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use c_kzg::KzgSettings;
use once_cell::sync::OnceCell;

#[rustfmt::skip]
mod generated;

/// Note: the type of this is an implementation detail, so we don't expose it in the public API.
static GLOBAL_KZG_SETTINGS: OnceCell<KzgSettings> = OnceCell::new();

/// Returns a reference to the global [`KzgSettings`] instance, initializing it with the default
/// value if it was not previously set.
pub fn get_global_or_default() -> &'static KzgSettings {
GLOBAL_KZG_SETTINGS.get_or_init(default)
}

/// Returns a reference to the global [`KzgSettings`] instance, initializing it with `f` if it was
/// not previously set.
pub fn get_global_or_init<F: FnOnce() -> KzgSettings>(f: F) -> &'static KzgSettings {
GLOBAL_KZG_SETTINGS.get_or_init(f)
}

/// Sets the given KZG settings as the global instance.
///
/// Returns `Ok(())` if the cell was empty and `Err(value)` if it was full.
pub fn set_global(value: KzgSettings) -> Result<(), KzgSettings> {
GLOBAL_KZG_SETTINGS.set(value)
}

/// Creates a new a KZG settings instance by initializing it with the default value.
pub fn default() -> KzgSettings {
c_kzg::KzgSettings::load_trusted_setup(generated::G1_POINTS, generated::G2_POINTS)
.expect("failed to load default trusted setup")
}
57 changes: 34 additions & 23 deletions crates/precompile/src/blob/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::{Error, Precompile, PrecompileAddress, PrecompileResult, B160};
use c_kzg::{bindings, Bytes32, Bytes48, CkzgError, KzgSettings};
use once_cell::sync::OnceCell;
use c_kzg::{bindings, Bytes32, Bytes48, CkzgError};
use revm_primitives::hex_literal::hex;
use sha2::{Digest, Sha256};

#[rustfmt::skip]
mod generated_settings;
pub mod kzg_settings;

pub const POINT_EVALUATION: PrecompileAddress =
PrecompileAddress(ADDRESS, Precompile::Standard(run));
Expand Down Expand Up @@ -63,25 +61,27 @@ fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] {
}

fn verify_kzg_proof(commitment: &Bytes48, z: &Bytes32, y: &Bytes32, proof: &Bytes48) -> bool {
// note: we use the bindings directly to avoid copying the input bytes.
// If `KzgProof::verify_kzg_proof` ever changes to take references, we should use that instead.
let mut ok = false;
let ret =
unsafe { bindings::verify_kzg_proof(&mut ok, commitment, z, y, proof, get_kzg_settings()) };
debug_assert!(
ret == CkzgError::C_KZG_OK,
"verify_kzg_proof returned an error: {ret:?}"
);
ok
}

fn get_kzg_settings() -> &'static KzgSettings {
static SETTINGS: OnceCell<KzgSettings> = OnceCell::new();
SETTINGS.get_or_init(|| {
c_kzg::KzgSettings::load_trusted_setup(
generated_settings::G1_POINTS,
generated_settings::G2_POINTS,
let ret = unsafe {
bindings::verify_kzg_proof(
&mut ok,
commitment,
z,
y,
proof,
kzg_settings::get_global_or_default(),
)
.expect("failed to load trusted setup")
})
};
if ret != CkzgError::C_KZG_OK {
#[cfg(debug_assertions)]
panic!("verify_kzg_proof returned an error: {ret:?}");

#[cfg(not(debug_assertions))]
return false;
}
ok
}

#[inline(always)]
Expand All @@ -96,14 +96,14 @@ fn as_array<const N: usize>(bytes: &[u8]) -> &[u8; N] {
#[cfg_attr(debug_assertions, track_caller)]
fn as_bytes32(bytes: &[u8]) -> &Bytes32 {
// SAFETY: `#[repr(C)] Bytes32([u8; 32])`
unsafe { *as_array::<32>(bytes).as_ptr().cast() }
unsafe { &*as_array::<32>(bytes).as_ptr().cast() }
}

#[inline(always)]
#[cfg_attr(debug_assertions, track_caller)]
fn as_bytes48(bytes: &[u8]) -> &Bytes48 {
// SAFETY: `#[repr(C)] Bytes48([u8; 48])`
unsafe { *as_array::<48>(bytes).as_ptr().cast() }
unsafe { &*as_array::<48>(bytes).as_ptr().cast() }
}

#[cfg(test)]
Expand All @@ -125,4 +125,15 @@ mod tests {
let result = [elements.to_be_bytes(), *BLS_MODULUS].concat();
assert_eq!(RETURN_VALUE[..], result);
}

// https://github.com/ethereum/go-ethereum/blob/41ee96fdfee5924004e8fbf9bbc8aef783893917/core/vm/testdata/precompiles/pointEvaluation.json
#[test]
fn basic_test() {
let input = hex!("01d18459b334ffe8e2226eef1db874fda6db2bdd9357268b39220af2d59464fb564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a1978a0d595c823c05947b1156175e72634a377808384256e9921ebf72181890be2d6b58d4a73a880541d1656875654806942307f266e636553e94006d11423f2688945ff3bdf515859eba1005c1a7708d620a94d91a1c0c285f9584e75ec2f82a");
let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
let gas = 50000;
let (actual_gas, actual_output) = run(&input, gas).unwrap();
assert_eq!(actual_gas, gas);
assert_eq!(actual_output, expected_output);
}
}
3 changes: 3 additions & 0 deletions crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ mod identity;
mod modexp;
mod secp256k1;

// Export kzg_settings initializers
pub use blob::kzg_settings;

use once_cell::sync::OnceCell;
pub use primitives::{
precompile::{PrecompileError as Error, *},
Expand Down

0 comments on commit 9c048b4

Please sign in to comment.