revm_precompile/
bn128.rs

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