revm_precompile/bls12_381/
g1_msm.rs

1//! BLS12-381 G1 msm precompile. More details in [`g1_msm`]
2use super::crypto_backend::{encode_g1_point, p1_msm, read_g1, read_scalar};
3use crate::bls12_381::utils::remove_g1_padding;
4use crate::bls12_381_const::{
5    DISCOUNT_TABLE_G1_MSM, G1_MSM_ADDRESS, G1_MSM_BASE_GAS_FEE, G1_MSM_INPUT_LENGTH,
6    PADDED_G1_LENGTH, SCALAR_LENGTH,
7};
8use crate::bls12_381_utils::msm_required_gas;
9use crate::{PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress};
10use primitives::Bytes;
11use std::vec::Vec;
12
13/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MSM precompile.
14pub const PRECOMPILE: PrecompileWithAddress = PrecompileWithAddress(G1_MSM_ADDRESS, g1_msm);
15
16/// Implements EIP-2537 G1MSM precompile.
17/// G1 multi-scalar-multiplication call expects `160*k` bytes as an input that is interpreted
18/// as byte concatenation of `k` slices each of them being a byte concatenation
19/// of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32`
20/// bytes).
21/// Output is an encoding of multi-scalar-multiplication operation result - single G1
22/// point (`128` bytes).
23/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-multiexponentiation>
24pub fn g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult {
25    let input_len = input.len();
26    if input_len == 0 || input_len % G1_MSM_INPUT_LENGTH != 0 {
27        return Err(PrecompileError::Other(format!(
28            "G1MSM input length should be multiple of {}, was {}",
29            G1_MSM_INPUT_LENGTH, input_len
30        )));
31    }
32
33    let k = input_len / G1_MSM_INPUT_LENGTH;
34    let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G1_MSM, G1_MSM_BASE_GAS_FEE);
35    if required_gas > gas_limit {
36        return Err(PrecompileError::OutOfGas);
37    }
38
39    let mut g1_points: Vec<_> = Vec::with_capacity(k);
40    let mut scalars = Vec::with_capacity(k);
41    for i in 0..k {
42        let encoded_g1_element =
43            &input[i * G1_MSM_INPUT_LENGTH..i * G1_MSM_INPUT_LENGTH + PADDED_G1_LENGTH];
44        let encoded_scalar = &input[i * G1_MSM_INPUT_LENGTH + PADDED_G1_LENGTH
45            ..i * G1_MSM_INPUT_LENGTH + PADDED_G1_LENGTH + SCALAR_LENGTH];
46
47        // Filter out points infinity as an optimization, since it is a no-op.
48        // Note: Previously, points were being batch converted from Jacobian to Affine.
49        // In `blst`, this would essentially, zero out all of the points.
50        // Since all points are now in affine, this bug is avoided.
51        if encoded_g1_element.iter().all(|i| *i == 0) {
52            continue;
53        }
54
55        let [a_x, a_y] = remove_g1_padding(encoded_g1_element)?;
56
57        // NB: Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
58        let p0_aff = read_g1(a_x, a_y)?;
59
60        // If the scalar is zero, then this is a no-op.
61        //
62        // Note: This check is made after checking that g1 is valid.
63        // this is because we want the precompile to error when
64        // G1 is invalid, even if the scalar is zero.
65        if encoded_scalar.iter().all(|i| *i == 0) {
66            continue;
67        }
68
69        g1_points.push(p0_aff);
70        scalars.push(read_scalar(encoded_scalar)?);
71    }
72
73    // Return the encoding for the point at the infinity according to EIP-2537
74    // if there are no points in the MSM.
75    const ENCODED_POINT_AT_INFINITY: [u8; PADDED_G1_LENGTH] = [0; PADDED_G1_LENGTH];
76    if g1_points.is_empty() {
77        return Ok(PrecompileOutput::new(
78            required_gas,
79            Bytes::from_static(&ENCODED_POINT_AT_INFINITY),
80        ));
81    }
82
83    let multiexp_aff = p1_msm(g1_points, scalars);
84
85    let out = encode_g1_point(&multiexp_aff);
86    Ok(PrecompileOutput::new(required_gas, out.into()))
87}
88
89#[cfg(test)]
90mod test {
91    use super::*;
92    use primitives::hex;
93
94    #[test]
95    fn bls_g1multiexp_g1_not_on_curve_but_in_subgroup() {
96        let input = Bytes::from(hex!("000000000000000000000000000000000a2833e497b38ee3ca5c62828bf4887a9f940c9e426c7890a759c20f248c23a7210d2432f4c98a514e524b5184a0ddac00000000000000000000000000000000150772d56bf9509469f9ebcd6e47570429fd31b0e262b66d512e245c38ec37255529f2271fd70066473e393a8bead0c30000000000000000000000000000000000000000000000000000000000000000"));
97        let fail = g1_msm(&input, G1_MSM_BASE_GAS_FEE);
98        assert_eq!(
99            fail,
100            Err(PrecompileError::Other(
101                "Element not on G1 curve".to_string()
102            ))
103        );
104    }
105}