revm_precompile/
bls12_381.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::PrecompileWithAddress;

mod g1;
pub mod g1_add;
pub mod g1_msm;
pub mod g1_mul;
mod g2;
pub mod g2_add;
pub mod g2_msm;
pub mod g2_mul;
pub mod map_fp2_to_g2;
pub mod map_fp_to_g1;
mod msm;
pub mod pairing;
mod utils;

/// Returns the BLS12-381 precompiles with their addresses.
pub fn precompiles() -> impl Iterator<Item = PrecompileWithAddress> {
    [
        g1_add::PRECOMPILE,
        g1_mul::PRECOMPILE,
        g1_msm::PRECOMPILE,
        g2_add::PRECOMPILE,
        g2_mul::PRECOMPILE,
        g2_msm::PRECOMPILE,
        pairing::PRECOMPILE,
        map_fp_to_g1::PRECOMPILE,
        map_fp2_to_g2::PRECOMPILE,
    ]
    .into_iter()
}

#[cfg(test)]
mod test {
    use super::g1_add;
    use super::g1_msm;
    use super::g1_mul;
    use super::g2_add;
    use super::g2_msm;
    use super::g2_mul;
    use super::map_fp2_to_g2;
    use super::map_fp_to_g1;
    use super::msm::msm_required_gas;
    use super::pairing;
    use eyre::Result;
    use revm_primitives::{hex::FromHex, Bytes, PrecompileResult};
    use rstest::rstest;
    use serde_derive::{Deserialize, Serialize};
    use std::{fs, path::Path};

    /// Test vector structure for BLS12-381 precompile tests.
    #[derive(Serialize, Deserialize, Debug)]
    #[serde(rename_all = "PascalCase")]
    struct TestVector {
        input: String,
        expected: Option<String>,
        name: String,
        gas: Option<u64>,
        expected_error: Option<String>,
    }

    #[derive(Serialize, Deserialize, Debug)]
    struct TestVectors(Vec<TestVector>);

    fn load_test_vectors<P: AsRef<Path>>(path: P) -> Result<TestVectors> {
        let file_contents = fs::read_to_string(path)?;
        Ok(serde_json::from_str(&file_contents)?)
    }

    #[rstest]
    #[case::fail_g1_add(g1_add::g1_add, "fail-add_G1_bls.json")]
    #[case::fail_g1_mul(g1_mul::g1_mul, "fail-mul_G1_bls.json")]
    #[case::fail_g1_msm(g1_msm::g1_msm, "fail-multiexp_G1_bls.json")]
    #[case::fail_g2_add(g2_add::g2_add, "fail-add_G2_bls.json")]
    #[case::fail_g2_mul(g2_mul::g2_mul, "fail-mul_G2_bls.json")]
    #[case::fail_g2_msm(g2_msm::g2_msm, "fail-multiexp_G2_bls.json")]
    #[case::fail_pairing(pairing::pairing, "fail-pairing_check_bls.json")]
    #[case::fail_map_fp_to_g1(map_fp_to_g1::map_fp_to_g1, "fail-map_fp_to_G1_bls.json")]
    #[case::fail_map_fp2_to_g2(map_fp2_to_g2::map_fp2_to_g2, "fail-map_fp2_to_G2_bls.json")]
    #[case::g1_add(g1_add::g1_add, "add_G1_bls.json")]
    #[case::g1_mul(g1_mul::g1_mul, "mul_G1_bls.json")]
    #[case::g1_msm(g1_msm::g1_msm, "multiexp_G1_bls.json")]
    #[case::g2_add(g2_add::g2_add, "add_G2_bls.json")]
    #[case::g2_mul(g2_mul::g2_mul, "mul_G2_bls.json")]
    #[case::g2_msm(g2_msm::g2_msm, "multiexp_G2_bls.json")]
    #[case::pairing(pairing::pairing, "pairing_check_bls.json")]
    #[case::map_fp_to_g1(map_fp_to_g1::map_fp_to_g1, "map_fp_to_G1_bls.json")]
    #[case::map_fp2_to_g2(map_fp2_to_g2::map_fp2_to_g2, "map_fp2_to_G2_bls.json")]
    fn test_bls(
        #[case] precompile: fn(input: &Bytes, gas_limit: u64) -> PrecompileResult,
        #[case] file_name: &str,
    ) {
        let test_vectors = load_test_vectors(format!("test-vectors/{file_name}"))
            .unwrap_or_else(|e| panic!("Failed to load test vectors from {file_name}: {e}"));

        for vector in test_vectors.0 {
            let test_name = format!("{file_name}/{}", vector.name);
            let input = Bytes::from_hex(vector.input.clone()).unwrap_or_else(|e| {
                panic!(
                    "could not deserialize input {} as hex in {test_name}: {e}",
                    &vector.input
                )
            });
            let target_gas: u64 = 30_000_000;
            let res = precompile(&input, target_gas);
            if let Some(expected_error) = vector.expected_error {
                assert!(res.is_err(), "expected error {expected_error} didn't happen in {test_name}, got result {res:?}");
            } else {
                let Some(gas) = vector.gas else {
                    panic!("gas is missing in {test_name}");
                };
                let outcome = res.unwrap_or_else(|e: revm_primitives::PrecompileErrors| {
                    panic!("precompile call failed for {test_name}: {e}")
                });
                assert_eq!(
                    gas, outcome.gas_used,
                    "expected gas: {}, actual gas: {} in {test_name}",
                    gas, outcome.gas_used
                );
                let Some(expected) = vector.expected else {
                    panic!("expected output is missing in {test_name}");
                };
                let expected_output = Bytes::from_hex(expected).unwrap();
                assert_eq!(
                    expected_output, outcome.bytes,
                    "expected output: {expected_output}, actual output: {:?} in {test_name}",
                    outcome.bytes
                );
            }
        }
    }

    #[rstest]
    #[case::g1_empty(0, g1_mul::BASE_GAS_FEE, 0)]
    #[case::g1_one_item(160, g1_mul::BASE_GAS_FEE, 14400)]
    #[case::g1_two_items(320, g1_mul::BASE_GAS_FEE, 21312)]
    #[case::g1_ten_items(1600, g1_mul::BASE_GAS_FEE, 50760)]
    #[case::g1_sixty_four_items(10240, g1_mul::BASE_GAS_FEE, 170496)]
    #[case::g1_one_hundred_twenty_eight_items(20480, g1_mul::BASE_GAS_FEE, 267264)]
    #[case::g1_one_hundred_twenty_nine_items(20640, g1_mul::BASE_GAS_FEE, 269352)]
    #[case::g1_two_hundred_fifty_six_items(40960, g1_mul::BASE_GAS_FEE, 534528)]
    fn test_g1_msm_required_gas(
        #[case] input_len: usize,
        #[case] multiplication_cost: u64,
        #[case] expected_output: u64,
    ) {
        let k = input_len / g1_mul::INPUT_LENGTH;

        let actual_output = msm_required_gas(k, multiplication_cost);

        assert_eq!(expected_output, actual_output);
    }
}