revm_precompile/
bn128.rs

1//! BN128 precompiles added in [`EIP-1962`](https://eips.ethereum.org/EIPS/eip-1962)
2use crate::{
3    utilities::{bool_to_bytes32, right_pad},
4    Address, PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress,
5};
6use std::vec::Vec;
7
8cfg_if::cfg_if! {
9    if #[cfg(feature = "bn")]{
10        mod substrate;
11        use substrate::{
12            encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point,
13            read_scalar,
14        };
15    } else {
16        mod arkworks;
17        use arkworks::{
18            encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point,
19            read_scalar,
20        };
21    }
22}
23
24/// Bn128 add precompile
25pub mod add {
26    use super::*;
27
28    /// Bn128 add precompile address
29    pub const ADDRESS: Address = crate::u64_to_address(6);
30
31    /// Bn128 add precompile with ISTANBUL gas rules
32    pub const ISTANBUL_ADD_GAS_COST: u64 = 150;
33
34    /// Bn128 add precompile with ISTANBUL gas rules
35    pub const ISTANBUL: PrecompileWithAddress =
36        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
37            run_add(input, ISTANBUL_ADD_GAS_COST, gas_limit)
38        });
39
40    /// Bn128 add precompile with BYZANTIUM gas rules
41    pub const BYZANTIUM_ADD_GAS_COST: u64 = 500;
42
43    /// Bn128 add precompile with BYZANTIUM gas rules
44    pub const BYZANTIUM: PrecompileWithAddress =
45        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
46            run_add(input, BYZANTIUM_ADD_GAS_COST, gas_limit)
47        });
48}
49
50/// Bn128 mul precompile
51pub mod mul {
52    use super::*;
53
54    /// Bn128 mul precompile address
55    pub const ADDRESS: Address = crate::u64_to_address(7);
56
57    /// Bn128 mul precompile with ISTANBUL gas rules
58    pub const ISTANBUL_MUL_GAS_COST: u64 = 6_000;
59
60    /// Bn128 mul precompile with ISTANBUL gas rules
61    pub const ISTANBUL: PrecompileWithAddress =
62        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
63            run_mul(input, ISTANBUL_MUL_GAS_COST, gas_limit)
64        });
65
66    /// Bn128 mul precompile with BYZANTIUM gas rules
67    pub const BYZANTIUM_MUL_GAS_COST: u64 = 40_000;
68
69    /// Bn128 mul precompile with BYZANTIUM gas rules
70    pub const BYZANTIUM: PrecompileWithAddress =
71        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
72            run_mul(input, BYZANTIUM_MUL_GAS_COST, gas_limit)
73        });
74}
75
76/// Bn128 pair precompile
77pub mod pair {
78    use super::*;
79
80    /// Bn128 pair precompile address
81    pub const ADDRESS: Address = crate::u64_to_address(8);
82
83    /// Bn128 pair precompile with ISTANBUL gas rules
84    pub const ISTANBUL_PAIR_PER_POINT: u64 = 34_000;
85
86    /// Bn128 pair precompile with ISTANBUL gas rules
87    pub const ISTANBUL_PAIR_BASE: u64 = 45_000;
88
89    /// Bn128 pair precompile with ISTANBUL gas rules
90    pub const ISTANBUL: PrecompileWithAddress =
91        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
92            run_pair(
93                input,
94                ISTANBUL_PAIR_PER_POINT,
95                ISTANBUL_PAIR_BASE,
96                gas_limit,
97            )
98        });
99
100    /// Bn128 pair precompile with BYZANTIUM gas rules
101    pub const BYZANTIUM_PAIR_PER_POINT: u64 = 80_000;
102
103    /// Bn128 pair precompile with BYZANTIUM gas rules
104    pub const BYZANTIUM_PAIR_BASE: u64 = 100_000;
105
106    /// Bn128 pair precompile with BYZANTIUM gas rules
107    pub const BYZANTIUM: PrecompileWithAddress =
108        PrecompileWithAddress(ADDRESS, |input, gas_limit| {
109            run_pair(
110                input,
111                BYZANTIUM_PAIR_PER_POINT,
112                BYZANTIUM_PAIR_BASE,
113                gas_limit,
114            )
115        });
116}
117
118/// FQ_LEN specifies the number of bytes needed to represent an
119/// Fq element. This is an element in the base field of BN254.
120///
121/// Note: The base field is used to define G1 and G2 elements.
122const FQ_LEN: usize = 32;
123
124/// SCALAR_LEN specifies the number of bytes needed to represent an Fr element.
125/// This is an element in the scalar field of BN254.
126const SCALAR_LEN: usize = 32;
127
128/// FQ2_LEN specifies the number of bytes needed to represent an
129/// Fq^2 element.
130///
131/// Note: This is the quadratic extension of Fq, and by definition
132/// means we need 2 Fq elements.
133const FQ2_LEN: usize = 2 * FQ_LEN;
134
135/// G1_LEN specifies the number of bytes needed to represent a G1 element.
136///
137/// Note: A G1 element contains 2 Fq elements.
138const G1_LEN: usize = 2 * FQ_LEN;
139/// G2_LEN specifies the number of bytes needed to represent a G2 element.
140///
141/// Note: A G2 element contains 2 Fq^2 elements.
142const G2_LEN: usize = 2 * FQ2_LEN;
143
144/// Input length for the add operation.
145/// `ADD` takes two uncompressed G1 points (64 bytes each).
146pub const ADD_INPUT_LEN: usize = 2 * G1_LEN;
147
148/// Input length for the multiplication operation.
149/// `MUL` takes an uncompressed G1 point (64 bytes) and scalar (32 bytes).
150pub const MUL_INPUT_LEN: usize = G1_LEN + SCALAR_LEN;
151
152/// Pair element length.
153/// `PAIR` elements are composed of an uncompressed G1 point (64 bytes) and an uncompressed G2 point
154/// (128 bytes).
155pub const PAIR_ELEMENT_LEN: usize = G1_LEN + G2_LEN;
156
157/// Run the Bn128 add precompile
158pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult {
159    if gas_cost > gas_limit {
160        return Err(PrecompileError::OutOfGas);
161    }
162
163    let input = right_pad::<ADD_INPUT_LEN>(input);
164
165    let p1 = read_g1_point(&input[..G1_LEN])?;
166    let p2 = read_g1_point(&input[G1_LEN..])?;
167    let result = g1_point_add(p1, p2);
168
169    let output = encode_g1_point(result);
170
171    Ok(PrecompileOutput::new(gas_cost, output.into()))
172}
173
174/// Run the Bn128 mul precompile
175pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult {
176    if gas_cost > gas_limit {
177        return Err(PrecompileError::OutOfGas);
178    }
179
180    let input = right_pad::<MUL_INPUT_LEN>(input);
181
182    let p = read_g1_point(&input[..G1_LEN])?;
183
184    let scalar = read_scalar(&input[G1_LEN..G1_LEN + SCALAR_LEN]);
185    let result = g1_point_mul(p, scalar);
186
187    let output = encode_g1_point(result);
188
189    Ok(PrecompileOutput::new(gas_cost, output.into()))
190}
191
192/// Run the Bn128 pair precompile
193pub fn run_pair(
194    input: &[u8],
195    pair_per_point_cost: u64,
196    pair_base_cost: u64,
197    gas_limit: u64,
198) -> PrecompileResult {
199    let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * pair_per_point_cost + pair_base_cost;
200    if gas_used > gas_limit {
201        return Err(PrecompileError::OutOfGas);
202    }
203
204    if input.len() % PAIR_ELEMENT_LEN != 0 {
205        return Err(PrecompileError::Bn128PairLength);
206    }
207
208    let elements = input.len() / PAIR_ELEMENT_LEN;
209
210    let mut points = Vec::with_capacity(elements);
211
212    for idx in 0..elements {
213        // Offset to the start of the pairing element at index `idx` in the byte slice
214        let start = idx * PAIR_ELEMENT_LEN;
215        let g1_start = start;
216        // Offset to the start of the G2 element in the pairing element
217        // This is where G1 ends.
218        let g2_start = start + G1_LEN;
219
220        let encoded_g1_element = &input[g1_start..g2_start];
221        let encoded_g2_element = &input[g2_start..g2_start + G2_LEN];
222
223        // If either the G1 or G2 element is the encoded representation
224        // of the point at infinity, then these two points are no-ops
225        // in the pairing computation.
226        //
227        // Note: we do not skip the validation of these two elements even if
228        // one of them is the point at infinity because we could have G1 be
229        // the point at infinity and G2 be an invalid element or vice versa.
230        // In that case, the precompile should error because one of the elements
231        // was invalid.
232        let g1_is_zero = encoded_g1_element.iter().all(|i| *i == 0);
233        let g2_is_zero = encoded_g2_element.iter().all(|i| *i == 0);
234
235        // Get G1 and G2 points from the input
236        let a = read_g1_point(encoded_g1_element)?;
237        let b = read_g2_point(encoded_g2_element)?;
238
239        if !g1_is_zero && !g2_is_zero {
240            points.push((a, b));
241        }
242    }
243
244    let success = pairing_check(&points);
245
246    Ok(PrecompileOutput::new(gas_used, bool_to_bytes32(success)))
247}
248
249#[cfg(test)]
250mod tests {
251    use crate::{
252        bn128::{
253            add::BYZANTIUM_ADD_GAS_COST,
254            mul::BYZANTIUM_MUL_GAS_COST,
255            pair::{BYZANTIUM_PAIR_BASE, BYZANTIUM_PAIR_PER_POINT},
256        },
257        PrecompileError,
258    };
259    use primitives::hex;
260
261    use super::*;
262
263    #[test]
264    fn test_alt_bn128_add() {
265        let input = hex::decode(
266            "\
267             18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9\
268             063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266\
269             07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed\
270             06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7",
271        )
272        .unwrap();
273        let expected = hex::decode(
274            "\
275            2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703\
276            301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915",
277        )
278        .unwrap();
279
280        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
281        assert_eq!(outcome.bytes, expected);
282
283        // Zero sum test
284        let input = hex::decode(
285            "\
286            0000000000000000000000000000000000000000000000000000000000000000\
287            0000000000000000000000000000000000000000000000000000000000000000\
288            0000000000000000000000000000000000000000000000000000000000000000\
289            0000000000000000000000000000000000000000000000000000000000000000",
290        )
291        .unwrap();
292        let expected = hex::decode(
293            "\
294            0000000000000000000000000000000000000000000000000000000000000000\
295            0000000000000000000000000000000000000000000000000000000000000000",
296        )
297        .unwrap();
298
299        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
300        assert_eq!(outcome.bytes, expected);
301
302        // Out of gas test
303        let input = hex::decode(
304            "\
305            0000000000000000000000000000000000000000000000000000000000000000\
306            0000000000000000000000000000000000000000000000000000000000000000\
307            0000000000000000000000000000000000000000000000000000000000000000\
308            0000000000000000000000000000000000000000000000000000000000000000",
309        )
310        .unwrap();
311
312        let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499);
313
314        assert!(matches!(res, Err(PrecompileError::OutOfGas)));
315
316        // No input test
317        let input = [0u8; 0];
318        let expected = hex::decode(
319            "\
320            0000000000000000000000000000000000000000000000000000000000000000\
321            0000000000000000000000000000000000000000000000000000000000000000",
322        )
323        .unwrap();
324
325        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
326        assert_eq!(outcome.bytes, expected);
327
328        // Point not on curve fail
329        let input = hex::decode(
330            "\
331            1111111111111111111111111111111111111111111111111111111111111111\
332            1111111111111111111111111111111111111111111111111111111111111111\
333            1111111111111111111111111111111111111111111111111111111111111111\
334            1111111111111111111111111111111111111111111111111111111111111111",
335        )
336        .unwrap();
337
338        let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500);
339        assert!(matches!(
340            res,
341            Err(PrecompileError::Bn128AffineGFailedToCreate)
342        ));
343    }
344
345    #[test]
346    fn test_alt_bn128_mul() {
347        let input = hex::decode(
348            "\
349            2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb7\
350            21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204\
351            00000000000000000000000000000000000000000000000011138ce750fa15c2",
352        )
353        .unwrap();
354        let expected = hex::decode(
355            "\
356            070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c\
357            031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc",
358        )
359        .unwrap();
360
361        let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap();
362        assert_eq!(outcome.bytes, expected);
363
364        // Out of gas test
365        let input = hex::decode(
366            "\
367            0000000000000000000000000000000000000000000000000000000000000000\
368            0000000000000000000000000000000000000000000000000000000000000000\
369            0200000000000000000000000000000000000000000000000000000000000000",
370        )
371        .unwrap();
372
373        let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999);
374        assert!(matches!(res, Err(PrecompileError::OutOfGas)));
375
376        // Zero multiplication test
377        let input = hex::decode(
378            "\
379            0000000000000000000000000000000000000000000000000000000000000000\
380            0000000000000000000000000000000000000000000000000000000000000000\
381            0200000000000000000000000000000000000000000000000000000000000000",
382        )
383        .unwrap();
384        let expected = hex::decode(
385            "\
386            0000000000000000000000000000000000000000000000000000000000000000\
387            0000000000000000000000000000000000000000000000000000000000000000",
388        )
389        .unwrap();
390
391        let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap();
392        assert_eq!(outcome.bytes, expected);
393
394        // No input test
395        let input = [0u8; 0];
396        let expected = hex::decode(
397            "\
398            0000000000000000000000000000000000000000000000000000000000000000\
399            0000000000000000000000000000000000000000000000000000000000000000",
400        )
401        .unwrap();
402
403        let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap();
404        assert_eq!(outcome.bytes, expected);
405
406        // Point not on curve fail
407        let input = hex::decode(
408            "\
409            1111111111111111111111111111111111111111111111111111111111111111\
410            1111111111111111111111111111111111111111111111111111111111111111\
411            0f00000000000000000000000000000000000000000000000000000000000000",
412        )
413        .unwrap();
414
415        let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000);
416        assert!(matches!(
417            res,
418            Err(PrecompileError::Bn128AffineGFailedToCreate)
419        ));
420    }
421
422    #[test]
423    fn test_alt_bn128_pair() {
424        let input = hex::decode(
425            "\
426            1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\
427            3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\
428            209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\
429            04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\
430            2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\
431            120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\
432            111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\
433            2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\
434            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\
435            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\
436            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\
437            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
438        )
439        .unwrap();
440        let expected =
441            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
442                .unwrap();
443
444        let outcome = run_pair(
445            &input,
446            BYZANTIUM_PAIR_PER_POINT,
447            BYZANTIUM_PAIR_BASE,
448            260_000,
449        )
450        .unwrap();
451        assert_eq!(outcome.bytes, expected);
452
453        // Out of gas test
454        let input = hex::decode(
455            "\
456            1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\
457            3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\
458            209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\
459            04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\
460            2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\
461            120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\
462            111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\
463            2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\
464            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\
465            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\
466            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\
467            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
468        )
469        .unwrap();
470
471        let res = run_pair(
472            &input,
473            BYZANTIUM_PAIR_PER_POINT,
474            BYZANTIUM_PAIR_BASE,
475            259_999,
476        );
477        assert!(matches!(res, Err(PrecompileError::OutOfGas)));
478
479        // No input test
480        let input = [0u8; 0];
481        let expected =
482            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
483                .unwrap();
484
485        let outcome = run_pair(
486            &input,
487            BYZANTIUM_PAIR_PER_POINT,
488            BYZANTIUM_PAIR_BASE,
489            260_000,
490        )
491        .unwrap();
492        assert_eq!(outcome.bytes, expected);
493
494        // Point not on curve fail
495        let input = hex::decode(
496            "\
497            1111111111111111111111111111111111111111111111111111111111111111\
498            1111111111111111111111111111111111111111111111111111111111111111\
499            1111111111111111111111111111111111111111111111111111111111111111\
500            1111111111111111111111111111111111111111111111111111111111111111\
501            1111111111111111111111111111111111111111111111111111111111111111\
502            1111111111111111111111111111111111111111111111111111111111111111",
503        )
504        .unwrap();
505
506        let res = run_pair(
507            &input,
508            BYZANTIUM_PAIR_PER_POINT,
509            BYZANTIUM_PAIR_BASE,
510            260_000,
511        );
512        assert!(matches!(
513            res,
514            Err(PrecompileError::Bn128AffineGFailedToCreate)
515        ));
516
517        // Invalid input length
518        let input = hex::decode(
519            "\
520            1111111111111111111111111111111111111111111111111111111111111111\
521            1111111111111111111111111111111111111111111111111111111111111111\
522            111111111111111111111111111111\
523        ",
524        )
525        .unwrap();
526
527        let res = run_pair(
528            &input,
529            BYZANTIUM_PAIR_PER_POINT,
530            BYZANTIUM_PAIR_BASE,
531            260_000,
532        );
533        assert!(matches!(res, Err(PrecompileError::Bn128PairLength)));
534    }
535}