diff --git a/.github/workflows/ethereum-tests.yml b/.github/workflows/ethereum-tests.yml index 3b972f7721..c4c1c8dfbc 100644 --- a/.github/workflows/ethereum-tests.yml +++ b/.github/workflows/ethereum-tests.yml @@ -47,3 +47,5 @@ jobs: ethtests/LegacyTests/Constantinople/GeneralStateTests/ \ ethtests/EIPTests/StateTests/stEIP1153-transientStorage/ \ ethtests/EIPTests/StateTests/stEIP4844-blobtransactions/ \ + ethtests/EIPTests/StateTests/stEIP2537/ \ + diff --git a/Cargo.lock b/Cargo.lock index 9c1edc6c9f..b074209e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2963,6 +2963,7 @@ name = "revm-precompile" version = "6.0.0" dependencies = [ "aurora-engine-modexp", + "blst", "c-kzg", "criterion", "k256", diff --git a/bins/revme/Cargo.toml b/bins/revme/Cargo.toml index 95290714c3..84a695d7ca 100644 --- a/bins/revme/Cargo.toml +++ b/bins/revme/Cargo.toml @@ -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", diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 14c009d7d6..a2f4e86373 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -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", @@ -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" diff --git a/crates/precompile/src/bls12_381/g1.rs b/crates/precompile/src/bls12_381/g1.rs new file mode 100644 index 0000000000..28e5a845d4 --- /dev/null +++ b/crates/precompile/src/bls12_381/g1.rs @@ -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 _) +} diff --git a/crates/precompile/src/bls12_381/g1_add.rs b/crates/precompile/src/bls12_381/g1_add.rs new file mode 100644 index 0000000000..e3adba016a --- /dev/null +++ b/crates/precompile/src/bls12_381/g1_add.rs @@ -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: +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)) +} diff --git a/crates/precompile/src/bls12_381/g1_msm.rs b/crates/precompile/src/bls12_381/g1_msm.rs new file mode 100644 index 0000000000..a339d86039 --- /dev/null +++ b/crates/precompile/src/bls12_381/g1_msm.rs @@ -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: +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 = Vec::with_capacity(k); + let mut scalars: Vec = 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)) +} diff --git a/crates/precompile/src/bls12_381/g1_mul.rs b/crates/precompile/src/bls12_381/g1_mul.rs new file mode 100644 index 0000000000..171de875c8 --- /dev/null +++ b/crates/precompile/src/bls12_381/g1_mul.rs @@ -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: +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)) +} diff --git a/crates/precompile/src/bls12_381/g2.rs b/crates/precompile/src/bls12_381/g2.rs new file mode 100644 index 0000000000..0c861e7f7d --- /dev/null +++ b/crates/precompile/src/bls12_381/g2.rs @@ -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 _) +} diff --git a/crates/precompile/src/bls12_381/g2_add.rs b/crates/precompile/src/bls12_381/g2_add.rs new file mode 100644 index 0000000000..2d7e95edf6 --- /dev/null +++ b/crates/precompile/src/bls12_381/g2_add.rs @@ -0,0 +1,62 @@ +use blst::{ + blst_p2, blst_p2_add_or_double_affine, blst_p2_affine, blst_p2_from_affine, blst_p2_to_affine, +}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::g2::{encode_g2_point, extract_g2_input, G2_INPUT_ITEM_LENGTH}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2ADD precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g2_add)); +/// BLS12_G2ADD precompile address. +pub const ADDRESS: u64 = 0x0e; +/// Base gas fee for BLS12-381 g2_add operation. +const BASE_GAS_FEE: u64 = 800; + +/// Input length of g2_add operation. +const INPUT_LENGTH: usize = 512; + +/// G2 addition call expects `512` bytes as an input that is interpreted as byte +/// concatenation of two G2 points (`256` bytes each). +/// +/// Output is an encoding of addition operation result - single G2 point (`256` +/// bytes). +/// See also +fn g2_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!( + "G2ADD Input should be {INPUT_LENGTH} bits, was {}", + input.len() + ))); + } + + let a_aff = extract_g2_input(&input[..G2_INPUT_ITEM_LENGTH])?; + let b_aff = extract_g2_input(&input[G2_INPUT_ITEM_LENGTH..])?; + + let mut b = blst_p2::default(); + // SAFETY: b and b_aff are blst values. + unsafe { + blst_p2_from_affine(&mut b, b_aff); + } + + let mut p = blst_p2::default(); + // SAFETY: p, b and a_aff are blst values. + unsafe { + blst_p2_add_or_double_affine(&mut p, &b, a_aff); + } + + let mut p_aff = blst_p2_affine::default(); + // SAFETY: p_aff and p are blst values. + unsafe { + blst_p2_to_affine(&mut p_aff, &p); + } + + let out = encode_g2_point(&p_aff); + Ok((BASE_GAS_FEE, out)) +} diff --git a/crates/precompile/src/bls12_381/g2_msm.rs b/crates/precompile/src/bls12_381/g2_msm.rs new file mode 100644 index 0000000000..8ea82c2024 --- /dev/null +++ b/crates/precompile/src/bls12_381/g2_msm.rs @@ -0,0 +1,77 @@ +use blst::{blst_p2, blst_p2_affine, blst_p2_from_affine, blst_p2_to_affine, p2_affines}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::{ + g2::{encode_g2_point, extract_g2_input, G2_INPUT_ITEM_LENGTH}, + g2_mul, + msm::msm_required_gas, + utils::{extract_scalar_input, NBITS, SCALAR_LENGTH}, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MSM precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g2_msm)); +/// BLS12_G2MSM precompile address. +pub const ADDRESS: u64 = 0x10; + +/// Implements EIP-2537 G2MSM precompile. +/// G2 multi-scalar-multiplication call expects `288*k` bytes as an input that is interpreted +/// as byte concatenation of `k` slices each of them being a byte concatenation +/// of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` +/// bytes). +/// Output is an encoding of multi-scalar-multiplication operation result - single G2 +/// point (`256` bytes). +/// See also: +fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let input_len = input.len(); + if input_len == 0 || input_len % g2_mul::INPUT_LENGTH != 0 { + return Err(PrecompileError::Other(format!( + "G2MSM input length should be multiple of {}, was {}", + g2_mul::INPUT_LENGTH, + input_len + ))); + } + + let k = input_len / g2_mul::INPUT_LENGTH; + let required_gas = msm_required_gas(k, g2_mul::BASE_GAS_FEE); + if required_gas > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + let mut g2_points: Vec = Vec::with_capacity(k); + let mut scalars: Vec = Vec::with_capacity(k * SCALAR_LENGTH); + for i in 0..k { + let p0_aff = extract_g2_input( + &input[i * g2_mul::INPUT_LENGTH..i * g2_mul::INPUT_LENGTH + G2_INPUT_ITEM_LENGTH], + )?; + let mut p0 = blst_p2::default(); + // SAFETY: p0 and p0_aff are blst values. + unsafe { + blst_p2_from_affine(&mut p0, p0_aff); + } + + g2_points.push(p0); + + scalars.extend_from_slice( + &extract_scalar_input( + &input[i * g2_mul::INPUT_LENGTH + G2_INPUT_ITEM_LENGTH + ..i * g2_mul::INPUT_LENGTH + G2_INPUT_ITEM_LENGTH + SCALAR_LENGTH], + )? + .b, + ); + } + + let points = p2_affines::from(&g2_points); + let multiexp = points.mult(&scalars, NBITS); + + let mut multiexp_aff = blst_p2_affine::default(); + // SAFETY: multiexp_aff and multiexp are blst values. + unsafe { + blst_p2_to_affine(&mut multiexp_aff, &multiexp); + } + + let out = encode_g2_point(&multiexp_aff); + Ok((required_gas, out)) +} diff --git a/crates/precompile/src/bls12_381/g2_mul.rs b/crates/precompile/src/bls12_381/g2_mul.rs new file mode 100644 index 0000000000..be3f4f9f14 --- /dev/null +++ b/crates/precompile/src/bls12_381/g2_mul.rs @@ -0,0 +1,61 @@ +use blst::{blst_p2, blst_p2_affine, blst_p2_from_affine, blst_p2_mult, blst_p2_to_affine}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::{ + g2::{encode_g2_point, extract_g2_input, G2_INPUT_ITEM_LENGTH}, + utils::{extract_scalar_input, NBITS}, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MUL precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(g2_mul)); +/// BLS12_G2MUL precompile address. +pub const ADDRESS: u64 = 0x0f; +/// Base gas fee for BLS12-381 g2_mul operation. +pub(super) const BASE_GAS_FEE: u64 = 45000; + +/// Input length of g2_mul operation. +pub(super) const INPUT_LENGTH: usize = 288; + +/// G2 multiplication call expects `288` bytes as an input that is interpreted as +/// byte concatenation of encoding of G2 point (`256` bytes) and encoding of a +/// scalar value (`32` bytes). +/// Output is an encoding of multiplication operation result - single G2 point +/// (`256` bytes). +/// See also: +fn g2_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!( + "G2MUL Input should be {INPUT_LENGTH} bits, was {}", + input.len() + ))); + } + + let p0_aff = extract_g2_input(&input[..G2_INPUT_ITEM_LENGTH])?; + let mut p0 = blst_p2::default(); + // SAFETY: p0 and p0_aff are blst values. + unsafe { + blst_p2_from_affine(&mut p0, p0_aff); + } + + let input_scalar0 = extract_scalar_input(&input[G2_INPUT_ITEM_LENGTH..])?; + + let mut p = blst_p2::default(); + // SAFETY: input_scalar0.b has fixed size, p and p0 are blst values. + unsafe { + blst_p2_mult(&mut p, &p0, input_scalar0.b.as_ptr(), NBITS); + } + let mut p_aff = blst_p2_affine::default(); + // SAFETY: p_aff and p are blst values. + unsafe { + blst_p2_to_affine(&mut p_aff, &p); + } + + let out = encode_g2_point(&p_aff); + Ok((BASE_GAS_FEE, out)) +} diff --git a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs new file mode 100644 index 0000000000..6448dda6f6 --- /dev/null +++ b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs @@ -0,0 +1,70 @@ +use blst::{ + blst_fp, blst_fp2, blst_fp_from_bendian, blst_map_to_g2, blst_p2, blst_p2_affine, + blst_p2_to_affine, +}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::{ + g2::encode_g2_point, + utils::{remove_padding, PADDED_FP2_LENGTH, PADDED_FP_LENGTH}, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP2_TO_G2 precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(map_fp2_to_g2)); +/// BLS12_MAP_FP2_TO_G2 precompile address. +pub const ADDRESS: u64 = 0x13; +/// Base gas fee for BLS12-381 map_fp2_to_g2 operation. +const BASE_GAS_FEE: u64 = 75000; + +/// Field-to-curve call expects 128 bytes as an input that is interpreted as a +/// an element of Fp2. Output of this call is 256 bytes and is an encoded G2 +/// point. +/// See also: +fn map_fp2_to_g2(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if BASE_GAS_FEE > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + if input.len() != PADDED_FP2_LENGTH { + return Err(PrecompileError::Other(format!( + "MAP_FP2_TO_G2 Input should be {PADDED_FP2_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..PADDED_FP2_LENGTH])?; + + let mut fp2 = blst_fp2::default(); + let mut fp_x = blst_fp::default(); + let mut fp_y = blst_fp::default(); + // SAFETY: input_p0_x has fixed length, fp_x is a blst value. + unsafe { + blst_fp_from_bendian(&mut fp_x, input_p0_x.as_ptr()); + } + // SAFETY: input_p0_y has fixed length, fp_y is a blst value. + unsafe { + blst_fp_from_bendian(&mut fp_y, input_p0_y.as_ptr()); + } + fp2.fp[0] = fp_x; + fp2.fp[1] = fp_y; + + let mut p = blst_p2::default(); + // SAFETY: p and fp2 are blst values. + unsafe { + // third argument is unused if null. + blst_map_to_g2(&mut p, &fp2, std::ptr::null()); + } + + let mut p_aff = blst_p2_affine::default(); + // SAFETY: p_aff and p are blst values. + unsafe { + blst_p2_to_affine(&mut p_aff, &p); + } + + let out = encode_g2_point(&p_aff); + Ok((BASE_GAS_FEE, out)) +} diff --git a/crates/precompile/src/bls12_381/map_fp_to_g1.rs b/crates/precompile/src/bls12_381/map_fp_to_g1.rs new file mode 100644 index 0000000000..f0d273f3bf --- /dev/null +++ b/crates/precompile/src/bls12_381/map_fp_to_g1.rs @@ -0,0 +1,60 @@ +use blst::{ + blst_fp, blst_fp_from_bendian, blst_map_to_g1, blst_p1, blst_p1_affine, blst_p1_to_affine, +}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::{ + g1::encode_g1_point, + utils::{remove_padding, PADDED_FP_LENGTH}, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_MAP_FP_TO_G1 precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(map_fp_to_g1)); +/// BLS12_MAP_FP_TO_G1 precompile address. +pub const ADDRESS: u64 = 0x12; +/// Base gas fee for BLS12-381 map_fp_to_g1 operation. +const MAP_FP_TO_G1_BASE: u64 = 5500; + +/// Field-to-curve call expects 64 bytes as an input that is interpreted as an +/// element of Fp. Output of this call is 128 bytes and is an encoded G1 point. +/// See also: +fn map_fp_to_g1(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if MAP_FP_TO_G1_BASE > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + if input.len() != PADDED_FP_LENGTH { + return Err(PrecompileError::Other(format!( + "MAP_FP_TO_G1 Input should be {PADDED_FP_LENGTH} bits, was {}", + input.len() + ))); + } + + let input_p0 = remove_padding(input)?; + + let mut fp = blst_fp::default(); + + // SAFETY: input_p0 has fixed length, fp is a blst value. + unsafe { + blst_fp_from_bendian(&mut fp, input_p0.as_ptr()); + } + + let mut p = blst_p1::default(); + // SAFETY: p and fp are blst values. + unsafe { + // third argument is unused if null. + blst_map_to_g1(&mut p, &fp, std::ptr::null()); + } + + 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((MAP_FP_TO_G1_BASE, out)) +} diff --git a/crates/precompile/src/bls12_381/mod.rs b/crates/precompile/src/bls12_381/mod.rs new file mode 100644 index 0000000000..764c4a50a1 --- /dev/null +++ b/crates/precompile/src/bls12_381/mod.rs @@ -0,0 +1,31 @@ +use crate::PrecompileWithAddress; + +mod g1; +pub mod g1_add; +pub mod g1_msm; +pub mod g1_mul; +mod g2; +pub mod g2_add; +pub mod g2_msm; +pub mod g2_mul; +pub mod map_fp2_to_g2; +pub mod map_fp_to_g1; +mod msm; +pub mod pairing; +mod utils; + +/// Returns the BLS12-381 precompiles with their addresses. +pub fn precompiles() -> impl Iterator { + [ + g1_add::PRECOMPILE, + g1_mul::PRECOMPILE, + g1_msm::PRECOMPILE, + g2_add::PRECOMPILE, + g2_mul::PRECOMPILE, + g2_msm::PRECOMPILE, + pairing::PRECOMPILE, + map_fp_to_g1::PRECOMPILE, + map_fp2_to_g2::PRECOMPILE, + ] + .into_iter() +} diff --git a/crates/precompile/src/bls12_381/msm.rs b/crates/precompile/src/bls12_381/msm.rs new file mode 100644 index 0000000000..e26ea3ebb2 --- /dev/null +++ b/crates/precompile/src/bls12_381/msm.rs @@ -0,0 +1,28 @@ +/// Amount used to calculate the multi-scalar-multiplication discount. +const MSM_MULTIPLIER: u64 = 1000; +/// Table of gas discounts for multi-scalar-multiplication operations. +const MSM_DISCOUNT_TABLE: [u64; 128] = [ + 1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, + 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, + 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, + 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, + 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, + 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, + 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174, +]; + +/// Implements the gas schedule for G1/G2 Multiscalar-multiplication assuming 30 +/// MGas/second, see also: +pub(super) fn msm_required_gas(k: usize, multiplication_cost: u64) -> u64 { + if k == 0 { + return 0; + } + + let discount = if k < MSM_DISCOUNT_TABLE.len() { + MSM_DISCOUNT_TABLE[k - 1] + } else { + MSM_DISCOUNT_TABLE[MSM_DISCOUNT_TABLE.len() - 1] + }; + + (k as u64 * discount * multiplication_cost) / MSM_MULTIPLIER +} diff --git a/crates/precompile/src/bls12_381/pairing.rs b/crates/precompile/src/bls12_381/pairing.rs new file mode 100644 index 0000000000..2cd5cf9a6d --- /dev/null +++ b/crates/precompile/src/bls12_381/pairing.rs @@ -0,0 +1,95 @@ +use blst::{blst_final_exp, blst_fp12, blst_fp12_is_one, blst_fp12_mul, blst_miller_loop}; +use revm_primitives::{Bytes, Precompile, PrecompileError, PrecompileResult, B256}; + +use crate::{u64_to_address, PrecompileWithAddress}; + +use super::{ + g1::{extract_g1_input, G1_INPUT_ITEM_LENGTH}, + g2::{extract_g2_input, G2_INPUT_ITEM_LENGTH}, +}; + +/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_PAIRING precompile. +pub const PRECOMPILE: PrecompileWithAddress = + PrecompileWithAddress(u64_to_address(ADDRESS), Precompile::Standard(pairing)); +/// BLS12_PAIRING precompile address. +pub const ADDRESS: u64 = 0x11; + +/// Multiplier gas fee for BLS12-381 pairing operation. +const PAIRING_MULTIPLIER_BASE: u64 = 43000; +/// Offset gas fee for BLS12-381 pairing operation. +const PAIRING_OFFSET_BASE: u64 = 65000; +/// Input length of paitring operation. +const INPUT_LENGTH: usize = 384; + +/// Pairing call expects 384*k (k being a positive integer) bytes as an inputs +/// that is interpreted as byte concatenation of k slices. Each slice has the +/// following structure: +/// * 128 bytes of G1 point encoding +/// * 256 bytes of G2 point encoding +/// Each point is expected to be in the subgroup of order q. +/// Output is a 32 bytes where first 31 bytes are equal to 0x00 and the last byte +/// is 0x01 if pairing result is equal to the multiplicative identity in a pairing +/// target field and 0x00 otherwise. +/// See also: +fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let input_len = input.len(); + if input_len == 0 || input_len % INPUT_LENGTH != 0 { + return Err(PrecompileError::Other(format!( + "Pairing input length should be multiple of {INPUT_LENGTH}, was {input_len}" + ))); + } + + let k = input_len / INPUT_LENGTH; + let required_gas: u64 = PAIRING_MULTIPLIER_BASE * k as u64 + PAIRING_OFFSET_BASE; + if required_gas > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + // accumulator for the fp12 multiplications of the miller loops. + let mut acc = blst_fp12::default(); + for i in 0..k { + let p1_aff = + extract_g1_input(&input[i * INPUT_LENGTH..i * INPUT_LENGTH + G1_INPUT_ITEM_LENGTH])?; + + let p2_aff = extract_g2_input( + &input[i * INPUT_LENGTH + G1_INPUT_ITEM_LENGTH + ..i * INPUT_LENGTH + G1_INPUT_ITEM_LENGTH + G2_INPUT_ITEM_LENGTH], + )?; + + if i > 0 { + // after the first slice (i>0) we use cur_ml to store the current + // miller loop and accumulate with the previous results using a fp12 + // multiplication. + let mut cur_ml = blst_fp12::default(); + let mut res = blst_fp12::default(); + // SAFETY: res, acc, cur_ml, p1_aff and p2_aff are blst values. + unsafe { + blst_miller_loop(&mut cur_ml, p2_aff, p1_aff); + blst_fp12_mul(&mut res, &acc, &cur_ml); + } + acc = res; + } else { + // on the first slice (i==0) there is no previous results and no need + // to accumulate. + // SAFETY: acc, p1_aff and p2_aff are blst values. + unsafe { + blst_miller_loop(&mut acc, p2_aff, p1_aff); + } + } + } + + // SAFETY: ret and acc are blst values. + let mut ret = blst_fp12::default(); + unsafe { + blst_final_exp(&mut ret, &acc); + } + + let mut result: u8 = 0; + // SAFETY: ret is a blst value. + unsafe { + if blst_fp12_is_one(&ret) { + result = 1; + } + } + Ok((required_gas, B256::with_last_byte(result).into())) +} diff --git a/crates/precompile/src/bls12_381/utils.rs b/crates/precompile/src/bls12_381/utils.rs new file mode 100644 index 0000000000..38673d66b7 --- /dev/null +++ b/crates/precompile/src/bls12_381/utils.rs @@ -0,0 +1,65 @@ +use blst::{blst_bendian_from_fp, blst_fp, blst_scalar, blst_scalar_from_bendian}; +use revm_primitives::PrecompileError; + +/// Number of bits used in the BLS12-381 curve finite field elements. +pub(super) const NBITS: usize = 256; +/// Finite field element input length. +pub(super) const FP_LENGTH: usize = 48; +/// Finite field element padded input length. +pub(super) const PADDED_FP_LENGTH: usize = 64; +/// Quadratic extension of finite field element input length. +pub(super) const PADDED_FP2_LENGTH: usize = 128; +/// Input elements padding length. +pub(super) const PADDING_LENGTH: usize = 16; +/// Scalar length. +pub(super) const SCALAR_LENGTH: usize = 32; + +/// Encodes a single finite field element into a byte slice with padding. +pub(super) fn fp_to_bytes(out: &mut [u8], input: *const blst_fp) { + if out.len() != PADDED_FP_LENGTH { + return; + } + for item in out.iter_mut().take(PADDING_LENGTH) { + *item = 0; + } + // SAFETY: out length is checked previously, input is a blst value. + unsafe { + blst_bendian_from_fp(out[PADDING_LENGTH..].as_mut_ptr(), input); + } +} + +/// Removes zeros with which the precompile inputs are left padded to 64 bytes. +pub(super) fn remove_padding(input: &[u8]) -> Result<[u8; FP_LENGTH], PrecompileError> { + if input.len() != PADDED_FP_LENGTH { + return Err(PrecompileError::Other(format!( + "Padded Input should be {PADDED_FP_LENGTH} bits, was {}", + input.len() + ))); + } + if !input.iter().take(PADDING_LENGTH).all(|&x| x == 0) { + return Err(PrecompileError::Other(format!( + "{PADDING_LENGTH} top bytes of input are not zero", + ))); + } + + let sliced = &input[PADDING_LENGTH..PADDED_FP_LENGTH]; + <[u8; FP_LENGTH]>::try_from(sliced).map_err(|e| PrecompileError::Other(format!("{e}"))) +} + +/// Extracts an Scalar from a 32 byte slice representation. +pub(super) fn extract_scalar_input(input: &[u8]) -> Result { + if input.len() != SCALAR_LENGTH { + return Err(PrecompileError::Other(format!( + "Input should be {SCALAR_LENGTH} bits, was {}", + input.len() + ))); + } + + let mut out = blst_scalar::default(); + // SAFETY: input length is checked previously, out is a blst value. + unsafe { + blst_scalar_from_bendian(&mut out, input.as_ptr()); + } + + Ok(out) +} diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index 7903d38b27..97c4282daa 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -9,6 +9,8 @@ extern crate alloc as std; pub mod blake2; +#[cfg(feature = "blst")] +pub mod bls12_381; pub mod bn128; pub mod hash; pub mod identity; @@ -159,10 +161,16 @@ impl Precompiles { pub fn prague() -> &'static Self { static INSTANCE: OnceBox = OnceBox::new(); INSTANCE.get_or_init(|| { - let precompiles = Self::cancun().clone(); - // EIP-2537: Precompile for BLS12-381 curve operations - // TODO(alexey): add BLS12-381 precompiles - // precompiles.extend(bls12_381::precompiles()); + let precompiles = Self::berlin().clone(); + + // Don't include BLS12-381 precompiles in no_std builds. + #[cfg(feature = "blst")] + let precompiles = { + let mut precompiles = precompiles; + precompiles.extend(bls12_381::precompiles()); + precompiles + }; + Box::new(precompiles) }) } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 7ddb717591..9b8446ad26 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -70,7 +70,7 @@ alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d alloy-transport-http = { git = "https://github.com/alloy-rs/alloy.git", rev = "44b8a6d" } [features] -default = ["std", "c-kzg", "secp256k1", "portable"] +default = ["std", "c-kzg", "secp256k1", "portable", "blst"] std = [ "serde?/std", "serde_json?/std", @@ -134,6 +134,7 @@ optional_beneficiary_reward = ["revm-interpreter/optional_beneficiary_reward"] # See comments in `revm-precompile` secp256k1 = ["revm-precompile/secp256k1"] c-kzg = ["revm-precompile/c-kzg"] +blst = ["revm-precompile/blst"] [[example]] name = "fork_ref_transact"