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: EIP-2537 - Precompiles for BLS12-381 curve operations #990

Open
wants to merge 12 commits into
base: feat/prague-hard-fork
Choose a base branch
from
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 engine-precompiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ num.workspace = true
ripemd.workspace = true
sha2.workspace = true
sha3.workspace = true
blst = "0.3.13"

[dev-dependencies]
aurora-engine-test-doubles.workspace = true
Expand Down
85 changes: 85 additions & 0 deletions engine-precompiles/src/bls12_381/g1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use super::{fp_from_bendian, fp_to_bytes, remove_padding, PADDED_FP_LENGTH};
use crate::prelude::{vec, Borrowed, Vec};
use blst::{blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve};
use evm::ExitError;

/// Length of each of the elements in a g1 operation input.
pub 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 byte slice with padded elements.
pub fn encode_g1_point(input: *const blst_p1_affine) -> Vec<u8> {
let mut out = vec![0u8; G1_OUTPUT_LENGTH];
// SAFETY: outcomes 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
}

/// Returns a `blst_p1_affine` from the provided byte slices, which represent the x and y
/// affine coordinates of the point.
///
/// If the x or y coordinate do not represent a canonical field element, an error is returned.
///
/// See [`fp_from_bendian`] for more information.
pub fn decode_and_check_g1(p0_x: &[u8; 48], p0_y: &[u8; 48]) -> Result<blst_p1_affine, ExitError> {
let out = blst_p1_affine {
x: fp_from_bendian(p0_x)?,
y: fp_from_bendian(p0_y)?,
};

Ok(out)
}

/// Extracts a G1 point in Affine format from a 128 byte slice representation.
///
/// NOTE: This function will perform a G1 subgroup check if `subgroup_check` is set to `true`.
pub fn extract_g1_input(input: &[u8], subgroup_check: bool) -> Result<blst_p1_affine, ExitError> {
if input.len() != G1_INPUT_ITEM_LENGTH {
return Err(ExitError::Other(Borrowed("ERR_BLS12_G1_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 out = decode_and_check_g1(input_p0_x, input_p0_y)?;

if subgroup_check {
// NB: Subgroup checks
//
// Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
//
// Implementations SHOULD use the optimized subgroup check method:
//
// https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
//
// On any input that fail the subgroup check, the precompile MUST return an error.
//
// As endomorphism acceleration requires input on the correct subgroup, implementers MAY
// use endomorphism acceleration.
if unsafe { !blst_p1_affine_in_g1(&out) } {
return Err(ExitError::Other(Borrowed("ERR_BLS12_ELEMENT_NOT_IN_G1")));
}
} else {
// From EIP-2537:
//
// Error cases:
//
// * An input is neither a point on the G1 elliptic curve nor the infinity point
//
// NB: There is no subgroup check for the G1 addition precompile.
//
// We use blst_p1_affine_on_curve instead of blst_p1_affine_in_g1 because the latter performs
// the subgroup check.
//
// SAFETY: out is a blst value.
if unsafe { !blst_p1_affine_on_curve(&out) } {
return Err(ExitError::Other(Borrowed("ERR_BLS12_ELEMENT_NOT_IN_G1")));
}
}

Ok(out)
}
107 changes: 107 additions & 0 deletions engine-precompiles/src/bls12_381/g1_add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use super::g1;
use crate::prelude::types::{make_address, Address, EthGas};
use crate::prelude::Borrowed;
use crate::{EvmPrecompileResult, Precompile, PrecompileOutput};
use blst::{
blst_p1, blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_from_affine, blst_p1_to_affine,
};
use evm::{Context, ExitError};

/// Base gas fee for BLS12-381 `g1_add` operation.
const BASE_GAS_FEE: u64 = 375;

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

/// BLS12-381 G1 Add
pub struct BlsG1Add;

impl BlsG1Add {
pub const ADDRESS: Address = make_address(0, 0xB);
}

impl Precompile for BlsG1Add {
fn required_gas(_input: &[u8]) -> Result<EthGas, ExitError>
where
Self: Sized,
{
Ok(EthGas::new(BASE_GAS_FEE))
}

/// 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 run(
&self,
input: &[u8],
target_gas: Option<EthGas>,
_context: &Context,
_is_static: bool,
) -> EvmPrecompileResult {
let cost = Self::required_gas(input)?;
if let Some(target_gas) = target_gas {
if cost > target_gas {
return Err(ExitError::OutOfGas);
}
}

if input.len() != INPUT_LENGTH {
return Err(ExitError::Other(Borrowed("ERR_BLS_G1ADD_INPUT_LEN")));
}

// NB: There is no subgroup check for the G1 addition precompile.
//
// We set the subgroup checks here to `false`
let a_aff = &g1::extract_g1_input(&input[..g1::G1_INPUT_ITEM_LENGTH], false)?;
let b_aff = &g1::extract_g1_input(&input[g1::G1_INPUT_ITEM_LENGTH..], false)?;

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 output = g1::encode_g1_point(&p_aff);
Ok(PrecompileOutput::without_logs(cost, output))
}
}

#[cfg(test)]
mod tests {
use super::*;
use aurora_engine_types::H160;

#[test]
fn bls12_381_g1_add() {
let precompile = BlsG1Add;
let ctx = Context {
address: H160::zero(),
caller: H160::zero(),
apparent_value: 0.into(),
};
let input = hex::decode("\
00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb\
0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2\
000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b\
0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95")
.expect("hex decoding failed");

let res = precompile
.run(&input, None, &ctx, false)
.expect("precompile run should not fail");
let expected = hex::decode("\
0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea\
600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6")
.expect("hex decoding failed");

assert_eq!(res.output, expected);
}
}
Loading
Loading