Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(host): Accelerate all BLS12-381 Precompiles #1010

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions Cargo.lock

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

114 changes: 114 additions & 0 deletions bin/client/src/precompiles/bls12_g1_add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! Contains the accelerated precompile for the BLS12-381 curve G1 Point Addition.
//!
//! BLS12-381 is introduced in [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537).
//!
//! For constants and logic, see the [revm implementation].
//!
//! [revm implementation]: https://github.com/bluealloy/revm/blob/main/crates/precompile/src/bls12_381/g1_add.rs

use crate::{HINT_WRITER, ORACLE_READER};
use alloc::{string::ToString, vec::Vec};
use alloy_primitives::{address, keccak256, Address, Bytes};
use kona_preimage::{
errors::PreimageOracleError, PreimageKey, PreimageKeyType, PreimageOracleClient,
};
use kona_proof::{errors::OracleProviderError, HintType};
use revm::{
precompile::{Error as PrecompileError, Precompile, PrecompileResult, PrecompileWithAddress},
primitives::PrecompileOutput,
};

/// The address of the BLS12-381 g1 addition check precompile.
///
/// See: <https://eips.ethereum.org/EIPS/eip-2537#constants>
const BLS12_G1_ADD_CHECK: Address = address!("0x000000000000000000000000000000000000000b");

/// Input length of G1 Addition operation.
const INPUT_LENGTH: usize = 256;

/// Base gas fee for the BLS12-381 g1 addition operation.
const G1_ADD_BASE_FEE: u64 = 375;

/// The address of the BLS12-381 g1 addition precompile.
pub(crate) const FPVM_BLS12_G1_ADD_ISTHMUS: PrecompileWithAddress =
PrecompileWithAddress(BLS12_G1_ADD_CHECK, Precompile::Standard(fpvm_bls12_g1_add));

/// Performs an FPVM-accelerated BLS12-381 G1 addition check.
///
/// Notice, there is no input size limit for this precompile.
/// See: <https://specs.optimism.io/protocol/isthmus/exec-engine.html#evm-changes>
fn fpvm_bls12_g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult {
if G1_ADD_BASE_FEE > gas_limit {
return Err(PrecompileError::OutOfGas.into());
}

let input_len = input.len();
if input_len != INPUT_LENGTH {
return Err(PrecompileError::Other(alloc::format!(
"G1 addition input length should be multiple of {INPUT_LENGTH}, was {input_len}"
))
.into());
}

Check warning on line 51 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L51

Added line #L51 was not covered by tests

let result_data = kona_proof::block_on(async move {
// Write the hint for the ecrecover precompile run.
let hint_data = &[BLS12_G1_ADD_CHECK.as_ref(), input.as_ref()];
HintType::L1Precompile.with_data(hint_data).send(&HINT_WRITER).await?;

Check warning on line 56 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L53-L56

Added lines #L53 - L56 were not covered by tests

// Construct the key hash for the ecrecover precompile run.
let raw_key_data = hint_data.iter().copied().flatten().copied().collect::<Vec<u8>>();
let key_hash = keccak256(&raw_key_data);

Check warning on line 60 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L59-L60

Added lines #L59 - L60 were not covered by tests

// Fetch the result of the ecrecover precompile run from the host.
let result_data = ORACLE_READER
.get(PreimageKey::new(*key_hash, PreimageKeyType::Precompile))
.await
.map_err(OracleProviderError::Preimage)?;

Check warning on line 66 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L63-L66

Added lines #L63 - L66 were not covered by tests

// Ensure we've received valid result data.
if result_data.is_empty() {
return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
"Invalid result data".to_string(),
)));
}

// Ensure we've not received an error from the host.
if result_data[0] == 0 {
return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
"Error executing ecrecover precompile in host".to_string(),
)));
}

// Return the result data.
Ok(result_data[1..].to_vec())
})
.map_err(|e| PrecompileError::Other(e.to_string()))?;

Check warning on line 85 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L69-L85

Added lines #L69 - L85 were not covered by tests

Ok(PrecompileOutput::new(G1_ADD_BASE_FEE, result_data.into()))

Check warning on line 87 in bin/client/src/precompiles/bls12_g1_add.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_add.rs#L87

Added line #L87 was not covered by tests
}

#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;

#[test]
fn test_fpvm_bls12_g1_add_input_len() {
let input = Bytes::from(vec![0u8; INPUT_LENGTH + 1]);
let err = PrecompileError::Other(alloc::format!(
"G1 addition input length should be multiple of {}, was {}",
INPUT_LENGTH,
INPUT_LENGTH + 1
));
assert_eq!(fpvm_bls12_g1_add(&input, G1_ADD_BASE_FEE), Err(err.into()));
}

#[test]
fn test_fpvm_bls12_g1_add_out_of_gas() {
let input = Bytes::from(vec![0u8; INPUT_LENGTH * 2]);
assert_eq!(
fpvm_bls12_g1_add(&input, G1_ADD_BASE_FEE - 1),
Err(PrecompileError::OutOfGas.into())
);
}
}
172 changes: 172 additions & 0 deletions bin/client/src/precompiles/bls12_g1_msm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//! Contains the accelerated precompile for the BLS12-381 curve G1 MSM.
//!
//! BLS12-381 is introduced in [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537).
//!
//! For constants and logic, see the [revm implementation].
//!
//! [revm implementation]: https://github.com/bluealloy/revm/blob/main/crates/precompile/src/bls12_381/g1_msm.rs

use crate::{HINT_WRITER, ORACLE_READER};
use alloc::{string::ToString, vec::Vec};
use alloy_primitives::{address, keccak256, Address, Bytes};
use kona_preimage::{
errors::PreimageOracleError, PreimageKey, PreimageKeyType, PreimageOracleClient,
};
use kona_proof::{errors::OracleProviderError, HintType};
use revm::{
precompile::{Error as PrecompileError, Precompile, PrecompileResult, PrecompileWithAddress},
primitives::PrecompileOutput,
};

/// Amount used to calculate the multi-scalar-multiplication discount
const MSM_MULTIPLIER: u64 = 1000;

/// Implements the gas schedule for G1/G2 Multiscalar-multiplication assuming 30
/// MGas/second, see also: <https://eips.ethereum.org/EIPS/eip-2537#g1g2-multiexponentiation>
#[inline]
fn msm_required_gas(k: usize, discount_table: &[u16], multiplication_cost: u64) -> u64 {
if k == 0 {
return 0;

Check warning on line 29 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L29

Added line #L29 was not covered by tests
}

let index = core::cmp::min(k - 1, discount_table.len() - 1);
let discount = discount_table[index] as u64;

(k as u64 * discount * multiplication_cost) / MSM_MULTIPLIER
}

/// The maximum input size for the BLS12-381 g1 msm operation after the Isthmus Hardfork.
///
/// See: <https://specs.optimism.io/protocol/isthmus/exec-engine.html#evm-changes>
const BLS12_MAX_G1_MSM_SIZE_ISTHMUS: usize = 513760;

/// The address of the BLS12-381 g1 msm check precompile.
///
/// See: <https://eips.ethereum.org/EIPS/eip-2537#constants>
const BLS12_G1_MSM_CHECK: Address = address!("0x000000000000000000000000000000000000000c");

/// Input length of g1 msm operation.
const INPUT_LENGTH: usize = 160;

/// Base gas fee for the BLS12-381 g1 msm operation.
const G1_MSM_BASE_FEE: u64 = 12000;

/// The address of the BLS12-381 g1 msm precompile.
pub(crate) const FPVM_BLS12_G1_MSM_ISTHMUS: PrecompileWithAddress =
PrecompileWithAddress(BLS12_G1_MSM_CHECK, Precompile::Standard(fpvm_bls12_g1_msm_isthmus));

/// Discounts table for G1 MSM as a vector of pairs `[k, discount]`.
static DISCOUNT_TABLE: [u16; 128] = [
1000, 949, 848, 797, 764, 750, 738, 728, 719, 712, 705, 698, 692, 687, 682, 677, 673, 669, 665,
661, 658, 654, 651, 648, 645, 642, 640, 637, 635, 632, 630, 627, 625, 623, 621, 619, 617, 615,
613, 611, 609, 608, 606, 604, 603, 601, 599, 598, 596, 595, 593, 592, 591, 589, 588, 586, 585,
584, 582, 581, 580, 579, 577, 576, 575, 574, 573, 572, 570, 569, 568, 567, 566, 565, 564, 563,
562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551, 550, 549, 548, 547, 547, 546, 545,
544, 543, 542, 541, 540, 540, 539, 538, 537, 536, 536, 535, 534, 533, 532, 532, 531, 530, 529,
528, 528, 527, 526, 525, 525, 524, 523, 522, 522, 521, 520, 520, 519,
];

/// Performs an FPVM-accelerated BLS12-381 G1 msm check.
fn fpvm_bls12_g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult {
let input_len = input.len();
if input_len == 0 || input_len % INPUT_LENGTH != 0 {
return Err(PrecompileError::Other(alloc::format!(
"G1MSM input length should be multiple of {}, was {}",
INPUT_LENGTH,
input_len
))
.into());
}

let k = input_len / INPUT_LENGTH;
let required_gas = msm_required_gas(k, &DISCOUNT_TABLE, G1_MSM_BASE_FEE);
if required_gas > gas_limit {
return Err(PrecompileError::OutOfGas.into());
}

Check warning on line 85 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L85

Added line #L85 was not covered by tests

let result_data = kona_proof::block_on(async move {
// Write the hint for the ecrecover precompile run.
let hint_data = &[BLS12_G1_MSM_CHECK.as_ref(), input.as_ref()];
HintType::L1Precompile.with_data(hint_data).send(&HINT_WRITER).await?;

Check warning on line 90 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L87-L90

Added lines #L87 - L90 were not covered by tests

// Construct the key hash for the ecrecover precompile run.
let raw_key_data = hint_data.iter().copied().flatten().copied().collect::<Vec<u8>>();
let key_hash = keccak256(&raw_key_data);

Check warning on line 94 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L93-L94

Added lines #L93 - L94 were not covered by tests

// Fetch the result of the ecrecover precompile run from the host.
let result_data = ORACLE_READER
.get(PreimageKey::new(*key_hash, PreimageKeyType::Precompile))
.await
.map_err(OracleProviderError::Preimage)?;

Check warning on line 100 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L97-L100

Added lines #L97 - L100 were not covered by tests

// Ensure we've received valid result data.
if result_data.is_empty() {
return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
"Invalid result data".to_string(),
)));
}

// Ensure we've not received an error from the host.
if result_data[0] == 0 {
return Err(OracleProviderError::Preimage(PreimageOracleError::Other(
"Error executing ecrecover precompile in host".to_string(),
)));
}

// Return the result data.
Ok(result_data[1..].to_vec())
})
.map_err(|e| PrecompileError::Other(e.to_string()))?;

Check warning on line 119 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L103-L119

Added lines #L103 - L119 were not covered by tests

Ok(PrecompileOutput::new(G1_MSM_BASE_FEE, result_data.into()))

Check warning on line 121 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L121

Added line #L121 was not covered by tests
}

/// Performs an FPVM-accelerated `bls12` g1 msm check precompile call
/// after the Isthmus Hardfork.
fn fpvm_bls12_g1_msm_isthmus(input: &Bytes, gas_limit: u64) -> PrecompileResult {
if input.len() > BLS12_MAX_G1_MSM_SIZE_ISTHMUS {
return Err(PrecompileError::Other(alloc::format!(
"G1MSM input length must be at most {}",
BLS12_MAX_G1_MSM_SIZE_ISTHMUS
))
.into());
}

fpvm_bls12_g1_msm(input, gas_limit)

Check warning on line 135 in bin/client/src/precompiles/bls12_g1_msm.rs

View check run for this annotation

Codecov / codecov/patch

bin/client/src/precompiles/bls12_g1_msm.rs#L133-L135

Added lines #L133 - L135 were not covered by tests
}

#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;

#[test]
fn test_fpvm_bls12_g1_msm_isthmus_max_bytes() {
let input = Bytes::from(vec![0u8; BLS12_MAX_G1_MSM_SIZE_ISTHMUS + 1]);
let gas_limit = G1_MSM_BASE_FEE;
let err = PrecompileError::Other(alloc::format!(
"G1MSM input length must be at most {}",
BLS12_MAX_G1_MSM_SIZE_ISTHMUS
));
assert_eq!(fpvm_bls12_g1_msm_isthmus(&input, gas_limit), Err(err.into()));
}

#[test]
fn test_fpvm_bls12_g1_msm_offset() {
let input = Bytes::from(vec![0u8; INPUT_LENGTH + 1]);
let gas_limit = G1_MSM_BASE_FEE;
let err = PrecompileError::Other(alloc::format!(
"G1MSM input length should be multiple of {}, was {}",
INPUT_LENGTH,
input.len(),
));
assert_eq!(fpvm_bls12_g1_msm(&input, gas_limit), Err(err.into()));
}

#[test]
fn test_fpvm_bls12_g1_msm_out_of_gas() {
let input = Bytes::from(vec![0u8; INPUT_LENGTH * 2]);
let gas_limit = G1_MSM_BASE_FEE - 1;
assert_eq!(fpvm_bls12_g1_msm(&input, gas_limit), Err(PrecompileError::OutOfGas.into()));
}
}
Loading