diff --git a/crates/precompile/src/bls12_381/blst.rs b/crates/precompile/src/bls12_381/blst.rs index d571b8f7fc..8d998e2e02 100644 --- a/crates/precompile/src/bls12_381/blst.rs +++ b/crates/precompile/src/bls12_381/blst.rs @@ -1,9 +1,9 @@ // This module contains a safe wrapper around the blst library. use crate::{ - bls12_381::utils::{is_valid_be, remove_padding}, bls12_381_const::{ - FP_LENGTH, FP_PAD_BY, PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH, SCALAR_LENGTH, + FP_LENGTH, FP_PAD_BY, MODULUS_REPR, PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH, + SCALAR_LENGTH, }, PrecompileError, }; @@ -97,15 +97,24 @@ pub(super) fn p2_add_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_a /// /// Takes a G1 point in affine form and a scalar, and returns the result /// of the scalar multiplication in affine form +/// +/// Note: The scalar is expected to be in Big Endian format. #[inline] -fn p1_scalar_mul(p: &blst_p1_affine, scalar: &[u8]) -> blst_p1_affine { +fn p1_scalar_mul(p: &blst_p1_affine, scalar: &blst_scalar) -> blst_p1_affine { // Convert point to Jacobian coordinates let p_jacobian = p1_from_affine(p); let mut result = blst_p1::default(); // SAFETY: all inputs are valid blst types - unsafe { blst_p1_mult(&mut result, &p_jacobian, scalar.as_ptr(), scalar.len() * 8) }; + unsafe { + blst_p1_mult( + &mut result, + &p_jacobian, + scalar.b.as_ptr(), + scalar.b.len() * 8, + ) + }; // Convert result back to affine coordinates p1_to_affine(&result) @@ -115,14 +124,23 @@ fn p1_scalar_mul(p: &blst_p1_affine, scalar: &[u8]) -> blst_p1_affine { /// /// Takes a G2 point in affine form and a scalar, and returns the result /// of the scalar multiplication in affine form +/// +/// Note: The scalar is expected to be in Big Endian format. #[inline] -fn p2_scalar_mul(p: &blst_p2_affine, scalar: &[u8]) -> blst_p2_affine { +fn p2_scalar_mul(p: &blst_p2_affine, scalar: &blst_scalar) -> blst_p2_affine { // Convert point to Jacobian coordinates let p_jacobian = p2_from_affine(p); let mut result = blst_p2::default(); // SAFETY: all inputs are valid blst types - unsafe { blst_p2_mult(&mut result, &p_jacobian, scalar.as_ptr(), scalar.len() * 8) }; + unsafe { + blst_p2_mult( + &mut result, + &p_jacobian, + scalar.b.as_ptr(), + scalar.b.len() * 8, + ) + }; // Convert result back to affine coordinates p2_to_affine(&result) @@ -136,16 +154,12 @@ fn p2_scalar_mul(p: &blst_p2_affine, scalar: &[u8]) -> blst_p2_affine { #[inline] pub(super) fn p1_msm( g1_points: Vec, - scalars_bytes: Vec, + scalars: Vec, 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, + scalars.len(), "number of scalars should equal the number of g1 points" ); @@ -162,9 +176,10 @@ pub(super) fn p1_msm( // When there is only a single point, we use a simpler scalar multiplication // procedure if g1_points.len() == 1 { - return p1_scalar_mul(&g1_points[0], &scalars_bytes); + return p1_scalar_mul(&g1_points[0], &scalars[0]); } + let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect(); // Perform multi-scalar multiplication let multiexp = g1_points.mult(&scalars_bytes, nbits); @@ -176,21 +191,17 @@ pub(super) fn p1_msm( /// /// 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. +/// Note: Scalars are expected to be in Big Endian format. +/// This method assumes that `g2_points` does not contain any points at infinity. #[inline] pub(super) fn p2_msm( g2_points: Vec, - scalars_bytes: Vec, + scalars: Vec, 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, + scalars.len(), "number of scalars should equal the number of g2 points" ); @@ -207,9 +218,11 @@ pub(super) fn p2_msm( // When there is only a single point, we use a simpler scalar multiplication // procedure if g2_points.len() == 1 { - return p2_scalar_mul(&g2_points[0], &scalars_bytes); + return p2_scalar_mul(&g2_points[0], &scalars[0]); } + let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect(); + // Perform multi-scalar multiplication let multiexp = g2_points.mult(&scalars_bytes, nbits); @@ -324,6 +337,8 @@ pub(super) fn pairing_check(pairs: &[(blst_p1_affine, blst_p2_affine)]) -> bool } /// Encodes a G1 point in affine format into byte slice with padded elements. +/// +/// Note: The encoded bytes are in Big Endian format. pub(super) fn encode_g1_point(input: &blst_p1_affine) -> [u8; PADDED_G1_LENGTH] { let mut out = [0u8; PADDED_G1_LENGTH]; fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &input.x); @@ -332,6 +347,8 @@ pub(super) fn encode_g1_point(input: &blst_p1_affine) -> [u8; PADDED_G1_LENGTH] } /// Encodes a single finite field element into byte slice with padding. +/// +/// Note: The encoded bytes are in Big Endian format. fn fp_to_bytes(out: &mut [u8], input: &blst_fp) { if out.len() != PADDED_FP_LENGTH { return; @@ -345,16 +362,18 @@ fn fp_to_bytes(out: &mut [u8], input: &blst_fp) { /// Returns a `blst_p1_affine` from the provided byte slices, which represent the x and y /// affine coordinates of the point. /// +/// Note: Coordinates are expected to be in Big Endian format. +/// /// - If the x or y coordinate do not represent a canonical field element, an error is returned. -/// See [fp_from_bendian] for more information. +/// See [read_fp] for more information. /// - If the point is not on the curve, an error is returned. -fn decode_and_check_g1( - p0_x: &[u8; 48], - p0_y: &[u8; 48], +fn decode_g1_on_curve( + p0_x: &[u8; FP_LENGTH], + p0_y: &[u8; FP_LENGTH], ) -> Result { let out = blst_p1_affine { - x: fp_from_bendian(p0_x)?, - y: fp_from_bendian(p0_y)?, + x: read_fp(p0_x)?, + y: read_fp(p0_y)?, }; // From EIP-2537: @@ -373,41 +392,40 @@ fn decode_and_check_g1( Ok(out) } -/// Extracts a G1 point in Affine format from a 128 byte slice representation. +/// Extracts a G1 point in Affine format from the x and y coordinates. /// -/// Note: By default, subgroup checks are performed. -pub(super) fn extract_g1_input(input: &[u8]) -> Result { - _extract_g1_input(input, true) +/// Note: Coordinates are expected to be in Big Endian format. +/// By default, subgroup checks are performed. +pub(super) fn read_g1( + x: &[u8; FP_LENGTH], + y: &[u8; FP_LENGTH], +) -> Result { + _extract_g1_input(x, y, true) } -/// Extracts a G1 point in Affine format from a 128 byte slice representation. +/// Extracts a G1 point in Affine format from the x and y coordinates /// without performing a subgroup check. /// -/// Note: Skipping subgroup checks can introduce security issues. +/// Note: Coordinates are expected to be in Big Endian format. +/// 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], +pub(super) fn read_g1_no_subgroup_check( + x: &[u8; FP_LENGTH], + y: &[u8; FP_LENGTH], ) -> Result { - _extract_g1_input(input, false) + _extract_g1_input(x, y, false) } -/// Extracts a G1 point in Affine format from a 128 byte slice representation. +/// Extracts a G1 point in Affine format from the x and y coordinates. /// -/// **Note**: This function will perform a G1 subgroup check if `subgroup_check` is set to `true`. +/// Note: Coordinates are expected to be in Big Endian format. +/// This function will perform a G1 subgroup check if `subgroup_check` is set to `true`. fn _extract_g1_input( - input: &[u8], + x: &[u8; FP_LENGTH], + y: &[u8; FP_LENGTH], subgroup_check: bool, ) -> Result { - if input.len() != PADDED_G1_LENGTH { - return Err(PrecompileError::Other(format!( - "Input should be {PADDED_G1_LENGTH} bytes, was {}", - input.len() - ))); - } - - let input_p0_x = remove_padding(&input[..PADDED_FP_LENGTH])?; - 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)?; + let out = decode_g1_on_curve(x, y)?; if subgroup_check { // NB: Subgroup checks @@ -430,6 +448,8 @@ fn _extract_g1_input( } /// Encodes a G2 point in affine format into byte slice with padded elements. +/// +/// Note: The encoded bytes are in Big Endian format. pub(super) fn encode_g2_point(input: &blst_p2_affine) -> [u8; PADDED_G2_LENGTH] { let mut out = [0u8; PADDED_G2_LENGTH]; fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &input.x.fp[0]); @@ -451,18 +471,20 @@ pub(super) fn encode_g2_point(input: &blst_p2_affine) -> [u8; PADDED_G2_LENGTH] /// Returns a `blst_p2_affine` from the provided byte slices, which represent the x and y /// affine coordinates of the point. /// +/// Note: Coordinates are expected to be in Big Endian format. +/// /// - If the x or y coordinate do not represent a canonical field element, an error is returned. -/// See [check_canonical_fp2] for more information. +/// See [read_fp2] for more information. /// - If the point is not on the curve, an error is returned. -fn decode_and_check_g2( - x1: &[u8; 48], - x2: &[u8; 48], - y1: &[u8; 48], - y2: &[u8; 48], +fn decode_g2_on_curve( + x1: &[u8; FP_LENGTH], + x2: &[u8; FP_LENGTH], + y1: &[u8; FP_LENGTH], + y2: &[u8; FP_LENGTH], ) -> Result { let out = blst_p2_affine { - x: check_canonical_fp2(x1, x2)?, - y: check_canonical_fp2(y1, y2)?, + x: read_fp2(x1, x2)?, + y: read_fp2(y1, y2)?, }; // From EIP-2537: @@ -481,57 +503,61 @@ fn decode_and_check_g2( Ok(out) } -/// Checks whether or not the input represents a canonical fp2 field element, returning the field -/// element if successful. -pub(super) fn check_canonical_fp2( - input_1: &[u8; 48], - input_2: &[u8; 48], +/// Creates a blst_fp2 element from two field elements. +/// +/// Field elements are expected to be in Big Endian format. +/// Returns an error if either of the input field elements is not canonical. +pub(super) fn read_fp2( + input_1: &[u8; FP_LENGTH], + input_2: &[u8; FP_LENGTH], ) -> Result { - let fp_1 = fp_from_bendian(input_1)?; - let fp_2 = fp_from_bendian(input_2)?; + let fp_1 = read_fp(input_1)?; + let fp_2 = read_fp(input_2)?; let fp2 = blst_fp2 { fp: [fp_1, fp_2] }; Ok(fp2) } -/// Extracts a G2 point in Affine format from a 256 byte slice representation. +/// Extracts a G2 point in Affine format from the x and y coordinates. /// -/// Note: By default, no subgroup checks are performed. -pub(super) fn extract_g2_input(input: &[u8]) -> Result { - _extract_g2_input(input, true) +/// Note: Coordinates are expected to be in Big Endian format. +/// By default, subgroup checks are performed. +pub(super) fn read_g2( + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], +) -> Result { + _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, true) } -/// Extracts a G2 point in Affine format from a 256 byte slice representation +/// Extracts a G2 point in Affine format from the x and y coordinates /// without performing a subgroup check. /// -/// Note: Skipping subgroup checks can introduce security issues. +/// Note: Coordinates are expected to be in Big Endian format. +/// 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_g2_input_no_subgroup_check( - input: &[u8], +pub(super) fn read_g2_no_subgroup_check( + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], ) -> Result { - _extract_g2_input(input, false) + _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, false) } -/// Extracts a G2 point in Affine format from a 256 byte slice representation. +/// Extracts a G2 point in Affine format from the x and y coordinates. /// -/// **Note**: This function will perform a G2 subgroup check if `subgroup_check` is set to `true`. +/// Note: Coordinates are expected to be in Big Endian format. +/// This function will perform a G2 subgroup check if `subgroup_check` is set to `true`. fn _extract_g2_input( - input: &[u8], + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], subgroup_check: bool, ) -> Result { - if input.len() != PADDED_G2_LENGTH { - return Err(PrecompileError::Other(format!( - "Input should be {PADDED_G2_LENGTH} bytes, was {}", - input.len() - ))); - } - - let mut input_fps = [&[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 out = decode_and_check_g2(input_fps[0], input_fps[1], input_fps[2], input_fps[3])?; + let out = decode_g2_on_curve(a_x_0, a_x_1, a_y_0, a_y_1)?; if subgroup_check { // NB: Subgroup checks @@ -553,9 +579,11 @@ fn _extract_g2_input( Ok(out) } -/// Checks whether or not the input represents a canonical field element, returning the field -/// element if successful. -pub(super) fn fp_from_bendian(input: &[u8; 48]) -> Result { +/// Checks whether or not the input represents a canonical field element +/// returning the field element if successful. +/// +/// Note: The field element is expected to be in big endian format. +pub(super) fn read_fp(input: &[u8; FP_LENGTH]) -> Result { if !is_valid_be(input) { return Err(PrecompileError::Other("non-canonical fp value".to_string())); } @@ -569,7 +597,7 @@ pub(super) fn fp_from_bendian(input: &[u8; 48]) -> Result Result Result { +pub(super) fn read_scalar(input: &[u8]) -> Result { if input.len() != SCALAR_LENGTH { return Err(PrecompileError::Other(format!( "Input should be {SCALAR_LENGTH} bytes, was {}", @@ -599,3 +627,8 @@ pub(super) fn extract_scalar_input(input: &[u8]) -> Result bool { + *input < MODULUS_REPR +} diff --git a/crates/precompile/src/bls12_381/g1_add.rs b/crates/precompile/src/bls12_381/g1_add.rs index 50d9c92262..ab00cac035 100644 --- a/crates/precompile/src/bls12_381/g1_add.rs +++ b/crates/precompile/src/bls12_381/g1_add.rs @@ -1,4 +1,5 @@ -use super::blst::{encode_g1_point, extract_g1_input_no_subgroup_check, p1_add_affine}; +use super::blst::{encode_g1_point, p1_add_affine, read_g1_no_subgroup_check}; +use super::utils::remove_g1_padding; use crate::bls12_381_const::{ G1_ADD_ADDRESS, G1_ADD_BASE_GAS_FEE, G1_ADD_INPUT_LENGTH, PADDED_G1_LENGTH, }; @@ -25,12 +26,15 @@ pub(super) fn g1_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { ))); } + let [a_x, a_y] = remove_g1_padding(&input[..PADDED_G1_LENGTH])?; + let [b_x, b_y] = remove_g1_padding(&input[PADDED_G1_LENGTH..])?; + // NB: There is no subgroup check for the G1 addition precompile because the time to do the subgroup // check would be more than the time it takes to to do the g1 addition. // // Users should be careful to note whether the points being added are indeed in the right subgroup. - let a_aff = &extract_g1_input_no_subgroup_check(&input[..PADDED_G1_LENGTH])?; - let b_aff = &extract_g1_input_no_subgroup_check(&input[PADDED_G1_LENGTH..])?; + let a_aff = &read_g1_no_subgroup_check(a_x, a_y)?; + let b_aff = &read_g1_no_subgroup_check(b_x, b_y)?; let p_aff = p1_add_affine(a_aff, b_aff); let out = encode_g1_point(&p_aff); diff --git a/crates/precompile/src/bls12_381/g1_msm.rs b/crates/precompile/src/bls12_381/g1_msm.rs index bbf1c2e586..a0d6435878 100644 --- a/crates/precompile/src/bls12_381/g1_msm.rs +++ b/crates/precompile/src/bls12_381/g1_msm.rs @@ -1,4 +1,5 @@ -use super::blst::{encode_g1_point, extract_g1_input, extract_scalar_input, p1_msm}; +use super::blst::{encode_g1_point, p1_msm, read_g1, read_scalar}; +use crate::bls12_381::utils::remove_g1_padding; use crate::bls12_381_const::{ DISCOUNT_TABLE_G1_MSM, G1_MSM_ADDRESS, G1_MSM_BASE_GAS_FEE, G1_MSM_INPUT_LENGTH, NBITS, PADDED_G1_LENGTH, SCALAR_LENGTH, @@ -34,7 +35,7 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { } let mut g1_points: Vec<_> = Vec::with_capacity(k); - let mut scalars_bytes: Vec = Vec::with_capacity(k * SCALAR_LENGTH); + let mut scalars = Vec::with_capacity(k); for i in 0..k { let encoded_g1_element = &input[i * G1_MSM_INPUT_LENGTH..i * G1_MSM_INPUT_LENGTH + PADDED_G1_LENGTH]; @@ -48,8 +49,11 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { if encoded_g1_element.iter().all(|i| *i == 0) { continue; } + + let [a_x, a_y] = remove_g1_padding(encoded_g1_element)?; + // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. - let p0_aff = extract_g1_input(encoded_g1_element)?; + let p0_aff = read_g1(a_x, a_y)?; // If the scalar is zero, then this is a no-op. // @@ -61,8 +65,7 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { } g1_points.push(p0_aff); - - scalars_bytes.extend_from_slice(&extract_scalar_input(encoded_scalar)?.b); + scalars.push(read_scalar(encoded_scalar)?); } // Return the encoding for the point at the infinity according to EIP-2537 @@ -75,7 +78,7 @@ pub(super) fn g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { )); } - let multiexp_aff = p1_msm(g1_points, scalars_bytes, NBITS); + let multiexp_aff = p1_msm(g1_points, scalars, NBITS); let out = encode_g1_point(&multiexp_aff); Ok(PrecompileOutput::new(required_gas, out.into())) diff --git a/crates/precompile/src/bls12_381/g2_add.rs b/crates/precompile/src/bls12_381/g2_add.rs index a0ae127c22..6edc1b57e7 100644 --- a/crates/precompile/src/bls12_381/g2_add.rs +++ b/crates/precompile/src/bls12_381/g2_add.rs @@ -1,4 +1,5 @@ -use super::blst::{encode_g2_point, extract_g2_input_no_subgroup_check, p2_add_affine}; +use super::blst::{encode_g2_point, p2_add_affine, read_g2_no_subgroup_check}; +use super::utils::remove_g2_padding; use crate::bls12_381_const::{ G2_ADD_ADDRESS, G2_ADD_BASE_GAS_FEE, G2_ADD_INPUT_LENGTH, PADDED_G2_LENGTH, }; @@ -26,12 +27,15 @@ pub(super) fn g2_add(input: &Bytes, gas_limit: u64) -> PrecompileResult { ))); } + let [a_x_0, a_x_1, a_y_0, a_y_1] = remove_g2_padding(&input[..PADDED_G2_LENGTH])?; + let [b_x_0, b_x_1, b_y_0, b_y_1] = remove_g2_padding(&input[PADDED_G2_LENGTH..])?; + // NB: There is no subgroup check for the G2 addition precompile because the time to do the subgroup // check would be more than the time it takes to to do the g1 addition. // // Users should be careful to note whether the points being added are indeed in the right subgroup. - let a_aff = &extract_g2_input_no_subgroup_check(&input[..PADDED_G2_LENGTH])?; - let b_aff = &extract_g2_input_no_subgroup_check(&input[PADDED_G2_LENGTH..])?; + let a_aff = &read_g2_no_subgroup_check(a_x_0, a_x_1, a_y_0, a_y_1)?; + let b_aff = &read_g2_no_subgroup_check(b_x_0, b_x_1, b_y_0, b_y_1)?; // Use the safe wrapper for G2 point addition let p_aff = p2_add_affine(a_aff, b_aff); diff --git a/crates/precompile/src/bls12_381/g2_msm.rs b/crates/precompile/src/bls12_381/g2_msm.rs index 86b37d3b15..9bb437ac21 100644 --- a/crates/precompile/src/bls12_381/g2_msm.rs +++ b/crates/precompile/src/bls12_381/g2_msm.rs @@ -1,4 +1,5 @@ -use super::blst::{encode_g2_point, extract_g2_input, extract_scalar_input, p2_msm}; +use super::blst::{encode_g2_point, p2_msm, read_g2, read_scalar}; +use super::utils::remove_g2_padding; use crate::bls12_381_const::{ DISCOUNT_TABLE_G2_MSM, G2_MSM_ADDRESS, G2_MSM_BASE_GAS_FEE, G2_MSM_INPUT_LENGTH, NBITS, PADDED_G2_LENGTH, SCALAR_LENGTH, @@ -34,9 +35,9 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { } let mut g2_points: Vec<_> = Vec::with_capacity(k); - let mut scalars_bytes: Vec = Vec::with_capacity(k * SCALAR_LENGTH); + let mut scalars = Vec::with_capacity(k); for i in 0..k { - let encoded_g2_elements = + let encoded_g2_element = &input[i * G2_MSM_INPUT_LENGTH..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH]; let encoded_scalar = &input[i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH ..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH + SCALAR_LENGTH]; @@ -44,13 +45,16 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { // Filter out points infinity as an optimization, since it is a no-op. // Note: Previously, points were being batch converted from Jacobian to Affine. In `blst`, this would essentially, // zero out all of the points. Since all points are in affine, this bug is avoided. - if encoded_g2_elements.iter().all(|i| *i == 0) { + if encoded_g2_element.iter().all(|i| *i == 0) { continue; } + + let [a_x_0, a_x_1, a_y_0, a_y_1] = remove_g2_padding(encoded_g2_element)?; + // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. // // So we set the subgroup_check flag to `true` - let p0_aff = extract_g2_input(encoded_g2_elements)?; + let p0_aff = read_g2(a_x_0, a_x_1, a_y_0, a_y_1)?; // If the scalar is zero, then this is a no-op. // @@ -63,17 +67,19 @@ pub(super) fn g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { // Convert affine point to Jacobian coordinates using our helper function g2_points.push(p0_aff); - - scalars_bytes.extend_from_slice(&extract_scalar_input(encoded_scalar)?.b); + scalars.push(read_scalar(encoded_scalar)?); } // Return infinity point if all points are infinity if g2_points.is_empty() { - return Ok(PrecompileOutput::new(required_gas, [0; 256].into())); + return Ok(PrecompileOutput::new( + required_gas, + [0; PADDED_G2_LENGTH].into(), + )); } // Perform multi-scalar multiplication using the safe wrapper - let multiexp_aff = p2_msm(g2_points, scalars_bytes, NBITS); + let multiexp_aff = p2_msm(g2_points, scalars, NBITS); let out = encode_g2_point(&multiexp_aff); Ok(PrecompileOutput::new(required_gas, out.into())) diff --git a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs index f71ed7fcbb..fcd7c902a1 100644 --- a/crates/precompile/src/bls12_381/map_fp2_to_g2.rs +++ b/crates/precompile/src/bls12_381/map_fp2_to_g2.rs @@ -1,6 +1,6 @@ use super::{ - blst::{check_canonical_fp2, encode_g2_point, map_fp2_to_g2 as blst_map_fp2_to_g2}, - utils::remove_padding, + blst::{encode_g2_point, map_fp2_to_g2 as blst_map_fp2_to_g2, read_fp2}, + utils::remove_fp_padding, }; use crate::bls12_381_const::{ MAP_FP2_TO_G2_ADDRESS, MAP_FP2_TO_G2_BASE_GAS_FEE, PADDED_FP2_LENGTH, PADDED_FP_LENGTH, @@ -29,9 +29,9 @@ pub(super) fn map_fp2_to_g2(input: &Bytes, gas_limit: u64) -> PrecompileResult { ))); } - let input_p0_x = remove_padding(&input[..PADDED_FP_LENGTH])?; - let input_p0_y = remove_padding(&input[PADDED_FP_LENGTH..PADDED_FP2_LENGTH])?; - let fp2 = check_canonical_fp2(input_p0_x, input_p0_y)?; + let input_p0_x = remove_fp_padding(&input[..PADDED_FP_LENGTH])?; + let input_p0_y = remove_fp_padding(&input[PADDED_FP_LENGTH..PADDED_FP2_LENGTH])?; + let fp2 = read_fp2(input_p0_x, input_p0_y)?; let p_aff = blst_map_fp2_to_g2(&fp2); let out = encode_g2_point(&p_aff); diff --git a/crates/precompile/src/bls12_381/map_fp_to_g1.rs b/crates/precompile/src/bls12_381/map_fp_to_g1.rs index b46eeb9f67..b9dc0fd537 100644 --- a/crates/precompile/src/bls12_381/map_fp_to_g1.rs +++ b/crates/precompile/src/bls12_381/map_fp_to_g1.rs @@ -1,6 +1,6 @@ use super::{ - blst::{encode_g1_point, fp_from_bendian, map_fp_to_g1 as blst_map_fp_to_g1}, - utils::remove_padding, + blst::{encode_g1_point, map_fp_to_g1 as blst_map_fp_to_g1, read_fp}, + utils::remove_fp_padding, }; use crate::bls12_381_const::{MAP_FP_TO_G1_ADDRESS, MAP_FP_TO_G1_BASE_GAS_FEE, PADDED_FP_LENGTH}; use crate::{PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress}; @@ -25,8 +25,8 @@ pub(super) fn map_fp_to_g1(input: &Bytes, gas_limit: u64) -> PrecompileResult { ))); } - let input_p0 = remove_padding(input)?; - let fp = fp_from_bendian(input_p0)?; + let input_p0 = remove_fp_padding(input)?; + let fp = read_fp(input_p0)?; let p_aff = blst_map_fp_to_g1(&fp); let out = encode_g1_point(&p_aff); diff --git a/crates/precompile/src/bls12_381/pairing.rs b/crates/precompile/src/bls12_381/pairing.rs index 08cd5960a9..04a8a3dd96 100644 --- a/crates/precompile/src/bls12_381/pairing.rs +++ b/crates/precompile/src/bls12_381/pairing.rs @@ -1,4 +1,5 @@ -use super::blst::{extract_g1_input, extract_g2_input, pairing_check}; +use super::blst::{pairing_check, read_g1, read_g2}; +use super::utils::{remove_g1_padding, remove_g2_padding}; use crate::bls12_381_const::{ PADDED_G1_LENGTH, PADDED_G2_LENGTH, PAIRING_ADDRESS, PAIRING_INPUT_LENGTH, PAIRING_MULTIPLIER_BASE, PAIRING_OFFSET_BASE, @@ -55,10 +56,13 @@ pub(super) fn pairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { let g1_is_zero = encoded_g1_element.iter().all(|i| *i == 0); let g2_is_zero = encoded_g2_element.iter().all(|i| *i == 0); + let [a_x, a_y] = remove_g1_padding(encoded_g1_element)?; + let [b_x_0, b_x_1, b_y_0, b_y_1] = remove_g2_padding(encoded_g2_element)?; + // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check. // extract_g1_input and extract_g2_input perform the necessary checks - let p1_aff = extract_g1_input(encoded_g1_element)?; - let p2_aff = extract_g2_input(encoded_g2_element)?; + let p1_aff = read_g1(a_x, a_y)?; + let p2_aff = read_g2(b_x_0, b_x_1, b_y_0, b_y_1)?; if !g1_is_zero & !g2_is_zero { pairs.push((p1_aff, p2_aff)); diff --git a/crates/precompile/src/bls12_381/utils.rs b/crates/precompile/src/bls12_381/utils.rs index 6fab569953..b32e5f302c 100644 --- a/crates/precompile/src/bls12_381/utils.rs +++ b/crates/precompile/src/bls12_381/utils.rs @@ -1,8 +1,10 @@ -use crate::bls12_381_const::{FP_LENGTH, FP_PAD_BY, MODULUS_REPR, PADDED_FP_LENGTH}; +use crate::bls12_381_const::{ + FP_LENGTH, FP_PAD_BY, PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH, +}; use crate::PrecompileError; /// 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> { +pub(super) fn remove_fp_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} bytes, was {}", @@ -17,8 +19,34 @@ pub(super) fn remove_padding(input: &[u8]) -> Result<&[u8; FP_LENGTH], Precompil } Ok(unpadded.try_into().unwrap()) } +/// remove_g1_padding removes the padding applied to the Fp elements that constitute the +/// encoded G1 element. +pub(super) fn remove_g1_padding(input: &[u8]) -> Result<[&[u8; FP_LENGTH]; 2], PrecompileError> { + if input.len() != PADDED_G1_LENGTH { + return Err(PrecompileError::Other(format!( + "Input should be {PADDED_G1_LENGTH} bytes, was {}", + input.len() + ))); + } + + let x = remove_fp_padding(&input[..PADDED_FP_LENGTH])?; + let y = remove_fp_padding(&input[PADDED_FP_LENGTH..PADDED_G1_LENGTH])?; + Ok([x, y]) +} -/// Checks if the input is a valid big-endian representation of a field element. -pub(super) fn is_valid_be(input: &[u8; 48]) -> bool { - *input < MODULUS_REPR +/// remove_g2_padding removes the padding applied to the Fp elements that constitute the +/// encoded G2 element. +pub(super) fn remove_g2_padding(input: &[u8]) -> Result<[&[u8; FP_LENGTH]; 4], PrecompileError> { + if input.len() != PADDED_G2_LENGTH { + return Err(PrecompileError::Other(format!( + "Input should be {PADDED_G2_LENGTH} bytes, was {}", + input.len() + ))); + } + + let mut input_fps = [&[0; FP_LENGTH]; 4]; + for i in 0..4 { + input_fps[i] = remove_fp_padding(&input[i * PADDED_FP_LENGTH..(i + 1) * PADDED_FP_LENGTH])?; + } + Ok(input_fps) }