revm_precompile/bls12_381/
g1_msm.rs

1//! BLS12-381 G1 msm precompile. More details in [`g1_msm`]
2use crate::{
3    bls12_381::{
4        utils::{pad_g1_point, remove_g1_padding},
5        G1Point,
6    },
7    bls12_381_const::{
8        DISCOUNT_TABLE_G1_MSM, G1_MSM_ADDRESS, G1_MSM_BASE_GAS_FEE, G1_MSM_INPUT_LENGTH,
9        PADDED_G1_LENGTH, SCALAR_LENGTH,
10    },
11    bls12_381_utils::msm_required_gas,
12    crypto, Precompile, PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult,
13};
14
15/// [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537#specification) BLS12_G1MSM precompile.
16pub const PRECOMPILE: Precompile =
17    Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, g1_msm);
18
19/// Implements EIP-2537 G1MSM precompile.
20/// G1 multi-scalar-multiplication call expects `160*k` bytes as an input that is interpreted
21/// as byte concatenation of `k` slices each of them being a byte concatenation
22/// of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32`
23/// bytes).
24/// Output is an encoding of multi-scalar-multiplication operation result - single G1
25/// point (`128` bytes).
26/// See also: <https://eips.ethereum.org/EIPS/eip-2537#abi-for-g1-multiexponentiation>
27pub fn g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult {
28    let input_len = input.len();
29    if input_len == 0 || !input_len.is_multiple_of(G1_MSM_INPUT_LENGTH) {
30        return Err(PrecompileError::Bls12381G1MsmInputLength);
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 valid_pairs_iter = (0..k).map(|i| {
40        let start = i * G1_MSM_INPUT_LENGTH;
41        let padded_g1 = &input[start..start + PADDED_G1_LENGTH];
42        let scalar_bytes = &input[start + PADDED_G1_LENGTH..start + G1_MSM_INPUT_LENGTH];
43
44        // Remove padding from G1 point - this validates padding format
45        let [x, y] = remove_g1_padding(padded_g1)?;
46        let scalar_array: [u8; SCALAR_LENGTH] = scalar_bytes.try_into().unwrap();
47
48        let point: G1Point = (*x, *y);
49        Ok((point, scalar_array))
50    });
51
52    let unpadded_result = crypto().bls12_381_g1_msm(&mut valid_pairs_iter)?;
53
54    // Pad the result for EVM compatibility
55    let padded_result = pad_g1_point(&unpadded_result);
56
57    Ok(PrecompileOutput::new(required_gas, padded_result.into()))
58}
59
60#[cfg(test)]
61mod test {
62    use super::*;
63    use primitives::{hex, Bytes};
64
65    #[test]
66    fn bls_g1multiexp_g1_not_on_curve_but_in_subgroup() {
67        let input = Bytes::from(hex!("000000000000000000000000000000000a2833e497b38ee3ca5c62828bf4887a9f940c9e426c7890a759c20f248c23a7210d2432f4c98a514e524b5184a0ddac00000000000000000000000000000000150772d56bf9509469f9ebcd6e47570429fd31b0e262b66d512e245c38ec37255529f2271fd70066473e393a8bead0c30000000000000000000000000000000000000000000000000000000000000000"));
68        let fail = g1_msm(&input, G1_MSM_BASE_GAS_FEE);
69        assert_eq!(fail, Err(PrecompileError::Bls12381G1NotOnCurve));
70    }
71}