-
Notifications
You must be signed in to change notification settings - Fork 596
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(precompile): Prague - EIP-2537 - BLS12-381 curve operations (#1389)
* feat(precompile): add Prague hardfork specification * feat(precompile): add Prague hardfork specification * feat(precompile): BLS12-381 * feature-gate blst * run EIP-2537 tests in CI * fix doc comment * fixes after review (arrays to vecs, question mark operators, default inits) * introduce separate variables for mutable blst calls * return value instead of mutating the input argument where possible * replace *mut with *const where mut is not needed
- Loading branch information
Showing
20 changed files
with
885 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 _) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 _) | ||
} |
Oops, something went wrong.