pallet_evm_precompile_bw6761/
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
20// Arkworks
21use ark_bw6_761::{Fq, Fr, G1Affine, G1Projective, G2Affine, G2Projective, BW6_761};
22use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, VariableBaseMSM};
23use ark_ff::{BigInteger768, PrimeField, Zero};
24use ark_std::{ops::Mul, vec::Vec};
25
26// Frontier
27use fp_evm::{
28	ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
29	PrecompileResult,
30};
31
32/// Gas discount table for BW6-761 G1 and G2 multi exponentiation operations.
33const BW6761_MULTIEXP_DISCOUNT_TABLE: [u16; 128] = [
34	1266, 733, 561, 474, 422, 387, 362, 344, 329, 318, 308, 300, 296, 289, 283, 279, 275, 272, 269,
35	266, 265, 260, 259, 256, 255, 254, 252, 251, 250, 249, 249, 220, 228, 225, 223, 219, 216, 214,
36	212, 209, 209, 205, 203, 202, 200, 198, 196, 199, 195, 192, 192, 191, 190, 187, 186, 185, 184,
37	184, 181, 181, 181, 180, 178, 179, 176, 177, 176, 175, 174, 173, 171, 171, 170, 170, 169, 168,
38	168, 167, 167, 166, 165, 167, 166, 166, 165, 165, 164, 164, 163, 163, 162, 162, 160, 163, 159,
39	162, 159, 160, 159, 159, 158, 158, 158, 158, 157, 157, 156, 155, 155, 156, 155, 155, 154, 155,
40	154, 153, 153, 153, 152, 152, 152, 152, 151, 151, 151, 151, 151, 150,
41];
42
43/// Encode Fq as `96` bytes by performing Big-Endian encoding of the corresponding (unsigned) integer.
44fn encode_fq(field: Fq) -> [u8; 96] {
45	let mut result = [0u8; 96];
46	let rep = field.into_bigint().0;
47
48	result[0..8].copy_from_slice(&rep[11].to_be_bytes());
49	result[8..16].copy_from_slice(&rep[10].to_be_bytes());
50	result[16..24].copy_from_slice(&rep[9].to_be_bytes());
51	result[24..32].copy_from_slice(&rep[8].to_be_bytes());
52	result[32..40].copy_from_slice(&rep[7].to_be_bytes());
53	result[40..48].copy_from_slice(&rep[6].to_be_bytes());
54	result[48..56].copy_from_slice(&rep[5].to_be_bytes());
55	result[56..64].copy_from_slice(&rep[4].to_be_bytes());
56	result[64..72].copy_from_slice(&rep[3].to_be_bytes());
57	result[72..80].copy_from_slice(&rep[2].to_be_bytes());
58	result[80..88].copy_from_slice(&rep[1].to_be_bytes());
59	result[88..96].copy_from_slice(&rep[0].to_be_bytes());
60
61	result
62}
63
64/// Encode point G1 as byte concatenation of encodings of the `x` and `y` affine coordinates.
65fn encode_g1(g1: G1Affine) -> [u8; 192] {
66	let mut result = [0u8; 192];
67	if !g1.is_zero() {
68		result[0..96].copy_from_slice(&encode_fq(g1.x));
69		result[96..192].copy_from_slice(&encode_fq(g1.y));
70	}
71	result
72}
73
74/// Encode point G2 as byte concatenation of encodings of the `x` and `y` affine coordinates.
75fn encode_g2(g2: G2Affine) -> [u8; 192] {
76	let mut result = [0u8; 192];
77	if !g2.is_zero() {
78		result[0..96].copy_from_slice(&encode_fq(g2.x));
79		result[96..192].copy_from_slice(&encode_fq(g2.y));
80	}
81	result
82}
83
84/// Copy bytes from source.offset to target.
85fn read_input(source: &[u8], target: &mut [u8], offset: usize) {
86	let len = target.len();
87	target[..len].copy_from_slice(&source[offset..][..len]);
88}
89
90/// Decode Fr expects 64 byte input, returns fr in scalar field.
91fn decode_fr(input: &[u8], offset: usize) -> Fr {
92	let mut bytes = [0u8; 64];
93	read_input(input, &mut bytes, offset);
94	Fr::from_be_bytes_mod_order(&bytes)
95}
96
97/// Decode Fq expects 96 byte input,
98/// returns Fq in base field.
99fn decode_fq(bytes: [u8; 96]) -> Option<Fq> {
100	let mut tmp = BigInteger768::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
101	// Note: The following unwraps are if the compiler cannot convert
102	// the byte slice into [u8;8], we know this is infallible since we
103	// are providing the indices at compile time and bytes has a fixed size
104	tmp.0[11] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap());
105	tmp.0[10] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap());
106	tmp.0[9] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
107	tmp.0[8] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
108	tmp.0[7] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap());
109	tmp.0[6] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap());
110	tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap());
111	tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap());
112	tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[64..72]).unwrap());
113	tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[72..80]).unwrap());
114	tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[80..88]).unwrap());
115	tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[88..96]).unwrap());
116
117	Fq::from_bigint(tmp)
118}
119
120fn extract_fq(bytes: [u8; 96]) -> Result<Fq, PrecompileFailure> {
121	let fq = decode_fq(bytes);
122	match fq {
123		None => Err(PrecompileFailure::Error {
124			exit_status: ExitError::Other("invalid Fq".into()),
125		}),
126		Some(c) => Ok(c),
127	}
128}
129
130/// Decode G1 given encoded (x, y) coordinates in 192 bytes returns a valid G1 Point.
131fn decode_g1(input: &[u8], offset: usize) -> Result<G1Projective, PrecompileFailure> {
132	let mut px_buf = [0u8; 96];
133	let mut py_buf = [0u8; 96];
134	read_input(input, &mut px_buf, offset);
135	read_input(input, &mut py_buf, offset + 96);
136
137	// Decode x
138	let px = extract_fq(px_buf)?;
139	// Decode y
140	let py = extract_fq(py_buf)?;
141
142	// Check if given input points to infinity
143	if px.is_zero() && py.is_zero() {
144		Ok(G1Projective::zero())
145	} else {
146		let g1 = G1Affine::new_unchecked(px, py);
147		if !g1.is_on_curve() {
148			Err(PrecompileFailure::Error {
149				exit_status: ExitError::Other("point is not on curve".into()),
150			})
151		} else {
152			Ok(g1.into())
153		}
154	}
155}
156
157// Decode G2 given encoded (x, y) coordinates in 192 bytes returns a valid G2 Point.
158fn decode_g2(input: &[u8], offset: usize) -> Result<G2Projective, PrecompileFailure> {
159	let mut px_buf = [0u8; 96];
160	let mut py_buf = [0u8; 96];
161	read_input(input, &mut px_buf, offset);
162	read_input(input, &mut py_buf, offset + 96);
163
164	// Decode x
165	let px = extract_fq(px_buf)?;
166	// Decode y
167	let py = extract_fq(py_buf)?;
168
169	// Check if given input points to infinity
170	if px.is_zero() && py.is_zero() {
171		Ok(G2Projective::zero())
172	} else {
173		let g2 = G2Affine::new_unchecked(px, py);
174		if !g2.is_on_curve() {
175			Err(PrecompileFailure::Error {
176				exit_status: ExitError::Other("point is not on curve".into()),
177			})
178		} else {
179			Ok(g2.into())
180		}
181	}
182}
183
184/// Bw6761G1Add implements EIP-3026 G1Add precompile.
185pub struct Bw6761G1Add;
186
187impl Bw6761G1Add {
188	const GAS_COST: u64 = 180;
189}
190
191impl Precompile for Bw6761G1Add {
192	/// Implements EIP-3026 G1Add precompile.
193	/// > G1 addition call expects `384` bytes as an input that is interpreted as byte concatenation of two G1 points (`192` bytes each).
194	/// > Output is an encoding of addition operation result - single G1 point (`192` bytes).
195	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
196		handle.record_cost(Bw6761G1Add::GAS_COST)?;
197
198		let input = handle.input();
199		if input.len() != 384 {
200			return Err(PrecompileFailure::Error {
201				exit_status: ExitError::Other("invalid input length".into()),
202			});
203		}
204
205		// Decode G1 point p_0
206		let p0 = decode_g1(input, 0)?;
207		// Decode G1 point p_1
208		let p1 = decode_g1(input, 192)?;
209		// Compute r = p_0 + p_1
210		let r = p0 + p1;
211		// Encode the G1 point into 192 bytes output
212		let output = encode_g1(r.into_affine());
213
214		Ok(PrecompileOutput {
215			exit_status: ExitSucceed::Returned,
216			output: output.to_vec(),
217		})
218	}
219}
220
221/// Bw6761G1Mul implements EIP-3026 G1Mul precompile.
222pub struct Bw6761G1Mul;
223
224impl Bw6761G1Mul {
225	const GAS_COST: u64 = 64_000;
226}
227
228impl Precompile for Bw6761G1Mul {
229	/// Implements EIP-3026 G1Mul precompile.
230	/// > G1 multiplication call expects `256` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`192` bytes) and encoding of a scalar value (`64` bytes).
231	/// > Output is an encoding of multiplication operation result - single G1 point (`192` bytes).
232	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
233		handle.record_cost(Bw6761G1Mul::GAS_COST)?;
234
235		let input = handle.input();
236		if input.len() != 256 {
237			return Err(PrecompileFailure::Error {
238				exit_status: ExitError::Other("invalid input length".into()),
239			});
240		}
241
242		// Decode G1 point
243		let p = decode_g1(input, 0)?;
244		// Decode scalar value
245		let e = decode_fr(input, 192);
246		// Compute r = e * p
247		let r = p.mul(e);
248		// Encode the G1 point into 192 bytes output
249		let output = encode_g1(r.into_affine());
250
251		Ok(PrecompileOutput {
252			exit_status: ExitSucceed::Returned,
253			output: output.to_vec(),
254		})
255	}
256}
257
258/// Bw6761G1MultiExp implements EIP-3026 G1MultiExp precompile.
259pub struct Bw6761G1MultiExp;
260
261impl Bw6761G1MultiExp {
262	const MULTIPLIER: u64 = 1_000;
263
264	/// Returns the gas required to execute the pre-compiled contract.
265	fn calculate_gas_cost(input_len: usize) -> u64 {
266		// Calculate G1 point, scalar value pair length
267		let k = input_len / 256;
268		if k == 0 {
269			return 0;
270		}
271		// Lookup discount value for G1 point, scalar value pair length
272		let d_len = BW6761_MULTIEXP_DISCOUNT_TABLE.len();
273		let discount = if k <= d_len {
274			BW6761_MULTIEXP_DISCOUNT_TABLE[k - 1]
275		} else {
276			BW6761_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
277		};
278		// Calculate gas and return the result
279		k as u64 * Bw6761G1Mul::GAS_COST * discount as u64 / Bw6761G1MultiExp::MULTIPLIER
280	}
281}
282
283impl Precompile for Bw6761G1MultiExp {
284	/// Implements EIP-3026 G1MultiExp precompile.
285	/// G1 multiplication call expects `256*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`192` bytes) and encoding of a scalar value (`64` bytes).
286	/// Output is an encoding of multiexponentiation operation result - single G1 point (`192` bytes).
287	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
288		let gas_cost = Bw6761G1MultiExp::calculate_gas_cost(handle.input().len());
289		handle.record_cost(gas_cost)?;
290
291		let k = handle.input().len() / 256;
292		if handle.input().is_empty() || handle.input().len() % 256 != 0 {
293			return Err(PrecompileFailure::Error {
294				exit_status: ExitError::Other("invalid input length".into()),
295			});
296		}
297
298		let input = handle.input();
299
300		let mut points = Vec::new();
301		let mut scalars = Vec::new();
302		// Decode point scalar pairs
303		for idx in 0..k {
304			let offset = idx * 256;
305			// Decode G1 point
306			let p = decode_g1(input, offset)?;
307			// Decode scalar value
308			let scalar = decode_fr(input, offset + 192);
309			points.push(p.into_affine());
310			scalars.push(scalar);
311		}
312
313		// Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
314		let r = G1Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
315			PrecompileFailure::Error {
316				exit_status: ExitError::Other("MSM failed".into()),
317			}
318		})?;
319
320		// Encode the G1 point into 128 bytes output
321		let output = encode_g1(r.into_affine());
322		Ok(PrecompileOutput {
323			exit_status: ExitSucceed::Returned,
324			output: output.to_vec(),
325		})
326	}
327}
328
329/// Bw6761G2Add implements EIP-3026 G2Add precompile.
330pub struct Bw6761G2Add;
331
332impl Bw6761G2Add {
333	const GAS_COST: u64 = 180;
334}
335
336impl Precompile for Bw6761G2Add {
337	/// Implements EIP-3026 G2Add precompile.
338	/// > G2 addition call expects `384` bytes as an input that is interpreted as byte concatenation of two G2 points (`192` bytes each).
339	/// > Output is an encoding of addition operation result - single G2 point (`192` bytes).
340	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
341		handle.record_cost(Bw6761G2Add::GAS_COST)?;
342
343		let input = handle.input();
344		if input.len() != 384 {
345			return Err(PrecompileFailure::Error {
346				exit_status: ExitError::Other("invalid input length".into()),
347			});
348		}
349
350		// Decode G2 point p_0
351		let p0 = decode_g2(input, 0)?;
352		// Decode G2 point p_1
353		let p1 = decode_g2(input, 192)?;
354		// Compute r = p_0 + p_1
355		let r = p0 + p1;
356		// Encode the G2 point into 256 bytes output
357		let output = encode_g2(r.into_affine());
358
359		Ok(PrecompileOutput {
360			exit_status: ExitSucceed::Returned,
361			output: output.to_vec(),
362		})
363	}
364}
365
366/// Bw6761G2Mul implements EIP-3026 G2Mul precompile.
367pub struct Bw6761G2Mul;
368
369impl Bw6761G2Mul {
370	const GAS_COST: u64 = 64_000;
371}
372
373impl Precompile for Bw6761G2Mul {
374	/// Implements EIP-3026 G2MUL precompile logic.
375	/// > G2 multiplication call expects `256` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`192` bytes) and encoding of a scalar value (`64` bytes).
376	/// > Output is an encoding of multiplication operation result - single G2 point (`192` bytes).
377	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
378		handle.record_cost(Bw6761G2Mul::GAS_COST)?;
379
380		let input = handle.input();
381		if input.len() != 256 {
382			return Err(PrecompileFailure::Error {
383				exit_status: ExitError::Other("invalid input length".into()),
384			});
385		}
386
387		// Decode G2 point
388		let p = decode_g2(input, 0)?;
389		// Decode scalar value
390		let e = decode_fr(input, 192);
391		// Compute r = e * p
392		let r = p.mul(e);
393		// Encode the G2 point into 256 bytes output
394		let output = encode_g2(r.into_affine());
395
396		Ok(PrecompileOutput {
397			exit_status: ExitSucceed::Returned,
398			output: output.to_vec(),
399		})
400	}
401}
402
403// Bw6761G2MultiExp implements EIP-3026 G2MultiExp precompile.
404pub struct Bw6761G2MultiExp;
405
406impl Bw6761G2MultiExp {
407	const MULTIPLIER: u64 = 1_000;
408
409	/// Returns the gas required to execute the pre-compiled contract.
410	fn calculate_gas_cost(input_len: usize) -> u64 {
411		// Calculate G2 point, scalar value pair length
412		let k = input_len / 256;
413		if k == 0 {
414			return 0;
415		}
416		// Lookup discount value for G2 point, scalar value pair length
417		let d_len = BW6761_MULTIEXP_DISCOUNT_TABLE.len();
418		let discount = if k <= d_len {
419			BW6761_MULTIEXP_DISCOUNT_TABLE[k - 1]
420		} else {
421			BW6761_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
422		};
423		// Calculate gas and return the result
424		k as u64 * Bw6761G2Mul::GAS_COST * discount as u64 / Bw6761G2MultiExp::MULTIPLIER
425	}
426}
427
428impl Precompile for Bw6761G2MultiExp {
429	/// Implements EIP-3026 G2MultiExp precompile logic
430	/// > G2 multiplication call expects `256*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`64` bytes).
431	/// > Output is an encoding of multiexponentiation operation result - single G2 point (`192` bytes).
432	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
433		let gas_cost = Bw6761G2MultiExp::calculate_gas_cost(handle.input().len());
434		handle.record_cost(gas_cost)?;
435
436		let k = handle.input().len() / 256;
437		if handle.input().is_empty() || handle.input().len() % 256 != 0 {
438			return Err(PrecompileFailure::Error {
439				exit_status: ExitError::Other("invalid input length".into()),
440			});
441		}
442
443		let input = handle.input();
444
445		let mut points = Vec::new();
446		let mut scalars = Vec::new();
447		// Decode point scalar pairs
448		for idx in 0..k {
449			let offset = idx * 256;
450			// Decode G2 point
451			let p = decode_g2(input, offset)?;
452			// Decode scalar value
453			let scalar = decode_fr(input, offset + 192);
454			points.push(p.into_affine());
455			scalars.push(scalar);
456		}
457
458		// Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
459		let r = G2Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
460			PrecompileFailure::Error {
461				exit_status: ExitError::Other("MSM failed".into()),
462			}
463		})?;
464
465		// Encode the G2 point to 256 bytes output
466		let output = encode_g2(r.into_affine());
467		Ok(PrecompileOutput {
468			exit_status: ExitSucceed::Returned,
469			output: output.to_vec(),
470		})
471	}
472}
473
474/// Bw6761Pairing implements EIP-3026 Pairing precompile.
475pub struct Bw6761Pairing;
476
477impl Bw6761Pairing {
478	const BASE_GAS: u64 = 120_000;
479	const PER_PAIR_GAS: u64 = 320_000;
480}
481
482impl Precompile for Bw6761Pairing {
483	/// Implements EIP-3026 Pairing precompile logic.
484	/// > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure:
485	/// > - `192` bytes of G1 point encoding
486	/// > - `192` bytes of G2 point encoding
487	/// >   Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise
488	/// >   (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively).
489	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
490		if handle.input().is_empty() || handle.input().len() % 384 != 0 {
491			return Err(PrecompileFailure::Error {
492				exit_status: ExitError::Other("invalid input length".into()),
493			});
494		}
495
496		let k = handle.input().len() / 384;
497		let gas_cost: u64 = Bw6761Pairing::BASE_GAS + (k as u64 * Bw6761Pairing::PER_PAIR_GAS);
498
499		handle.record_cost(gas_cost)?;
500
501		let input = handle.input();
502
503		let mut a = Vec::new();
504		let mut b = Vec::new();
505		// Decode G1 G2 pairs
506		for idx in 0..k {
507			let offset = idx * 384;
508			// Decode G1 point
509			let g1 = decode_g1(input, offset)?;
510			// Decode G2 point
511			let g2 = decode_g2(input, offset + 192)?;
512
513			// 'point is on curve' check already done,
514			// Here we need to apply subgroup checks.
515			if !g1.into_affine().is_in_correct_subgroup_assuming_on_curve() {
516				return Err(PrecompileFailure::Error {
517					exit_status: ExitError::Other("g1 point is not on correct subgroup".into()),
518				});
519			}
520			if !g2.into_affine().is_in_correct_subgroup_assuming_on_curve() {
521				return Err(PrecompileFailure::Error {
522					exit_status: ExitError::Other("g2 point is not on correct subgroup".into()),
523				});
524			}
525
526			a.push(g1);
527			b.push(g2);
528		}
529
530		let mut output = [0u8; 32];
531		// Compute pairing and set the output
532		if BW6_761::multi_pairing(a, b).is_zero() {
533			output[31] = 1;
534		}
535
536		Ok(PrecompileOutput {
537			exit_status: ExitSucceed::Returned,
538			output: output.to_vec(),
539		})
540	}
541}
542
543#[cfg(test)]
544mod tests;