Skip to main content

revm_precompile/
bn254.rs

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