revm_precompile/bls12_381/
g2_msm.rs

1//! BLS12-381 G2 msm precompile. More details in [`g2_msm`]
2use super::crypto_backend::{encode_g2_point, p2_msm, read_g2, read_scalar};
3use super::utils::remove_g2_padding;
4use crate::bls12_381_const::{
5    DISCOUNT_TABLE_G2_MSM, G2_MSM_ADDRESS, G2_MSM_BASE_GAS_FEE, G2_MSM_INPUT_LENGTH,
6    PADDED_G2_LENGTH, SCALAR_LENGTH,
7};
8use crate::bls12_381_utils::msm_required_gas;
9use crate::{PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress};
10use std::vec::Vec;
11
12/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G2MSM precompile.
13pub const PRECOMPILE: PrecompileWithAddress = PrecompileWithAddress(G2_MSM_ADDRESS, g2_msm);
14
15/// Implements EIP-2537 G2MSM precompile.
16/// G2 multi-scalar-multiplication call expects `288*k` bytes as an input that is interpreted
17/// as byte concatenation of `k` slices each of them being a byte concatenation
18/// of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32`
19/// bytes).
20/// Output is an encoding of multi-scalar-multiplication operation result - single G2
21/// point (`256` bytes).
22/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g2-multiexponentiation>
23pub fn g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult {
24    let input_len = input.len();
25    if input_len == 0 || input_len % G2_MSM_INPUT_LENGTH != 0 {
26        return Err(PrecompileError::Other(format!(
27            "G2MSM input length should be multiple of {G2_MSM_INPUT_LENGTH}, was {input_len}"
28        )));
29    }
30
31    let k = input_len / G2_MSM_INPUT_LENGTH;
32    let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G2_MSM, G2_MSM_BASE_GAS_FEE);
33    if required_gas > gas_limit {
34        return Err(PrecompileError::OutOfGas);
35    }
36
37    let mut g2_points: Vec<_> = Vec::with_capacity(k);
38    let mut scalars = Vec::with_capacity(k);
39    for i in 0..k {
40        let encoded_g2_element =
41            &input[i * G2_MSM_INPUT_LENGTH..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH];
42        let encoded_scalar = &input[i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH
43            ..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH + SCALAR_LENGTH];
44
45        // Filter out points infinity as an optimization, since it is a no-op.
46        // Note: Previously, points were being batch converted from Jacobian to Affine. In `blst`, this would essentially,
47        // zero out all of the points. Since all points are in affine, this bug is avoided.
48        if encoded_g2_element.iter().all(|i| *i == 0) {
49            continue;
50        }
51
52        let [a_x_0, a_x_1, a_y_0, a_y_1] = remove_g2_padding(encoded_g2_element)?;
53
54        // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
55        //
56        // So we set the subgroup_check flag to `true`
57        let p0_aff = read_g2(a_x_0, a_x_1, a_y_0, a_y_1)?;
58
59        // If the scalar is zero, then this is a no-op.
60        //
61        // Note: This check is made after checking that g2 is valid.
62        // this is because we want the precompile to error when
63        // G2 is invalid, even if the scalar is zero.
64        if encoded_scalar.iter().all(|i| *i == 0) {
65            continue;
66        }
67
68        // Convert affine point to Jacobian coordinates using our helper function
69        g2_points.push(p0_aff);
70        scalars.push(read_scalar(encoded_scalar)?);
71    }
72
73    // Return infinity point if all points are infinity
74    if g2_points.is_empty() {
75        return Ok(PrecompileOutput::new(
76            required_gas,
77            [0; PADDED_G2_LENGTH].into(),
78        ));
79    }
80
81    // Perform multi-scalar multiplication using the safe wrapper
82    let multiexp_aff = p2_msm(g2_points, scalars);
83
84    let out = encode_g2_point(&multiexp_aff);
85    Ok(PrecompileOutput::new(required_gas, out.into()))
86}