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
53 changes: 53 additions & 0 deletions crates/precompile/src/bls12_381/g1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use blst::{blst_fp_from_bendian, blst_p1_affine, blst_p1_affine_in_g1};
use revm_primitives::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.
pub(super) 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(out: &mut [u8], input: *const blst_p1_affine) {
// SAFETY: out comes from fixed length array, x and y are blst values.
unsafe {
fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &(*input).x);
fp_to_bytes(&mut out[PADDED_FP_LENGTH..], &(*input).y);
}
}

/// Extracts a G1 point in Affine format from a 128 byte slice representation.
pub(super) fn extract_g1_input(
out: *mut blst_p1_affine,
input: &[u8],
) -> Result<*mut 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 = match remove_padding(&input[..PADDED_FP_LENGTH]) {
Ok(input_p0_x) => input_p0_x,
Err(e) => return Err(e),
};
shekhirin marked this conversation as resolved.
Show resolved Hide resolved
let input_p0_y = match remove_padding(&input[PADDED_FP_LENGTH..G1_INPUT_ITEM_LENGTH]) {
Ok(input_p0_y) => input_p0_y,
Err(e) => return Err(e),
};

// SAFETY: input_p0_x and input_p0_y have fixed length, x and y are blst values.
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(out)
}
66 changes: 66 additions & 0 deletions crates/precompile/src/bls12_381/g1_add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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, G1_OUTPUT_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 mut a_aff: blst_p1_affine = Default::default();
shekhirin marked this conversation as resolved.
Show resolved Hide resolved
let a_aff = extract_g1_input(&mut a_aff, &input[..G1_INPUT_ITEM_LENGTH])?;

let mut b_aff: blst_p1_affine = Default::default();
let b_aff = extract_g1_input(&mut b_aff, &input[G1_INPUT_ITEM_LENGTH..])?;

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

let mut p: blst_p1 = Default::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::default();
// SAFETY: p_aff and p are blst values.
unsafe {
blst_p1_to_affine(&mut p_aff, &p);
}

let mut out = [0u8; G1_OUTPUT_LENGTH];
shekhirin marked this conversation as resolved.
Show resolved Hide resolved
encode_g1_point(&mut out, &p_aff);

Ok((BASE_GAS_FEE, out.into()))
}
81 changes: 81 additions & 0 deletions crates/precompile/src/bls12_381/g1_msm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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_OUTPUT_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 mut p0_aff: blst_p1_affine = Default::default();
let p0_aff = extract_g1_input(
&mut p0_aff,
&input[i * g1_mul::INPUT_LENGTH..i * g1_mul::INPUT_LENGTH + G1_INPUT_ITEM_LENGTH],
)?;
let mut p0: blst_p1 = Default::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::default();
// SAFETY: multiexp_aff and multiexp are blst values.
unsafe {
blst_p1_to_affine(&mut multiexp_aff, &multiexp);
}

let mut out = [0u8; G1_OUTPUT_LENGTH];
encode_g1_point(&mut out, &multiexp_aff);

Ok((required_gas, out.into()))
}
64 changes: 64 additions & 0 deletions crates/precompile/src/bls12_381/g1_mul.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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, G1_OUTPUT_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 mut p0_aff: blst_p1_affine = Default::default();
let p0_aff = extract_g1_input(&mut p0_aff, &input[..G1_INPUT_ITEM_LENGTH])?;
let mut p0: blst_p1 = Default::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::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::default();
// SAFETY: p_aff and p are blst values.
unsafe {
blst_p1_to_affine(&mut p_aff, &p);
}

let mut out = [0u8; G1_OUTPUT_LENGTH];
shekhirin marked this conversation as resolved.
Show resolved Hide resolved
encode_g1_point(&mut out, &p_aff);

Ok((BASE_GAS_FEE, out.into()))
}
66 changes: 66 additions & 0 deletions crates/precompile/src/bls12_381/g2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use blst::{blst_fp_from_bendian, blst_p2_affine, blst_p2_affine_in_g2};
use revm_primitives::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.
pub(super) 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(out: &mut [u8], input: *const blst_p2_affine) {
// 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],
);
}
}

/// Extracts a G2 point in Affine format from a 256 byte slice representation.
pub(super) fn extract_g2_input(
out: *mut blst_p2_affine,
input: &[u8],
) -> Result<*mut 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] =
match remove_padding(&input[i * PADDED_FP_LENGTH..(i + 1) * PADDED_FP_LENGTH]) {
Ok(fp_0) => fp_0,
Err(e) => return Err(e),
};
}

// 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(out)
}
Loading
Loading