revm_precompile/
bn128.rs

1use crate::{
2    utilities::{bool_to_bytes32, right_pad},
3    Address, Error, Precompile, PrecompileResult, PrecompileWithAddress,
4};
5use bn::{AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2};
6use revm_primitives::PrecompileOutput;
7use std::vec::Vec;
8
9pub mod add {
10    use super::*;
11
12    const ADDRESS: Address = crate::u64_to_address(6);
13
14    pub const ISTANBUL_ADD_GAS_COST: u64 = 150;
15    pub const ISTANBUL: PrecompileWithAddress = PrecompileWithAddress(
16        ADDRESS,
17        Precompile::Standard(|input, gas_limit| run_add(input, ISTANBUL_ADD_GAS_COST, gas_limit)),
18    );
19
20    pub const BYZANTIUM_ADD_GAS_COST: u64 = 500;
21    pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress(
22        ADDRESS,
23        Precompile::Standard(|input, gas_limit| run_add(input, BYZANTIUM_ADD_GAS_COST, gas_limit)),
24    );
25}
26
27pub mod mul {
28    use super::*;
29
30    const ADDRESS: Address = crate::u64_to_address(7);
31
32    pub const ISTANBUL_MUL_GAS_COST: u64 = 6_000;
33    pub const ISTANBUL: PrecompileWithAddress = PrecompileWithAddress(
34        ADDRESS,
35        Precompile::Standard(|input, gas_limit| run_mul(input, ISTANBUL_MUL_GAS_COST, gas_limit)),
36    );
37
38    pub const BYZANTIUM_MUL_GAS_COST: u64 = 40_000;
39    pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress(
40        ADDRESS,
41        Precompile::Standard(|input, gas_limit| run_mul(input, BYZANTIUM_MUL_GAS_COST, gas_limit)),
42    );
43}
44
45pub mod pair {
46    use super::*;
47
48    pub const ADDRESS: Address = crate::u64_to_address(8);
49
50    pub const ISTANBUL_PAIR_PER_POINT: u64 = 34_000;
51    pub const ISTANBUL_PAIR_BASE: u64 = 45_000;
52    pub const ISTANBUL: PrecompileWithAddress = PrecompileWithAddress(
53        ADDRESS,
54        Precompile::Standard(|input, gas_limit| {
55            run_pair(
56                input,
57                ISTANBUL_PAIR_PER_POINT,
58                ISTANBUL_PAIR_BASE,
59                gas_limit,
60            )
61        }),
62    );
63
64    pub const BYZANTIUM_PAIR_PER_POINT: u64 = 80_000;
65    pub const BYZANTIUM_PAIR_BASE: u64 = 100_000;
66    pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress(
67        ADDRESS,
68        Precompile::Standard(|input, gas_limit| {
69            run_pair(
70                input,
71                BYZANTIUM_PAIR_PER_POINT,
72                BYZANTIUM_PAIR_BASE,
73                gas_limit,
74            )
75        }),
76    );
77}
78
79/// Input length for the add operation.
80/// `ADD` takes two uncompressed G1 points (64 bytes each).
81pub const ADD_INPUT_LEN: usize = 64 + 64;
82
83/// Input length for the multiplication operation.
84/// `MUL` takes an uncompressed G1 point (64 bytes) and scalar (32 bytes).
85pub const MUL_INPUT_LEN: usize = 64 + 32;
86
87/// Pair element length.
88/// `PAIR` elements are composed of an uncompressed G1 point (64 bytes) and an uncompressed G2 point
89/// (128 bytes).
90pub const PAIR_ELEMENT_LEN: usize = 64 + 128;
91
92/// Reads a single `Fq` from the input slice.
93///
94/// # Panics
95///
96/// Panics if the input is not at least 32 bytes long.
97#[inline]
98pub fn read_fq(input: &[u8]) -> Result<Fq, Error> {
99    Fq::from_slice(&input[..32]).map_err(|_| Error::Bn128FieldPointNotAMember)
100}
101
102/// Reads the `x` and `y` points from the input slice.
103///
104/// # Panics
105///
106/// Panics if the input is not at least 64 bytes long.
107#[inline]
108pub fn read_point(input: &[u8]) -> Result<G1, Error> {
109    let px = read_fq(&input[0..32])?;
110    let py = read_fq(&input[32..64])?;
111    new_g1_point(px, py)
112}
113
114/// Creates a new `G1` point from the given `x` and `y` coordinates.
115pub fn new_g1_point(px: Fq, py: Fq) -> Result<G1, Error> {
116    if px == Fq::zero() && py == Fq::zero() {
117        Ok(G1::zero())
118    } else {
119        AffineG1::new(px, py)
120            .map(Into::into)
121            .map_err(|_| Error::Bn128AffineGFailedToCreate)
122    }
123}
124
125pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult {
126    if gas_cost > gas_limit {
127        return Err(Error::OutOfGas.into());
128    }
129
130    let input = right_pad::<ADD_INPUT_LEN>(input);
131
132    let p1 = read_point(&input[..64])?;
133    let p2 = read_point(&input[64..])?;
134
135    let mut output = [0u8; 64];
136    if let Some(sum) = AffineG1::from_jacobian(p1 + p2) {
137        sum.x().to_big_endian(&mut output[..32]).unwrap();
138        sum.y().to_big_endian(&mut output[32..]).unwrap();
139    }
140    Ok(PrecompileOutput::new(gas_cost, output.into()))
141}
142
143pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult {
144    if gas_cost > gas_limit {
145        return Err(Error::OutOfGas.into());
146    }
147
148    let input = right_pad::<MUL_INPUT_LEN>(input);
149
150    let p = read_point(&input[..64])?;
151
152    // `Fr::from_slice` can only fail when the length is not 32.
153    let fr = bn::Fr::from_slice(&input[64..96]).unwrap();
154
155    let mut output = [0u8; 64];
156    if let Some(mul) = AffineG1::from_jacobian(p * fr) {
157        mul.x().to_big_endian(&mut output[..32]).unwrap();
158        mul.y().to_big_endian(&mut output[32..]).unwrap();
159    }
160    Ok(PrecompileOutput::new(gas_cost, output.into()))
161}
162
163pub fn run_pair(
164    input: &[u8],
165    pair_per_point_cost: u64,
166    pair_base_cost: u64,
167    gas_limit: u64,
168) -> PrecompileResult {
169    let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * pair_per_point_cost + pair_base_cost;
170    if gas_used > gas_limit {
171        return Err(Error::OutOfGas.into());
172    }
173
174    if input.len() % PAIR_ELEMENT_LEN != 0 {
175        return Err(Error::Bn128PairLength.into());
176    }
177
178    let success = if input.is_empty() {
179        true
180    } else {
181        let elements = input.len() / PAIR_ELEMENT_LEN;
182
183        let mut points = Vec::with_capacity(elements);
184
185        // read points
186        for idx in 0..elements {
187            let read_fq_at = |n: usize| {
188                debug_assert!(n < PAIR_ELEMENT_LEN / 32);
189                let start = idx * PAIR_ELEMENT_LEN + n * 32;
190                // SAFETY: We're reading `6 * 32 == PAIR_ELEMENT_LEN` bytes from `input[idx..]`
191                // per iteration. This is guaranteed to be in-bounds.
192                let slice = unsafe { input.get_unchecked(start..start + 32) };
193                Fq::from_slice(slice).map_err(|_| Error::Bn128FieldPointNotAMember)
194            };
195            let ax = read_fq_at(0)?;
196            let ay = read_fq_at(1)?;
197            let bay = read_fq_at(2)?;
198            let bax = read_fq_at(3)?;
199            let bby = read_fq_at(4)?;
200            let bbx = read_fq_at(5)?;
201
202            let a = new_g1_point(ax, ay)?;
203            let b = {
204                let ba = Fq2::new(bax, bay);
205                let bb = Fq2::new(bbx, bby);
206                // TODO: check whether or not we need these zero checks
207                if ba.is_zero() && bb.is_zero() {
208                    G2::zero()
209                } else {
210                    G2::from(AffineG2::new(ba, bb).map_err(|_| Error::Bn128AffineGFailedToCreate)?)
211                }
212            };
213
214            points.push((a, b));
215        }
216
217        let mul = bn::pairing_batch(&points);
218
219        mul == Gt::one()
220    };
221    Ok(PrecompileOutput::new(gas_used, bool_to_bytes32(success)))
222}
223
224#[cfg(test)]
225mod tests {
226    use crate::bn128::add::BYZANTIUM_ADD_GAS_COST;
227    use crate::bn128::mul::BYZANTIUM_MUL_GAS_COST;
228    use crate::bn128::pair::{BYZANTIUM_PAIR_BASE, BYZANTIUM_PAIR_PER_POINT};
229    use revm_primitives::{hex, PrecompileErrors};
230
231    use super::*;
232
233    #[test]
234    fn test_alt_bn128_add() {
235        let input = hex::decode(
236            "\
237             18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9\
238             063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266\
239             07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed\
240             06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7",
241        )
242        .unwrap();
243        let expected = hex::decode(
244            "\
245            2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703\
246            301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915",
247        )
248        .unwrap();
249
250        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
251        assert_eq!(outcome.bytes, expected);
252
253        // zero sum test
254        let input = hex::decode(
255            "\
256            0000000000000000000000000000000000000000000000000000000000000000\
257            0000000000000000000000000000000000000000000000000000000000000000\
258            0000000000000000000000000000000000000000000000000000000000000000\
259            0000000000000000000000000000000000000000000000000000000000000000",
260        )
261        .unwrap();
262        let expected = hex::decode(
263            "\
264            0000000000000000000000000000000000000000000000000000000000000000\
265            0000000000000000000000000000000000000000000000000000000000000000",
266        )
267        .unwrap();
268
269        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
270        assert_eq!(outcome.bytes, expected);
271
272        // out of gas test
273        let input = hex::decode(
274            "\
275            0000000000000000000000000000000000000000000000000000000000000000\
276            0000000000000000000000000000000000000000000000000000000000000000\
277            0000000000000000000000000000000000000000000000000000000000000000\
278            0000000000000000000000000000000000000000000000000000000000000000",
279        )
280        .unwrap();
281
282        let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499);
283        println!("{:?}", res);
284        assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas))));
285
286        // no input test
287        let input = [0u8; 0];
288        let expected = hex::decode(
289            "\
290            0000000000000000000000000000000000000000000000000000000000000000\
291            0000000000000000000000000000000000000000000000000000000000000000",
292        )
293        .unwrap();
294
295        let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap();
296        assert_eq!(outcome.bytes, expected);
297
298        // point not on curve fail
299        let input = hex::decode(
300            "\
301            1111111111111111111111111111111111111111111111111111111111111111\
302            1111111111111111111111111111111111111111111111111111111111111111\
303            1111111111111111111111111111111111111111111111111111111111111111\
304            1111111111111111111111111111111111111111111111111111111111111111",
305        )
306        .unwrap();
307
308        let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500);
309        assert!(matches!(
310            res,
311            Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate))
312        ));
313    }
314
315    #[test]
316    fn test_alt_bn128_mul() {
317        let input = hex::decode(
318            "\
319            2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb7\
320            21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204\
321            00000000000000000000000000000000000000000000000011138ce750fa15c2",
322        )
323        .unwrap();
324        let expected = hex::decode(
325            "\
326            070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c\
327            031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc",
328        )
329        .unwrap();
330
331        let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap();
332        assert_eq!(outcome.bytes, expected);
333
334        // out of gas test
335        let input = hex::decode(
336            "\
337            0000000000000000000000000000000000000000000000000000000000000000\
338            0000000000000000000000000000000000000000000000000000000000000000\
339            0200000000000000000000000000000000000000000000000000000000000000",
340        )
341        .unwrap();
342
343        let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999);
344        assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas))));
345
346        // zero multiplication test
347        let input = hex::decode(
348            "\
349            0000000000000000000000000000000000000000000000000000000000000000\
350            0000000000000000000000000000000000000000000000000000000000000000\
351            0200000000000000000000000000000000000000000000000000000000000000",
352        )
353        .unwrap();
354        let expected = hex::decode(
355            "\
356            0000000000000000000000000000000000000000000000000000000000000000\
357            0000000000000000000000000000000000000000000000000000000000000000",
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        // no input test
365        let input = [0u8; 0];
366        let expected = hex::decode(
367            "\
368            0000000000000000000000000000000000000000000000000000000000000000\
369            0000000000000000000000000000000000000000000000000000000000000000",
370        )
371        .unwrap();
372
373        let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap();
374        assert_eq!(outcome.bytes, expected);
375
376        // point not on curve fail
377        let input = hex::decode(
378            "\
379            1111111111111111111111111111111111111111111111111111111111111111\
380            1111111111111111111111111111111111111111111111111111111111111111\
381            0f00000000000000000000000000000000000000000000000000000000000000",
382        )
383        .unwrap();
384
385        let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000);
386        assert!(matches!(
387            res,
388            Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate))
389        ));
390    }
391
392    #[test]
393    fn test_alt_bn128_pair() {
394        let input = hex::decode(
395            "\
396            1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\
397            3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\
398            209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\
399            04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\
400            2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\
401            120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\
402            111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\
403            2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\
404            198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\
405            1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\
406            090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\
407            12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
408        )
409        .unwrap();
410        let expected =
411            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
412                .unwrap();
413
414        let outcome = run_pair(
415            &input,
416            BYZANTIUM_PAIR_PER_POINT,
417            BYZANTIUM_PAIR_BASE,
418            260_000,
419        )
420        .unwrap();
421        assert_eq!(outcome.bytes, expected);
422
423        // out of gas test
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
441        let res = run_pair(
442            &input,
443            BYZANTIUM_PAIR_PER_POINT,
444            BYZANTIUM_PAIR_BASE,
445            259_999,
446        );
447        assert!(matches!(res, Err(PrecompileErrors::Error(Error::OutOfGas))));
448
449        // no input test
450        let input = [0u8; 0];
451        let expected =
452            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
453                .unwrap();
454
455        let outcome = run_pair(
456            &input,
457            BYZANTIUM_PAIR_PER_POINT,
458            BYZANTIUM_PAIR_BASE,
459            260_000,
460        )
461        .unwrap();
462        assert_eq!(outcome.bytes, expected);
463
464        // point not on curve fail
465        let input = hex::decode(
466            "\
467            1111111111111111111111111111111111111111111111111111111111111111\
468            1111111111111111111111111111111111111111111111111111111111111111\
469            1111111111111111111111111111111111111111111111111111111111111111\
470            1111111111111111111111111111111111111111111111111111111111111111\
471            1111111111111111111111111111111111111111111111111111111111111111\
472            1111111111111111111111111111111111111111111111111111111111111111",
473        )
474        .unwrap();
475
476        let res = run_pair(
477            &input,
478            BYZANTIUM_PAIR_PER_POINT,
479            BYZANTIUM_PAIR_BASE,
480            260_000,
481        );
482        assert!(matches!(
483            res,
484            Err(PrecompileErrors::Error(Error::Bn128AffineGFailedToCreate))
485        ));
486
487        // invalid input length
488        let input = hex::decode(
489            "\
490            1111111111111111111111111111111111111111111111111111111111111111\
491            1111111111111111111111111111111111111111111111111111111111111111\
492            111111111111111111111111111111\
493        ",
494        )
495        .unwrap();
496
497        let res = run_pair(
498            &input,
499            BYZANTIUM_PAIR_PER_POINT,
500            BYZANTIUM_PAIR_BASE,
501            260_000,
502        );
503        assert!(matches!(
504            res,
505            Err(PrecompileErrors::Error(Error::Bn128PairLength))
506        ));
507    }
508}