1#![cfg_attr(not(feature = "std"), no_std)]
19
20use 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
26use fp_evm::{
28 ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
29 PrecompileResult,
30};
31
32const 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
43fn 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
64fn 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
74fn 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
84fn 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
90fn 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
97fn 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 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
130fn 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 let px = extract_fq(px_buf)?;
139 let py = extract_fq(py_buf)?;
141
142 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
157fn 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 let px = extract_fq(px_buf)?;
166 let py = extract_fq(py_buf)?;
168
169 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
184pub struct Bw6761G1Add;
186
187impl Bw6761G1Add {
188 const GAS_COST: u64 = 180;
189}
190
191impl Precompile for Bw6761G1Add {
192 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 let p0 = decode_g1(input, 0)?;
207 let p1 = decode_g1(input, 192)?;
209 let r = p0 + p1;
211 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
221pub struct Bw6761G1Mul;
223
224impl Bw6761G1Mul {
225 const GAS_COST: u64 = 64_000;
226}
227
228impl Precompile for Bw6761G1Mul {
229 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 let p = decode_g1(input, 0)?;
244 let e = decode_fr(input, 192);
246 let r = p.mul(e);
248 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
258pub struct Bw6761G1MultiExp;
260
261impl Bw6761G1MultiExp {
262 const MULTIPLIER: u64 = 1_000;
263
264 fn calculate_gas_cost(input_len: usize) -> u64 {
266 let k = input_len / 256;
268 if k == 0 {
269 return 0;
270 }
271 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 k as u64 * Bw6761G1Mul::GAS_COST * discount as u64 / Bw6761G1MultiExp::MULTIPLIER
280 }
281}
282
283impl Precompile for Bw6761G1MultiExp {
284 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 for idx in 0..k {
304 let offset = idx * 256;
305 let p = decode_g1(input, offset)?;
307 let scalar = decode_fr(input, offset + 192);
309 points.push(p.into_affine());
310 scalars.push(scalar);
311 }
312
313 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 let output = encode_g1(r.into_affine());
322 Ok(PrecompileOutput {
323 exit_status: ExitSucceed::Returned,
324 output: output.to_vec(),
325 })
326 }
327}
328
329pub struct Bw6761G2Add;
331
332impl Bw6761G2Add {
333 const GAS_COST: u64 = 180;
334}
335
336impl Precompile for Bw6761G2Add {
337 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 let p0 = decode_g2(input, 0)?;
352 let p1 = decode_g2(input, 192)?;
354 let r = p0 + p1;
356 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
366pub struct Bw6761G2Mul;
368
369impl Bw6761G2Mul {
370 const GAS_COST: u64 = 64_000;
371}
372
373impl Precompile for Bw6761G2Mul {
374 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 let p = decode_g2(input, 0)?;
389 let e = decode_fr(input, 192);
391 let r = p.mul(e);
393 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
403pub struct Bw6761G2MultiExp;
405
406impl Bw6761G2MultiExp {
407 const MULTIPLIER: u64 = 1_000;
408
409 fn calculate_gas_cost(input_len: usize) -> u64 {
411 let k = input_len / 256;
413 if k == 0 {
414 return 0;
415 }
416 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 k as u64 * Bw6761G2Mul::GAS_COST * discount as u64 / Bw6761G2MultiExp::MULTIPLIER
425 }
426}
427
428impl Precompile for Bw6761G2MultiExp {
429 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 for idx in 0..k {
449 let offset = idx * 256;
450 let p = decode_g2(input, offset)?;
452 let scalar = decode_fr(input, offset + 192);
454 points.push(p.into_affine());
455 scalars.push(scalar);
456 }
457
458 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 let output = encode_g2(r.into_affine());
467 Ok(PrecompileOutput {
468 exit_status: ExitSucceed::Returned,
469 output: output.to_vec(),
470 })
471 }
472}
473
474pub 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 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 for idx in 0..k {
507 let offset = idx * 384;
508 let g1 = decode_g1(input, offset)?;
510 let g2 = decode_g2(input, offset + 192)?;
512
513 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 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;