Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/optimism/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,8 @@ mod tests {
#[test]
#[cfg(feature = "blst")]
fn test_halted_tx_call_bls12_381_pairing_out_of_gas() {
let pairing_gas: u64 = bls12_381_const::PAIRING_PAIRING_MULTIPLIER_BASE
+ bls12_381_const::PAIRING_PAIRING_OFFSET_BASE;
let pairing_gas: u64 =
bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE;

let ctx = Context::op()
.modify_tx_chained(|tx| {
Expand Down Expand Up @@ -583,8 +583,8 @@ mod tests {
#[test]
#[cfg(feature = "blst")]
fn test_tx_call_bls12_381_pairing_wrong_input_layout() {
let pairing_gas: u64 = bls12_381_const::PAIRING_PAIRING_MULTIPLIER_BASE
+ bls12_381_const::PAIRING_PAIRING_OFFSET_BASE;
let pairing_gas: u64 =
bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE;

let ctx = Context::op()
.modify_tx_chained(|tx| {
Expand Down
1 change: 1 addition & 0 deletions crates/precompile/src/bls12_381.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::PrecompileWithAddress;

mod blst;
mod g1;
pub mod g1_add;
pub mod g1_msm;
Expand Down
265 changes: 265 additions & 0 deletions crates/precompile/src/bls12_381/blst.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// This module contains a safe wrapper around the blst library.

use crate::bls12_381_const::SCALAR_LENGTH;
use blst::{
blst_final_exp, blst_fp, blst_fp12, blst_fp12_is_one, blst_fp12_mul, blst_fp2, blst_map_to_g1,
blst_map_to_g2, blst_miller_loop, blst_p1, blst_p1_add_or_double_affine, blst_p1_affine,
blst_p1_from_affine, blst_p1_to_affine, blst_p2, blst_p2_add_or_double_affine, blst_p2_affine,
blst_p2_from_affine, blst_p2_to_affine, MultiPoint,
};

#[inline]
fn p1_to_affine(p: &blst_p1) -> blst_p1_affine {
let mut p_affine = blst_p1_affine::default();
// SAFETY: both inputs are valid blst types
unsafe { blst_p1_to_affine(&mut p_affine, p) };
p_affine
}

#[inline]
fn p1_from_affine(p_affine: &blst_p1_affine) -> blst_p1 {
let mut p = blst_p1::default();
// SAFETY: both inputs are valid blst types
unsafe { blst_p1_from_affine(&mut p, p_affine) };
p
}

#[inline]
fn p1_add_or_double(p: &blst_p1, p_affine: &blst_p1_affine) -> blst_p1 {
let mut result = blst_p1::default();
// SAFETY: all inputs are valid blst types
unsafe { blst_p1_add_or_double_affine(&mut result, p, p_affine) };
result
}

#[inline]
fn p2_to_affine(p: &blst_p2) -> blst_p2_affine {
let mut p_affine = blst_p2_affine::default();
// SAFETY: both inputs are valid blst types
unsafe { blst_p2_to_affine(&mut p_affine, p) };
p_affine
}

#[inline]
fn p2_from_affine(p_affine: &blst_p2_affine) -> blst_p2 {
let mut p = blst_p2::default();
// SAFETY: both inputs are valid blst types
unsafe { blst_p2_from_affine(&mut p, p_affine) };
p
}

#[inline]
fn p2_add_or_double(p: &blst_p2, p_affine: &blst_p2_affine) -> blst_p2 {
let mut result = blst_p2::default();
// SAFETY: all inputs are valid blst types
unsafe { blst_p2_add_or_double_affine(&mut result, p, p_affine) };
result
}

/// p1_add_affine adds two G1 points in affine form, returning the result in affine form
///
/// Note: `a` and `b` can be the same, ie this method is safe to call if one wants
/// to essentially double a point
#[inline]
pub(super) fn p1_add_affine(a: &blst_p1_affine, b: &blst_p1_affine) -> blst_p1_affine {
// Convert first point to Jacobian coordinates
let a_jacobian = p1_from_affine(a);

// Add second point (in affine) to first point (in Jacobian)
let sum_jacobian = p1_add_or_double(&a_jacobian, b);

// Convert result back to affine coordinates
p1_to_affine(&sum_jacobian)
}

/// Add two G2 points in affine form, returning the result in affine form
#[inline]
pub(super) fn p2_add_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_affine {
// Convert first point to Jacobian coordinates
let a_jacobian = p2_from_affine(a);

// Add second point (in affine) to first point (in Jacobian)
let sum_jacobian = p2_add_or_double(&a_jacobian, b);

// Convert result back to affine coordinates
p2_to_affine(&sum_jacobian)
}

/// Performs multi-scalar multiplication (MSM) for G1 points
///
/// Takes a vector of G1 points and corresponding scalars, and returns their weighted sum
///
/// Note: This method assumes that `g1_points` does not contain any points at infinity.
#[inline]
pub(super) fn p1_msm(
g1_points: Vec<blst_p1_affine>,
scalars_bytes: Vec<u8>,
nbits: usize,
) -> blst_p1_affine {
assert!(
scalars_bytes.len() % SCALAR_LENGTH == 0,
"Each scalar should be {SCALAR_LENGTH} bytes"
);

assert_eq!(
g1_points.len(),
scalars_bytes.len() / SCALAR_LENGTH,
"number of scalars should equal the number of g1 points"
);
// When no inputs are given, we trigger an assert.
// While it is mathematically sound to have no inputs (can return point at infinity)
// EIP2537 forbids this and since this is the only function that
// currently calls this method, we have this assert.
assert!(
!g1_points.is_empty(),
"number of inputs to pairing should be non-zero"
);

// Perform multi-scalar multiplication
let multiexp = g1_points.mult(&scalars_bytes, nbits);

// Convert result back to affine coordinates
p1_to_affine(&multiexp)
}

/// Performs multi-scalar multiplication (MSM) for G2 points
///
/// Takes a vector of G2 points and corresponding scalars, and returns their weighted sum
///
/// Note: This method assumes that `g2_points` does not contain any points at infinity.
#[inline]
pub(super) fn p2_msm(
g2_points: Vec<blst_p2_affine>,
scalars_bytes: Vec<u8>,
nbits: usize,
) -> blst_p2_affine {
assert!(
scalars_bytes.len() % SCALAR_LENGTH == 0,
"Each scalar should be {SCALAR_LENGTH} bytes"
);

assert_eq!(
g2_points.len(),
scalars_bytes.len() / SCALAR_LENGTH,
"number of scalars should equal the number of g2 points"
);
// When no inputs are given, we trigger an assert.
// While it is mathematically sound to have no inputs (can return point at infinity)
// EIP2537 forbids this and since this is the only function that
// currently calls this method, we have this assert.
assert!(
!g2_points.is_empty(),
"number of inputs to pairing should be non-zero"
);

// Perform multi-scalar multiplication
let multiexp = g2_points.mult(&scalars_bytes, nbits);

// Convert result back to affine coordinates
p2_to_affine(&multiexp)
}

/// Maps a field element to a G1 point
///
/// Takes a field element (blst_fp) and returns the corresponding G1 point in affine form
#[inline]
pub(super) fn map_fp_to_g1(fp: &blst_fp) -> blst_p1_affine {
// Create a new G1 point in Jacobian coordinates
let mut p = blst_p1::default();

// Map the field element to a point on the curve
// SAFETY: `p` and `fp` are blst values
// Third argument is unused if null
unsafe { blst_map_to_g1(&mut p, fp, core::ptr::null()) };

// Convert to affine coordinates
p1_to_affine(&p)
}

/// Maps a field element to a G2 point
///
/// Takes a field element (blst_fp2) and returns the corresponding G2 point in affine form
#[inline]
pub(super) fn map_fp2_to_g2(fp2: &blst_fp2) -> blst_p2_affine {
// Create a new G2 point in Jacobian coordinates
let mut p = blst_p2::default();

// Map the field element to a point on the curve
// SAFETY: `p` and `fp2` are blst values
// Third argument is unused if null
unsafe { blst_map_to_g2(&mut p, fp2, core::ptr::null()) };

// Convert to affine coordinates
p2_to_affine(&p)
}

/// Computes a single miller loop for a given G1, G2 pair
#[inline]
fn compute_miller_loop(g1: &blst_p1_affine, g2: &blst_p2_affine) -> blst_fp12 {
let mut result = blst_fp12::default();

// SAFETY: All arguments are valid blst types
unsafe { blst_miller_loop(&mut result, g2, g1) }

result
}

/// multiply_fp12 multiplies two fp12 elements
#[inline]
fn multiply_fp12(a: &blst_fp12, b: &blst_fp12) -> blst_fp12 {
let mut result = blst_fp12::default();

// SAFETY: All arguments are valid blst types
unsafe { blst_fp12_mul(&mut result, a, b) }

result
}

/// final_exp computes the final exponentiation on an fp12 element
#[inline]
fn final_exp(f: &blst_fp12) -> blst_fp12 {
let mut result = blst_fp12::default();

// SAFETY: All arguments are valid blst types
unsafe { blst_final_exp(&mut result, f) }

result
}

/// is_fp12_one checks if an fp12 element equals
/// multiplicative identity element, one
#[inline]
fn is_fp12_one(f: &blst_fp12) -> bool {
// SAFETY: argument is a valid blst type
unsafe { blst_fp12_is_one(f) }
}

/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and
/// returns true if the result is equal to the identity element.
#[inline]
pub(super) fn pairing_check(pairs: &[(blst_p1_affine, blst_p2_affine)]) -> bool {
// When no inputs are given, we trigger an assert.
// While it is mathematically sound to have no inputs (can return true)
// EIP2537 forbids this and since this is the only function that
// currently calls this method, we have this assert.
assert!(
!pairs.is_empty(),
"number of inputs to pairing should be non-zero"
);

// Compute the miller loop for the first pair
let (first_g1, first_g2) = &pairs[0];
let mut acc = compute_miller_loop(first_g1, first_g2);

// For the remaining pairs, compute miller loop and multiply with the accumulated result
for (g1, g2) in pairs.iter().skip(1) {
let ml = compute_miller_loop(g1, g2);
acc = multiply_fp12(&acc, &ml);
}

// Apply final exponentiation and check if result is 1
let final_result = final_exp(&acc);

// Check if the result is one (identity element)
is_fp12_one(&final_result)
}
52 changes: 35 additions & 17 deletions crates/precompile/src/bls12_381/g1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ pub(super) fn encode_g1_point(input: *const blst_p1_affine) -> Bytes {
/// 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.
/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
/// See [fp_from_bendian] for more information.
/// - If the point is not on the curve, an error is returned.
pub(super) fn decode_and_check_g1(
p0_x: &[u8; 48],
p0_y: &[u8; 48],
Expand All @@ -30,13 +30,44 @@ pub(super) fn decode_and_check_g1(
y: fp_from_bendian(p0_y)?,
};

// From EIP-2537:
//
// Error cases:
//
// * An input is neither a point on the G1 elliptic curve nor the infinity point
//
// SAFETY: Out is a blst value.
if unsafe { !blst_p1_affine_on_curve(&out) } {
return Err(PrecompileError::Other(
"Element not on G1 curve".to_string(),
));
}

Ok(out)
}

/// Extracts a G1 point in Affine format from a 128 byte slice representation.
///
/// Note: By default, subgroup checks are performed.
pub(super) fn extract_g1_input(input: &[u8]) -> Result<blst_p1_affine, PrecompileError> {
_extract_g1_input(input, true)
}
/// Extracts a G1 point in Affine format from a 128 byte slice representation.
/// without performing a subgroup check.
///
/// Note: Skipping subgroup checks can introduce security issues.
/// This method should only be called if:
/// - The EIP specifies that no subgroup check should be performed
/// - One can be certain that the point is in the correct subgroup.
pub(super) fn extract_g1_input_no_subgroup_check(
input: &[u8],
) -> Result<blst_p1_affine, PrecompileError> {
_extract_g1_input(input, false)
}
/// 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(super) fn extract_g1_input(
fn _extract_g1_input(
input: &[u8],
subgroup_check: bool,
) -> Result<blst_p1_affine, PrecompileError> {
Expand All @@ -51,19 +82,6 @@ pub(super) fn extract_g1_input(
let input_p0_y = remove_padding(&input[PADDED_FP_LENGTH..PADDED_G1_LENGTH])?;
let out = decode_and_check_g1(input_p0_x, input_p0_y)?;

// From EIP-2537:
//
// Error cases:
//
// * An input is neither a point on the G1 elliptic curve nor the infinity point
//
// SAFETY: Out is a blst value.
if unsafe { !blst_p1_affine_on_curve(&out) } {
return Err(PrecompileError::Other(
"Element not on G1 curve".to_string(),
));
}

if subgroup_check {
// NB: Subgroup checks
//
Expand Down
Loading
Loading