diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index a22ca88b8..24d1f5ba6 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -8,7 +8,9 @@ use lambdaworks_math::{ short_weierstrass::{ curves::bn_254::{ curve::{BN254Curve, BN254FieldElement, BN254TwistCurveFieldElement}, - field_extension::Degree12ExtensionField, + field_extension::{ + BN254FieldModulus, Degree12ExtensionField, Degree2ExtensionField, + }, pairing::BN254AtePairing, twist::BN254TwistCurve, }, @@ -16,7 +18,10 @@ use lambdaworks_math::{ }, traits::{IsEllipticCurve, IsPairing}, }, - field::{element::FieldElement, extensions::quadratic::QuadraticExtensionFieldElement}, + field::{ + element::FieldElement, extensions::quadratic::QuadraticExtensionFieldElement, + fields::montgomery_backed_prime_fields::MontgomeryBackendPrimeField, + }, traits::ByteConversion, unsigned_integer::element, }; @@ -216,6 +221,7 @@ pub fn ecrecover( Ok(Bytes::from(output.to_vec())) } +/// Returns the calldata received pub fn identity( calldata: &Bytes, gas_for_call: u64, @@ -263,6 +269,7 @@ pub fn ripemd_160( Ok(Bytes::from(output)) } +/// Returns the result of the module-exponentiation operation pub fn modexp( calldata: &Bytes, gas_for_call: u64, @@ -307,8 +314,7 @@ pub fn modexp( let modulus_limit = m_size .checked_add(exponent_limit) .ok_or(InternalError::ArithmeticOperationOverflow)?; - // The reason I use unwrap_or_default is to cover the case where calldata does not reach the required - // length, so then we should fill the rest with zeros. The same is done in modulus parsing + let b = get_slice_or_default(&calldata, 96, base_limit, b_size)?; let base = BigUint::from_bytes_be(&b); @@ -318,7 +324,7 @@ pub fn modexp( let m = get_slice_or_default(&calldata, exponent_limit, modulus_limit, m_size)?; let modulus = BigUint::from_bytes_be(&m); - // first 32 bytes of exponent or exponent if e_size < 32 + // First 32 bytes of exponent or exponent if e_size < 32 let bytes_to_take = 32.min(e_size); // Use of unwrap_or_default because if e == 0 get_slice_or_default returns an empty vec let exp_first_32 = BigUint::from_bytes_be(e.get(0..bytes_to_take).unwrap_or_default()); @@ -334,6 +340,8 @@ pub fn modexp( Ok(res_bytes.slice(..m_size)) } +/// This function returns the slice between the lower and upper limit of the calldata (as a vector), +/// padding with zeros at the end if necessary. fn get_slice_or_default( calldata: &Bytes, lower_limit: usize, @@ -366,6 +374,7 @@ fn mod_exp(base: BigUint, exponent: BigUint, modulus: BigUint) -> BigUint { } } +/// If the result size is less than needed, pads left with zeros. pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Result { let mut padded_result = vec![0u8; m_size]; if result.len() < m_size { @@ -383,6 +392,7 @@ pub fn increase_left_pad(result: &Bytes, m_size: usize) -> Result>, + FieldElement>, +); + +/// Parses first point coordinates and makes verification of invalid infinite +fn parse_first_point_coordinates(input_data: &[u8]) -> Result { + let first_point_x = input_data.get(..32).ok_or(InternalError::SlicingError)?; + let first_point_y = input_data.get(32..64).ok_or(InternalError::SlicingError)?; + + // Infinite is defined by (0,0). Any other zero-combination is invalid + if (U256::from_big_endian(first_point_x) == U256::zero()) + ^ (U256::from_big_endian(first_point_y) == U256::zero()) + { + return Err(VMError::PrecompileError(PrecompileError::DefaultError)); + } + + let first_point_y = BN254FieldElement::from_bytes_be(first_point_y) + .map_err(|_| PrecompileError::DefaultError)?; + let first_point_x = BN254FieldElement::from_bytes_be(first_point_x) + .map_err(|_| PrecompileError::DefaultError)?; + + Ok((first_point_x, first_point_y)) +} + +/// Parses second point coordinates and makes verification of invalid infinite and curve belonging. +fn parse_second_point_coordinates( + input_data: &[u8], +) -> Result< + ( + FieldElement, + FieldElement, + ), + VMError, +> { + let second_point_x_first_part = input_data.get(96..128).ok_or(InternalError::SlicingError)?; + let second_point_x_second_part = input_data.get(64..96).ok_or(InternalError::SlicingError)?; + + // Infinite is defined by (0,0). Any other zero-combination is invalid + if (U256::from_big_endian(second_point_x_first_part) == U256::zero()) + ^ (U256::from_big_endian(second_point_x_second_part) == U256::zero()) + { + return Err(VMError::PrecompileError(PrecompileError::DefaultError)); + } + + let second_point_y_first_part = input_data + .get(160..192) + .ok_or(InternalError::SlicingError)?; + let second_point_y_second_part = input_data + .get(128..160) + .ok_or(InternalError::SlicingError)?; + + // Infinite is defined by (0,0). Any other zero-combination is invalid + if (U256::from_big_endian(second_point_y_first_part) == U256::zero()) + ^ (U256::from_big_endian(second_point_y_second_part) == U256::zero()) + { + return Err(VMError::PrecompileError(PrecompileError::DefaultError)); + } + + // Check if the second point belongs to the curve (this happens if it's lower than the prime) + if U256::from_big_endian(second_point_x_first_part) >= ALT_BN128_PRIME + || U256::from_big_endian(second_point_x_second_part) >= ALT_BN128_PRIME + || U256::from_big_endian(second_point_y_first_part) >= ALT_BN128_PRIME + || U256::from_big_endian(second_point_y_second_part) >= ALT_BN128_PRIME + { + return Err(VMError::PrecompileError(PrecompileError::DefaultError)); + } + + let second_point_x_bytes = [second_point_x_first_part, second_point_x_second_part].concat(); + let second_point_y_bytes = [second_point_y_first_part, second_point_y_second_part].concat(); + + let second_point_x = BN254TwistCurveFieldElement::from_bytes_be(&second_point_x_bytes) + .map_err(|_| PrecompileError::DefaultError)?; + let second_point_y = BN254TwistCurveFieldElement::from_bytes_be(&second_point_y_bytes) + .map_err(|_| PrecompileError::DefaultError)?; + + Ok((second_point_x, second_point_y)) +} + +/// Handles pairing given a certain elements, and depending on if elements represent infinity, then +/// continues, verifies errors on the other point or calculates the pairing +fn handle_pairing_from_coordinates( + first_point_x: FieldElement>, + first_point_y: FieldElement>, + second_point_x: FieldElement, + second_point_y: FieldElement, + mul: &mut FieldElement, +) -> Result { + let zero_element = BN254FieldElement::from(0); + let twcurve_zero_element = BN254TwistCurveFieldElement::from(0); + let first_point_is_infinity = + first_point_x.eq(&zero_element) && first_point_y.eq(&zero_element); + let second_point_is_infinity = + second_point_x.eq(&twcurve_zero_element) && second_point_y.eq(&twcurve_zero_element); + + match (first_point_is_infinity, second_point_is_infinity) { + (true, true) => { + // If both points are infinity, then continue to the next input + Ok(true) + } + (true, false) => { + // If the first point is infinity, then do the checks for the second + if let Ok(p2) = BN254TwistCurve::create_point_from_affine( + second_point_x.clone(), + second_point_y.clone(), + ) { + if !p2.is_in_subgroup() { + Err(VMError::PrecompileError(PrecompileError::DefaultError)) + } else { + Ok(true) + } + } else { + Err(VMError::PrecompileError(PrecompileError::DefaultError)) + } + } + (false, true) => { + // If the second point is infinity, then do the checks for the first + if BN254Curve::create_point_from_affine(first_point_x.clone(), first_point_y.clone()) + .is_err() + { + Err(VMError::PrecompileError(PrecompileError::DefaultError)) + } else { + Ok(true) + } + } + (false, false) => { + // Define the pairing points + let first_point = BN254Curve::create_point_from_affine(first_point_x, first_point_y) + .map_err(|_| PrecompileError::DefaultError)?; + + let second_point = + BN254TwistCurve::create_point_from_affine(second_point_x, second_point_y) + .map_err(|_| PrecompileError::DefaultError)?; + if !second_point.is_in_subgroup() { + return Err(VMError::PrecompileError(PrecompileError::DefaultError)); + } + + // Get the result of the pairing and affect the mul value with it + update_pairing_result(mul, first_point, second_point)?; + Ok(false) + } + } +} + +/// Performs a bilinear pairing on points on the elliptic curve 'alt_bn128', returns 1 on success and 0 on failure pub fn ecpairing( calldata: &Bytes, gas_for_call: u64, @@ -547,125 +710,18 @@ pub fn ecpairing( .get(input_start..input_end) .ok_or(InternalError::SlicingError)?; - let first_point_x = input_data.get(..32).ok_or(InternalError::SlicingError)?; - let first_point_y = input_data.get(32..64).ok_or(InternalError::SlicingError)?; + let (first_point_x, first_point_y) = parse_first_point_coordinates(input_data)?; - // Infinite is defined by (0,0). Any other zero-combination is invalid - if (U256::from_big_endian(first_point_x) == U256::zero()) - ^ (U256::from_big_endian(first_point_y) == U256::zero()) - { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - - let first_point_y = BN254FieldElement::from_bytes_be(first_point_y) - .map_err(|_| PrecompileError::DefaultError)?; - let first_point_x = BN254FieldElement::from_bytes_be(first_point_x) - .map_err(|_| PrecompileError::DefaultError)?; - - let second_point_x_first_part = - input_data.get(96..128).ok_or(InternalError::SlicingError)?; - let second_point_x_second_part = - input_data.get(64..96).ok_or(InternalError::SlicingError)?; - - // Infinite is defined by (0,0). Any other zero-combination is invalid - if (U256::from_big_endian(second_point_x_first_part) == U256::zero()) - ^ (U256::from_big_endian(second_point_x_second_part) == U256::zero()) - { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - - let second_point_y_first_part = input_data - .get(160..192) - .ok_or(InternalError::SlicingError)?; - let second_point_y_second_part = input_data - .get(128..160) - .ok_or(InternalError::SlicingError)?; + let (second_point_x, second_point_y) = parse_second_point_coordinates(input_data)?; - // Infinite is defined by (0,0). Any other zero-combination is invalid - if (U256::from_big_endian(second_point_y_first_part) == U256::zero()) - ^ (U256::from_big_endian(second_point_y_second_part) == U256::zero()) - { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - - let alt_bn128_prime = U256::from_str_radix( - "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", - 16, - ) - .map_err(|_| InternalError::ConversionError)?; - - // Check if the second point belongs to the curve (this happens if it's lower than the prime) - if U256::from_big_endian(second_point_x_first_part) >= alt_bn128_prime - || U256::from_big_endian(second_point_x_second_part) >= alt_bn128_prime - || U256::from_big_endian(second_point_y_first_part) >= alt_bn128_prime - || U256::from_big_endian(second_point_y_second_part) >= alt_bn128_prime - { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - - let second_point_x_bytes = [second_point_x_first_part, second_point_x_second_part].concat(); - let second_point_y_bytes = [second_point_y_first_part, second_point_y_second_part].concat(); - - let second_point_x: FieldElement = BN254TwistCurveFieldElement::from_bytes_be(&second_point_x_bytes) - .map_err(|_| PrecompileError::DefaultError)?; - let second_point_y = BN254TwistCurveFieldElement::from_bytes_be(&second_point_y_bytes) - .map_err(|_| PrecompileError::DefaultError)?; - - let zero_element = BN254FieldElement::from(0); - let twcurve_zero_element = BN254TwistCurveFieldElement::from(0); - let first_point_is_infinity = - first_point_x.eq(&zero_element) && first_point_y.eq(&zero_element); - let second_point_is_infinity = - second_point_x.eq(&twcurve_zero_element) && second_point_y.eq(&twcurve_zero_element); - - match (first_point_is_infinity, second_point_is_infinity) { - (true, true) => { - // If both points are infinity, then continue to the next input - continue; - } - (true, false) => { - // If the first point is infinity, then do the checks for the second - if let Ok(p2) = BN254TwistCurve::create_point_from_affine( - second_point_x.clone(), - second_point_y.clone(), - ) { - if !p2.is_in_subgroup() { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } else { - continue; - } - } else { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - } - (false, true) => { - // If the second point is infinity, then do the checks for the first - if BN254Curve::create_point_from_affine( - first_point_x.clone(), - first_point_y.clone(), - ) - .is_err() - { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - continue; - } - (false, false) => { - // Define the pairing points - let first_point = - BN254Curve::create_point_from_affine(first_point_x, first_point_y) - .map_err(|_| PrecompileError::DefaultError)?; - - let second_point = - BN254TwistCurve::create_point_from_affine(second_point_x, second_point_y) - .map_err(|_| PrecompileError::DefaultError)?; - if !second_point.is_in_subgroup() { - return Err(VMError::PrecompileError(PrecompileError::DefaultError)); - } - - // Get the result of the pairing and affect the mul value with it - update_pairing_result(&mut mul, first_point, second_point)?; - } + if handle_pairing_from_coordinates( + first_point_x, + first_point_y, + second_point_x, + second_point_y, + &mut mul, + )? { + continue; } } @@ -676,7 +732,8 @@ pub fn ecpairing( Ok(Bytes::from(result.to_vec())) } -/// I allow this clippy alert because lib handles mul for the type and will not panic in case of overflow +/// Updates the success variable with the pairing result. I allow this clippy alert because lib handles +/// mul for the type and will not panic in case of overflow #[allow(clippy::arithmetic_side_effects)] fn update_pairing_result( mul: &mut FieldElement, @@ -726,10 +783,10 @@ const R2: u32 = 24; const R3: u32 = 16; const R4: u32 = 63; -// The G primitive function mixes two input words, "x" and "y", into -// four words indexed by "a", "b", "c", and "d" in the working vector -// v[0..15]. The full modified vector is returned. -// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.1 +/// The G primitive function mixes two input words, "x" and "y", into +/// four words indexed by "a", "b", "c", and "d" in the working vector +/// v[0..15]. The full modified vector is returned. +/// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.1 #[allow(clippy::indexing_slicing)] fn g(v: [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) -> [u64; 16] { let mut ret = v; @@ -745,7 +802,7 @@ fn g(v: [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) -> [u ret } -// Perform the permutations on the work vector +/// Perform the permutations on the work vector given the rounds to permute and the message block #[allow(clippy::indexing_slicing)] fn word_permutation(rounds_to_permute: usize, v: [u64; 16], m: &[u64; 16]) -> [u64; 16] { let mut ret = v; @@ -769,13 +826,13 @@ fn word_permutation(rounds_to_permute: usize, v: [u64; 16], m: &[u64; 16]) -> [u ret } -// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.2 +/// Based on https://datatracker.ietf.org/doc/html/rfc7693#section-3.2 fn blake2f_compress_f( - rounds: usize, - h: [u64; 8], - m: &[u64; 16], - t: &[u64; 2], - f: bool, + rounds: usize, // Specifies the rounds to permute + h: [u64; 8], // State vector, defines the work vector (v) and affects the XOR process + m: &[u64; 16], // The message block to compress + t: &[u64; 2], // Affects the work vector (v) before permutations + f: bool, // If set as true, inverts all bits ) -> Result<[u64; 8], InternalError> { // Initialize local work vector v[0..15], takes first half from state and second half from IV. let mut v: [u64; 16] = [0; 16]; @@ -805,7 +862,8 @@ fn blake2f_compress_f( Ok(output) } -// Reads a part of the calldata and returns what is read as u64 or an error +/// Reads part of the calldata and returns what is read as u64 or an error +/// in the case where the calculated indexes don't match the calldata fn read_bytes_from_offset(calldata: &Bytes, offset: usize, index: usize) -> Result { let index_start = (index .checked_mul(BLAKE2F_ELEMENT_SIZE) @@ -855,6 +913,7 @@ fn parse_slice_arguments(calldata: &Bytes) -> Result { Ok((h, m, t)) } +/// Returns the result of Blake2 hashing algorithm given a certain parameters from the calldata. pub fn blake2f( calldata: &Bytes, gas_for_call: u64, @@ -893,23 +952,25 @@ pub fn blake2f( Ok(Bytes::from(output)) } -// Taken from the same name function from crates/common/types/blobs_bundle.rs -fn kzg_commitment_to_versioned_hash(data: &[u8; 48]) -> H256 { +/// Converts the provided commitment to match the provided versioned_hash. +/// Taken from the same name function from crates/common/types/blobs_bundle.rs +fn kzg_commitment_to_versioned_hash(commitment_bytes: &[u8; 48]) -> H256 { use k256::sha2::Digest; - let mut versioned_hash: [u8; 32] = k256::sha2::Sha256::digest(data).into(); + let mut versioned_hash: [u8; 32] = k256::sha2::Sha256::digest(commitment_bytes).into(); versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; versioned_hash.into() } +/// Verifies that p(z) = y given a commitment that corresponds to the polynomial p(x) and a KZG proof fn verify_kzg_proof( commitment_bytes: &[u8; 48], - x: &[u8; 32], + z: &[u8; 32], y: &[u8; 32], proof_bytes: &[u8; 48], ) -> Result { let commitment_bytes = Bytes48::from_slice(commitment_bytes).map_err(|_| PrecompileError::EvaluationError)?; // Could be ParsingInputError - let z_bytes = Bytes32::from_slice(x).map_err(|_| PrecompileError::EvaluationError)?; + let z_bytes = Bytes32::from_slice(z).map_err(|_| PrecompileError::EvaluationError)?; let y_bytes = Bytes32::from_slice(y).map_err(|_| PrecompileError::EvaluationError)?; let proof_bytes = Bytes48::from_slice(proof_bytes).map_err(|_| PrecompileError::EvaluationError)?; @@ -936,6 +997,7 @@ const POINT_EVALUATION_OUTPUT_BYTES: [u8; 64] = [ 0x53, 0xBD, 0xA4, 0x02, 0xFF, 0xFE, 0x5B, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, ]; +/// Makes verifications on the received point, proof and commitment, if true returns a constant value fn point_evaluation( calldata: &Bytes, gas_for_call: u64,