revm_precompile/
bn254.rs

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