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(precompile): BLS12-381 #1389

Merged
merged 12 commits into from
May 10, 2024
Merged
2 changes: 2 additions & 0 deletions .github/workflows/ethereum-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ jobs:
ethtests/LegacyTests/Constantinople/GeneralStateTests/ \
ethtests/EIPTests/StateTests/stEIP1153-transientStorage/ \
ethtests/EIPTests/StateTests/stEIP4844-blobtransactions/ \
ethtests/EIPTests/StateTests/stEIP2537/ \

1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bins/revme/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ revm = { path = "../../crates/revm", version = "8.0.0", default-features = false
"std",
"serde-json",
"c-kzg",
"blst"
] }
alloy-rlp = { version = "0.3", default-features = false, features = [
"arrayvec",
Expand Down
8 changes: 7 additions & 1 deletion crates/precompile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ secp256k1 = { version = "0.29.0", default-features = false, features = [
"global-context",
], optional = true }

# BLS12-381 precompiles
blst = { version = "0.3.11", optional = true }

[dev-dependencies]
criterion = { version = "0.5" }
rand = { version = "0.8", features = ["std"] }

[features]
default = ["std", "c-kzg", "secp256k1", "portable"]
default = ["std", "c-kzg", "secp256k1", "portable", "blst"]
std = [
"revm-primitives/std",
"k256/std",
Expand Down Expand Up @@ -80,6 +83,9 @@ portable = ["revm-primitives/portable", "c-kzg?/portable"]
# In Linux it passes. If you don't require to build wasm on win/mac, it is safe to use it and it is enabled by default.
secp256k1 = ["dep:secp256k1"]

# Enables the BLS12-381 precompiles.
blst = ["dep:blst"]

[[bench]]
name = "bench"
path = "benches/bench.rs"
Expand Down
48 changes: 48 additions & 0 deletions crates/precompile/src/bls12_381/g1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use blst::{blst_fp_from_bendian, blst_p1_affine, blst_p1_affine_in_g1};
use revm_primitives::{Bytes, PrecompileError};

use super::utils::{fp_to_bytes, remove_padding, PADDED_FP_LENGTH};

/// Length of each of the elements in a g1 operation input.
pub(super) const G1_INPUT_ITEM_LENGTH: usize = 128;
/// Output length of a g1 operation.
const G1_OUTPUT_LENGTH: usize = 128;

/// Encodes a G1 point in affine format into a byte slice with padded elements.
pub(super) fn encode_g1_point(input: *const blst_p1_affine) -> Bytes {
let mut out = vec![0u8; G1_OUTPUT_LENGTH];
// SAFETY: out comes from fixed length array, input is a blst value.
unsafe {
fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &(*input).x);
fp_to_bytes(&mut out[PADDED_FP_LENGTH..], &(*input).y);
}
out.into()
}

/// Extracts a G1 point in Affine format from a 128 byte slice representation.
pub(super) fn extract_g1_input(input: &[u8]) -> Result<*const blst_p1_affine, PrecompileError> {
if input.len() != G1_INPUT_ITEM_LENGTH {
return Err(PrecompileError::Other(format!(
"Input should be {G1_INPUT_ITEM_LENGTH} bits, was {}",
input.len()
)));
}

let input_p0_x = remove_padding(&input[..PADDED_FP_LENGTH])?;
let input_p0_y = remove_padding(&input[PADDED_FP_LENGTH..G1_INPUT_ITEM_LENGTH])?;

let mut out = blst_p1_affine::default();
// SAFETY: input_p0_x and input_p0_y have fixed length, out is a blst value.
unsafe {
blst_fp_from_bendian(&mut out.x, input_p0_x.as_ptr());
blst_fp_from_bendian(&mut out.y, input_p0_y.as_ptr());
}

// SAFETY: out is a blst value.
unsafe {
if !blst_p1_affine_in_g1(&out) {
return Err(PrecompileError::Other("Element not in G1".to_string()));
}
}
Ok(&mut out as *const _)
}
61 changes: 61 additions & 0 deletions crates/precompile/src/bls12_381/g1_add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use blst::{
blst_p1, blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_from_affine, blst_p1_to_affine,
};
use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult};

use crate::{u64_to_address, PrecompileWithAddress};

use super::g1::{encode_g1_point, extract_g1_input, G1_INPUT_ITEM_LENGTH};

/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1ADD precompile.
pub const PRECOMPILE: PrecompileWithAddress =
PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g1_add));
/// BLS12_G1ADD precompile address.
pub const ADDRESS: u64 = 0x0b;
/// Base gas fee for BLS12-381 g1_add operation.
const BASE_GAS_FEE: u64 = 500;

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

/// G1 addition call expects `256` bytes as an input that is interpreted as byte
/// concatenation of two G1 points (`128` bytes each).
/// Output is an encoding of addition operation result - single G1 point (`128`
/// bytes).
/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-addition>
fn g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult {
if BASE_GAS_FEE > gas_limit {
return Err(PrecompileError::OutOfGas);
}

if input.len() != INPUT_LENGTH {
return Err(PrecompileError::Other(format!(
"G1ADD Input should be {INPUT_LENGTH} bits, was {}",
input.len()
)));
}

let a_aff = extract_g1_input(&input[..G1_INPUT_ITEM_LENGTH])?;
let b_aff = extract_g1_input(&input[G1_INPUT_ITEM_LENGTH..])?;

let mut b = blst_p1::default();
// SAFETY: b and b_aff are blst values.
unsafe {
blst_p1_from_affine(&mut b, b_aff);
}

let mut p = blst_p1::default();
// SAFETY: p, b and a_aff are blst values.
unsafe {
blst_p1_add_or_double_affine(&mut p, &b, a_aff);
}

let mut p_aff = blst_p1_affine::default();
// SAFETY: p_aff and p are blst values.
unsafe {
blst_p1_to_affine(&mut p_aff, &p);
}

let out = encode_g1_point(&p_aff);
Ok((BASE_GAS_FEE, out))
}
77 changes: 77 additions & 0 deletions crates/precompile/src/bls12_381/g1_msm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use blst::{blst_p1, blst_p1_affine, blst_p1_from_affine, blst_p1_to_affine, p1_affines};
use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult};

use crate::{u64_to_address, PrecompileWithAddress};

use super::{
g1::{encode_g1_point, extract_g1_input, G1_INPUT_ITEM_LENGTH},
g1_mul,
msm::msm_required_gas,
utils::{extract_scalar_input, NBITS, SCALAR_LENGTH},
};

/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MSM precompile.
pub const PRECOMPILE: PrecompileWithAddress =
PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g1_msm));
/// BLS12_G1MSM precompile address.
pub const ADDRESS: u64 = 0x0d;

/// Implements EIP-2537 G1MSM precompile.
/// G1 multi-scalar-multiplication call expects `160*k` bytes as an input that is interpreted
/// as byte concatenation of `k` slices each of them being a byte concatenation
/// of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32`
/// bytes).
/// Output is an encoding of multi-scalar-multiplication operation result - single G1
/// point (`128` bytes).
/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-multiexponentiation>
fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult {
let input_len = input.len();
if input_len == 0 || input_len % g1_mul::INPUT_LENGTH != 0 {
return Err(PrecompileError::Other(format!(
"G1MSM input length should be multiple of {}, was {}",
g1_mul::INPUT_LENGTH,
input_len
)));
}

let k = input_len / g1_mul::INPUT_LENGTH;
let required_gas = msm_required_gas(k, g1_mul::BASE_GAS_FEE);
if required_gas > gas_limit {
return Err(PrecompileError::OutOfGas);
}

let mut g1_points: Vec<blst_p1> = Vec::with_capacity(k);
let mut scalars: Vec<u8> = Vec::with_capacity(k * SCALAR_LENGTH);
for i in 0..k {
let p0_aff = extract_g1_input(
&input[i * g1_mul::INPUT_LENGTH..i * g1_mul::INPUT_LENGTH + G1_INPUT_ITEM_LENGTH],
)?;
let mut p0 = blst_p1::default();
// SAFETY: p0 and p0_aff are blst values.
unsafe {
blst_p1_from_affine(&mut p0, p0_aff);
}

g1_points.push(p0);

scalars.extend_from_slice(
&extract_scalar_input(
&input[i * g1_mul::INPUT_LENGTH + G1_INPUT_ITEM_LENGTH
..i * g1_mul::INPUT_LENGTH + G1_INPUT_ITEM_LENGTH + SCALAR_LENGTH],
)?
.b,
);
}

let points = p1_affines::from(&g1_points);
let multiexp = points.mult(&scalars, NBITS);

let mut multiexp_aff = blst_p1_affine::default();
// SAFETY: multiexp_aff and multiexp are blst values.
unsafe {
blst_p1_to_affine(&mut multiexp_aff, &multiexp);
}

let out = encode_g1_point(&multiexp_aff);
Ok((required_gas, out))
}
61 changes: 61 additions & 0 deletions crates/precompile/src/bls12_381/g1_mul.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use blst::{blst_p1, blst_p1_affine, blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine};
use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult};

use crate::{u64_to_address, PrecompileWithAddress};

use super::{
g1::{encode_g1_point, extract_g1_input, G1_INPUT_ITEM_LENGTH},
utils::{extract_scalar_input, NBITS},
};

/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MUL precompile.
pub const PRECOMPILE: PrecompileWithAddress =
PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g1_mul));
/// BLS12_G1MUL precompile address.
pub const ADDRESS: u64 = 0x0c;
/// Base gas fee for BLS12-381 g1_mul operation.
pub(super) const BASE_GAS_FEE: u64 = 12000;

/// Input length of g1_mul operation.
pub(super) const INPUT_LENGTH: usize = 160;

/// G1 multiplication call expects `160` bytes as an input that is interpreted as
/// byte concatenation of encoding of G1 point (`128` bytes) and encoding of a
/// scalar value (`32` bytes).
/// Output is an encoding of multiplication operation result - single G1 point
/// (`128` bytes).
/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-multiplication>
pub fn g1_mul(input: &Bytes, gas_limit: u64) -> PrecompileResult {
if BASE_GAS_FEE > gas_limit {
return Err(PrecompileError::OutOfGas);
}
if input.len() != INPUT_LENGTH {
return Err(PrecompileError::Other(format!(
"G1MUL Input should be {INPUT_LENGTH} bits, was {}",
input.len()
)));
}

let p0_aff = extract_g1_input(&input[..G1_INPUT_ITEM_LENGTH])?;
let mut p0 = blst_p1::default();
// SAFETY: p0 and p0_aff are blst values.
unsafe {
blst_p1_from_affine(&mut p0, p0_aff);
}

let input_scalar0 = extract_scalar_input(&input[G1_INPUT_ITEM_LENGTH..])?;

let mut p = blst_p1::default();
// SAFETY: input_scalar0.b has fixed size, p and p0 are blst values.
unsafe {
blst_p1_mult(&mut p, &p0, input_scalar0.b.as_ptr(), NBITS);
}
let mut p_aff = blst_p1_affine::default();
// SAFETY: p_aff and p are blst values.
unsafe {
blst_p1_to_affine(&mut p_aff, &p);
}

let out = encode_g1_point(&p_aff);
Ok((BASE_GAS_FEE, out))
}
64 changes: 64 additions & 0 deletions crates/precompile/src/bls12_381/g2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use blst::{blst_fp_from_bendian, blst_p2_affine, blst_p2_affine_in_g2};
use revm_primitives::{Bytes, PrecompileError};

use super::utils::{fp_to_bytes, remove_padding, FP_LENGTH, PADDED_FP_LENGTH};

/// Length of each of the elements in a g2 operation input.
pub(super) const G2_INPUT_ITEM_LENGTH: usize = 256;
/// Output length of a g2 operation.
const G2_OUTPUT_LENGTH: usize = 256;

/// Encodes a G2 point in affine format into a byte slice with padded elements.
pub(super) fn encode_g2_point(input: *const blst_p2_affine) -> Bytes {
let mut out = vec![0u8; G2_OUTPUT_LENGTH];
// SAFETY: out comes from fixed length array, input is a blst value.
unsafe {
fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &(*input).x.fp[0]);
fp_to_bytes(
&mut out[PADDED_FP_LENGTH..2 * PADDED_FP_LENGTH],
&(*input).x.fp[1],
);
fp_to_bytes(
&mut out[2 * PADDED_FP_LENGTH..3 * PADDED_FP_LENGTH],
&(*input).y.fp[0],
);
fp_to_bytes(
&mut out[3 * PADDED_FP_LENGTH..4 * PADDED_FP_LENGTH],
&(*input).y.fp[1],
);
}
out.into()
}

/// Extracts a G2 point in Affine format from a 256 byte slice representation.
pub(super) fn extract_g2_input(input: &[u8]) -> Result<*const blst_p2_affine, PrecompileError> {
if input.len() != G2_INPUT_ITEM_LENGTH {
return Err(PrecompileError::Other(format!(
"Input should be {G2_INPUT_ITEM_LENGTH} bits, was {}",
input.len()
)));
}

let mut input_fps: [[u8; FP_LENGTH]; 4] = [[0; FP_LENGTH]; 4];
for i in 0..4 {
input_fps[i] = remove_padding(&input[i * PADDED_FP_LENGTH..(i + 1) * PADDED_FP_LENGTH])?;
}

let mut out = blst_p2_affine::default();
// SAFETY: items in fps have fixed length, out is a blst value.
unsafe {
blst_fp_from_bendian(&mut out.x.fp[0], input_fps[0].as_ptr());
blst_fp_from_bendian(&mut out.x.fp[1], input_fps[1].as_ptr());
blst_fp_from_bendian(&mut out.y.fp[0], input_fps[2].as_ptr());
blst_fp_from_bendian(&mut out.y.fp[1], input_fps[3].as_ptr());
}

// SAFETY: out is a blst value.
unsafe {
if !blst_p2_affine_in_g2(&out) {
return Err(PrecompileError::Other("Element not in G2".to_string()));
}
}

Ok(&mut out as *const _)
}
Loading
Loading