revm_precompile/bls12_381/
g2_msm.rs

1use super::crypto_backend::{encode_g2_point, p2_msm, read_g2, read_scalar};
2use super::utils::remove_g2_padding;
3use crate::bls12_381_const::{
4    DISCOUNT_TABLE_G2_MSM, G2_MSM_ADDRESS, G2_MSM_BASE_GAS_FEE, G2_MSM_INPUT_LENGTH,
5    PADDED_G2_LENGTH, SCALAR_LENGTH,
6};
7use crate::bls12_381_utils::msm_required_gas;
8use crate::{PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress};
9use primitives::Bytes;
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(super) fn g2_msm(input: &Bytes, 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 {}, was {}",
28            G2_MSM_INPUT_LENGTH, input_len
29        )));
30    }
31
32    let k = input_len / G2_MSM_INPUT_LENGTH;
33    let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G2_MSM, G2_MSM_BASE_GAS_FEE);
34    if required_gas > gas_limit {
35        return Err(PrecompileError::OutOfGas);
36    }
37
38    let mut g2_points: Vec<_> = Vec::with_capacity(k);
39    let mut scalars = Vec::with_capacity(k);
40    for i in 0..k {
41        let encoded_g2_element =
42            &input[i * G2_MSM_INPUT_LENGTH..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH];
43        let encoded_scalar = &input[i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH
44            ..i * G2_MSM_INPUT_LENGTH + PADDED_G2_LENGTH + SCALAR_LENGTH];
45
46        // Filter out points infinity as an optimization, since it is a no-op.
47        // Note: Previously, points were being batch converted from Jacobian to Affine. In `blst`, this would essentially,
48        // zero out all of the points. Since all points are in affine, this bug is avoided.
49        if encoded_g2_element.iter().all(|i| *i == 0) {
50            continue;
51        }
52
53        let [a_x_0, a_x_1, a_y_0, a_y_1] = remove_g2_padding(encoded_g2_element)?;
54
55        // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
56        //
57        // So we set the subgroup_check flag to `true`
58        let p0_aff = read_g2(a_x_0, a_x_1, a_y_0, a_y_1)?;
59
60        // If the scalar is zero, then this is a no-op.
61        //
62        // Note: This check is made after checking that g2 is valid.
63        // this is because we want the precompile to error when
64        // G2 is invalid, even if the scalar is zero.
65        if encoded_scalar.iter().all(|i| *i == 0) {
66            continue;
67        }
68
69        // Convert affine point to Jacobian coordinates using our helper function
70        g2_points.push(p0_aff);
71        scalars.push(read_scalar(encoded_scalar)?);
72    }
73
74    // Return infinity point if all points are infinity
75    if g2_points.is_empty() {
76        return Ok(PrecompileOutput::new(
77            required_gas,
78            [0; PADDED_G2_LENGTH].into(),
79        ));
80    }
81
82    // Perform multi-scalar multiplication using the safe wrapper
83    let multiexp_aff = p2_msm(g2_points, scalars);
84
85    let out = encode_g2_point(&multiexp_aff);
86    Ok(PrecompileOutput::new(required_gas, out.into()))
87}