pallet_evm_precompile_bn128/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(unused_crate_dependencies)]
20
21extern crate alloc;
22
23use alloc::vec::Vec;
24use fp_evm::{
25	ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
26	PrecompileResult,
27};
28use sp_core::U256;
29
30/// Copy bytes from input to target.
31fn read_input(source: &[u8], target: &mut [u8], offset: usize) {
32	// Out of bounds, nothing to copy.
33	if source.len() <= offset {
34		return;
35	}
36
37	// Find len to copy up to target len, but not out of bounds.
38	let len = core::cmp::min(target.len(), source.len() - offset);
39	target[..len].copy_from_slice(&source[offset..][..len]);
40}
41
42fn read_fr(input: &[u8], start_inx: usize) -> Result<bn::Fr, PrecompileFailure> {
43	let mut buf = [0u8; 32];
44	read_input(input, &mut buf, start_inx);
45
46	bn::Fr::from_slice(&buf).map_err(|_| PrecompileFailure::Error {
47		exit_status: ExitError::Other("Invalid field element".into()),
48	})
49}
50
51fn read_point(input: &[u8], start_inx: usize) -> Result<bn::G1, PrecompileFailure> {
52	use bn::{AffineG1, Fq, Group, G1};
53
54	let mut px_buf = [0u8; 32];
55	let mut py_buf = [0u8; 32];
56	read_input(input, &mut px_buf, start_inx);
57	read_input(input, &mut py_buf, start_inx + 32);
58
59	let px = Fq::from_slice(&px_buf).map_err(|_| PrecompileFailure::Error {
60		exit_status: ExitError::Other("Invalid point x coordinate".into()),
61	})?;
62
63	let py = Fq::from_slice(&py_buf).map_err(|_| PrecompileFailure::Error {
64		exit_status: ExitError::Other("Invalid point y coordinate".into()),
65	})?;
66
67	Ok(if px == Fq::zero() && py == Fq::zero() {
68		G1::zero()
69	} else {
70		AffineG1::new(px, py)
71			.map_err(|_| PrecompileFailure::Error {
72				exit_status: ExitError::Other("Invalid curve point".into()),
73			})?
74			.into()
75	})
76}
77
78/// The Bn128Add builtin
79pub struct Bn128Add;
80
81impl Bn128Add {
82	const GAS_COST: u64 = 150; // https://eips.ethereum.org/EIPS/eip-1108
83}
84
85impl Precompile for Bn128Add {
86	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
87		use bn::AffineG1;
88
89		handle.record_cost(Bn128Add::GAS_COST)?;
90
91		let input = handle.input();
92
93		let p1 = read_point(input, 0)?;
94		let p2 = read_point(input, 64)?;
95
96		let mut buf = [0u8; 64];
97		if let Some(sum) = AffineG1::from_jacobian(p1 + p2) {
98			// point not at infinity
99			sum.x()
100				.to_big_endian(&mut buf[0..32])
101				.map_err(|_| PrecompileFailure::Error {
102					exit_status: ExitError::Other(
103						"Cannot fail since 0..32 is 32-byte length".into(),
104					),
105				})?;
106			sum.y()
107				.to_big_endian(&mut buf[32..64])
108				.map_err(|_| PrecompileFailure::Error {
109					exit_status: ExitError::Other(
110						"Cannot fail since 32..64 is 32-byte length".into(),
111					),
112				})?;
113		}
114
115		Ok(PrecompileOutput {
116			exit_status: ExitSucceed::Returned,
117			output: buf.to_vec(),
118		})
119	}
120}
121
122/// The Bn128Mul builtin
123pub struct Bn128Mul;
124
125impl Bn128Mul {
126	const GAS_COST: u64 = 6_000; // https://eips.ethereum.org/EIPS/eip-1108
127}
128
129impl Precompile for Bn128Mul {
130	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
131		use bn::AffineG1;
132
133		handle.record_cost(Bn128Mul::GAS_COST)?;
134
135		let input = handle.input();
136
137		let p = read_point(input, 0)?;
138		let fr = read_fr(input, 64)?;
139
140		let mut buf = [0u8; 64];
141		if let Some(sum) = AffineG1::from_jacobian(p * fr) {
142			// point not at infinity
143			sum.x()
144				.to_big_endian(&mut buf[0..32])
145				.map_err(|_| PrecompileFailure::Error {
146					exit_status: ExitError::Other(
147						"Cannot fail since 0..32 is 32-byte length".into(),
148					),
149				})?;
150			sum.y()
151				.to_big_endian(&mut buf[32..64])
152				.map_err(|_| PrecompileFailure::Error {
153					exit_status: ExitError::Other(
154						"Cannot fail since 32..64 is 32-byte length".into(),
155					),
156				})?;
157		}
158
159		Ok(PrecompileOutput {
160			exit_status: ExitSucceed::Returned,
161			output: buf.to_vec(),
162		})
163	}
164}
165
166/// The Bn128Pairing builtin
167pub struct Bn128Pairing;
168
169impl Bn128Pairing {
170	// https://eips.ethereum.org/EIPS/eip-1108
171	const BASE_GAS_COST: u64 = 45_000;
172	const GAS_COST_PER_PAIRING: u64 = 34_000;
173}
174
175impl Precompile for Bn128Pairing {
176	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
177		use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2};
178
179		let ret_val = if handle.input().is_empty() {
180			handle.record_cost(Bn128Pairing::BASE_GAS_COST)?;
181			U256::one()
182		} else {
183			if handle.input().len() % 192 > 0 {
184				return Err(PrecompileFailure::Error {
185					exit_status: ExitError::Other("bad elliptic curve pairing size".into()),
186				});
187			}
188
189			// (a, b_a, b_b - each 64-byte affine coordinates)
190			let elements = handle.input().len() / 192;
191
192			let gas_cost: u64 = Bn128Pairing::BASE_GAS_COST
193				+ (elements as u64 * Bn128Pairing::GAS_COST_PER_PAIRING);
194
195			handle.record_cost(gas_cost)?;
196
197			let input = handle.input();
198
199			let mut vals = Vec::new();
200			for idx in 0..elements {
201				let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]).map_err(|_| {
202					PrecompileFailure::Error {
203						exit_status: ExitError::Other("Invalid a argument x coordinate".into()),
204					}
205				})?;
206
207				let a_y = Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]).map_err(|_| {
208					PrecompileFailure::Error {
209						exit_status: ExitError::Other("Invalid a argument y coordinate".into()),
210					}
211				})?;
212
213				let b_a_y =
214					Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]).map_err(|_| {
215						PrecompileFailure::Error {
216							exit_status: ExitError::Other(
217								"Invalid b argument imaginary coeff x coordinate".into(),
218							),
219						}
220					})?;
221
222				let b_a_x =
223					Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]).map_err(|_| {
224						PrecompileFailure::Error {
225							exit_status: ExitError::Other(
226								"Invalid b argument imaginary coeff y coordinate".into(),
227							),
228						}
229					})?;
230
231				let b_b_y =
232					Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]).map_err(|_| {
233						PrecompileFailure::Error {
234							exit_status: ExitError::Other(
235								"Invalid b argument real coeff x coordinate".into(),
236							),
237						}
238					})?;
239
240				let b_b_x =
241					Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]).map_err(|_| {
242						PrecompileFailure::Error {
243							exit_status: ExitError::Other(
244								"Invalid b argument real coeff y coordinate".into(),
245							),
246						}
247					})?;
248
249				let b_a = Fq2::new(b_a_x, b_a_y);
250				let b_b = Fq2::new(b_b_x, b_b_y);
251				let b = if b_a.is_zero() && b_b.is_zero() {
252					G2::zero()
253				} else {
254					G2::from(
255						AffineG2::new(b_a, b_b).map_err(|_| PrecompileFailure::Error {
256							exit_status: ExitError::Other(
257								"Invalid b argument - not on curve".into(),
258							),
259						})?,
260					)
261				};
262				let a = if a_x.is_zero() && a_y.is_zero() {
263					G1::zero()
264				} else {
265					G1::from(
266						AffineG1::new(a_x, a_y).map_err(|_| PrecompileFailure::Error {
267							exit_status: ExitError::Other(
268								"Invalid a argument - not on curve".into(),
269							),
270						})?,
271					)
272				};
273				vals.push((a, b));
274			}
275
276			let mul = pairing_batch(&vals);
277
278			if mul == Gt::one() {
279				U256::one()
280			} else {
281				U256::zero()
282			}
283		};
284
285		Ok(PrecompileOutput {
286			exit_status: ExitSucceed::Returned,
287			output: ret_val.to_big_endian().to_vec(),
288		})
289	}
290}
291
292#[cfg(test)]
293mod tests {
294	use super::*;
295	use pallet_evm_test_vector_support::test_precompile_test_vectors;
296
297	#[test]
298	fn process_consensus_tests_for_add() -> Result<(), String> {
299		test_precompile_test_vectors::<Bn128Add>("../testdata/common_bnadd.json")?;
300		Ok(())
301	}
302
303	#[test]
304	fn process_consensus_tests_for_mul() -> Result<(), String> {
305		test_precompile_test_vectors::<Bn128Mul>("../testdata/common_bnmul.json")?;
306		Ok(())
307	}
308
309	#[test]
310	fn process_consensus_tests_for_pair() -> Result<(), String> {
311		test_precompile_test_vectors::<Bn128Pairing>("../testdata/common_bnpair.json")?;
312		Ok(())
313	}
314}