1#![cfg_attr(not(feature = "std"), no_std)]
19
20use ark_bls12_377::{
22 g1::Config as G1Config, g2::Config as G2Config, Bls12_377, Fq, Fq2, Fr, G1Affine, G1Projective,
23 G2Affine, G2Projective,
24};
25use ark_ec::{
26 hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve, HashToCurveError},
27 pairing::Pairing,
28 AffineRepr, CurveGroup, VariableBaseMSM,
29};
30use ark_ff::{BigInteger384, PrimeField, Zero};
31use ark_std::{ops::Mul, vec::Vec};
32
33use fp_evm::{
35 ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
36 PrecompileResult,
37};
38
39const BLS12377_MULTIEXP_DISCOUNT_TABLE: [u16; 128] = [
41 1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322,
42 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260,
43 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232,
44 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213,
45 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199,
46 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185,
47 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174,
48];
49
50fn encode_fq(field: Fq) -> [u8; 64] {
52 let mut result = [0u8; 64];
53 let rep = field.into_bigint().0;
54
55 result[16..24].copy_from_slice(&rep[5].to_be_bytes());
56 result[24..32].copy_from_slice(&rep[4].to_be_bytes());
57 result[32..40].copy_from_slice(&rep[3].to_be_bytes());
58 result[40..48].copy_from_slice(&rep[2].to_be_bytes());
59 result[48..56].copy_from_slice(&rep[1].to_be_bytes());
60 result[56..64].copy_from_slice(&rep[0].to_be_bytes());
61
62 result
63}
64
65fn encode_g1(g1: G1Affine) -> [u8; 128] {
67 let mut result = [0u8; 128];
68 if !g1.is_zero() {
69 result[0..64].copy_from_slice(&encode_fq(g1.x));
70 result[64..128].copy_from_slice(&encode_fq(g1.y));
71 }
72 result
73}
74
75fn encode_g2(g2: G2Affine) -> [u8; 256] {
77 let mut result = [0u8; 256];
78 if !g2.is_zero() {
79 result[0..64].copy_from_slice(&encode_fq(g2.x.c0));
80 result[64..128].copy_from_slice(&encode_fq(g2.x.c1));
81 result[128..192].copy_from_slice(&encode_fq(g2.y.c0));
82 result[192..256].copy_from_slice(&encode_fq(g2.y.c1));
83 }
84 result
85}
86
87fn read_input(source: &[u8], target: &mut [u8], offset: usize) {
89 let len = target.len();
90 target[..len].copy_from_slice(&source[offset..][..len]);
91}
92
93fn decode_fr(input: &[u8], offset: usize) -> Fr {
95 let mut bytes = [0u8; 32];
96 read_input(input, &mut bytes, offset);
97 Fr::from_be_bytes_mod_order(&bytes)
98}
99
100fn decode_fq(bytes: [u8; 64]) -> Option<Fq> {
103 for b in bytes.iter().take(16) {
105 if b.ne(&0u8) {
106 return None;
107 }
108 }
109
110 let mut tmp = BigInteger384::new([0, 0, 0, 0, 0, 0]);
111 tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
115 tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
116 tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap());
117 tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap());
118 tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap());
119 tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap());
120
121 Fq::from_bigint(tmp)
122}
123
124fn extract_fq(bytes: [u8; 64]) -> Result<Fq, PrecompileFailure> {
125 let fq = decode_fq(bytes);
126 match fq {
127 None => Err(PrecompileFailure::Error {
128 exit_status: ExitError::Other("invalid Fq".into()),
129 }),
130 Some(c) => Ok(c),
131 }
132}
133
134fn read_fq(input: &[u8], offset: usize) -> Result<Fq, PrecompileFailure> {
135 let mut buf = [0u8; 64];
136 read_input(input, &mut buf, offset);
137 extract_fq(buf)
138}
139
140fn read_fq2(input: &[u8], offset: usize) -> Result<Fq2, PrecompileFailure> {
141 let mut x_buf = [0u8; 64];
142 let mut y_buf = [0u8; 64];
143 read_input(input, &mut x_buf, offset);
144 read_input(input, &mut y_buf, offset + 64);
145 let px = extract_fq(x_buf)?;
146 let py = extract_fq(y_buf)?;
147 Ok(Fq2::new(px, py))
148}
149
150fn map_to_curve_g1(fq: Fq) -> Result<G1Affine, HashToCurveError> {
151 let m2c = WBMap::<G1Config>::new()?;
152 m2c.map_to_curve(fq)
153}
154
155fn map_to_curve_g2(fq2: Fq2) -> Result<G2Affine, HashToCurveError> {
156 let m2c = WBMap::<G2Config>::new()?;
157 m2c.map_to_curve(fq2)
158}
159
160fn decode_g1(input: &[u8], offset: usize) -> Result<G1Projective, PrecompileFailure> {
162 let mut px_buf = [0u8; 64];
163 let mut py_buf = [0u8; 64];
164 read_input(input, &mut px_buf, offset);
165 read_input(input, &mut py_buf, offset + 64);
166
167 let px = extract_fq(px_buf)?;
169 let py = extract_fq(py_buf)?;
171
172 if px.is_zero() && py.is_zero() {
174 Ok(G1Projective::zero())
175 } else {
176 let g1 = G1Affine::new_unchecked(px, py);
177 if !g1.is_on_curve() {
178 Err(PrecompileFailure::Error {
179 exit_status: ExitError::Other("point is not on curve".into()),
180 })
181 } else {
182 Ok(g1.into())
183 }
184 }
185}
186
187fn decode_g2(input: &[u8], offset: usize) -> Result<G2Projective, PrecompileFailure> {
189 let mut px0_buf = [0u8; 64];
190 let mut px1_buf = [0u8; 64];
191 let mut py0_buf = [0u8; 64];
192 let mut py1_buf = [0u8; 64];
193 read_input(input, &mut px0_buf, offset);
194 read_input(input, &mut px1_buf, offset + 64);
195 read_input(input, &mut py0_buf, offset + 128);
196 read_input(input, &mut py1_buf, offset + 192);
197
198 let px0 = extract_fq(px0_buf)?;
199 let px1 = extract_fq(px1_buf)?;
200 let py0 = extract_fq(py0_buf)?;
201 let py1 = extract_fq(py1_buf)?;
202
203 let px = Fq2::new(px0, px1);
204 let py = Fq2::new(py0, py1);
205
206 if px.is_zero() && py.is_zero() {
207 Ok(G2Projective::zero())
208 } else {
209 let g2 = G2Affine::new_unchecked(px, py);
210 if !g2.is_on_curve() {
211 Err(PrecompileFailure::Error {
212 exit_status: ExitError::Other("point is not on curve".into()),
213 })
214 } else {
215 Ok(g2.into())
216 }
217 }
218}
219
220pub struct Bls12377G1Add;
222
223impl Bls12377G1Add {
224 const GAS_COST: u64 = 600;
226}
227
228impl Precompile for Bls12377G1Add {
229 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
233 handle.record_cost(Bls12377G1Add::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 p0 = decode_g1(input, 0)?;
244 let p1 = decode_g1(input, 128)?;
246 let r = p0 + p1;
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 Bls12377G1Mul;
260
261impl Bls12377G1Mul {
262 const GAS_COST: u64 = 12_000;
264}
265
266impl Precompile for Bls12377G1Mul {
267 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
271 handle.record_cost(Bls12377G1Mul::GAS_COST)?;
272
273 let input = handle.input();
274 if input.len() != 160 {
275 return Err(PrecompileFailure::Error {
276 exit_status: ExitError::Other("invalid input length".into()),
277 });
278 }
279
280 let p = decode_g1(input, 0)?;
282 let e = decode_fr(input, 128);
284 let r = p.mul(e);
286 let output = encode_g1(r.into_affine());
288
289 Ok(PrecompileOutput {
290 exit_status: ExitSucceed::Returned,
291 output: output.to_vec(),
292 })
293 }
294}
295
296pub struct Bls12377G1MultiExp;
298
299impl Bls12377G1MultiExp {
300 const MULTIPLIER: u64 = 1_000;
301
302 fn calculate_gas_cost(input_len: usize) -> u64 {
304 let k = input_len / 160;
306 if k == 0 {
307 return 0;
308 }
309 let d_len = BLS12377_MULTIEXP_DISCOUNT_TABLE.len();
311 let discount = if k <= d_len {
312 BLS12377_MULTIEXP_DISCOUNT_TABLE[k - 1]
313 } else {
314 BLS12377_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
315 };
316 k as u64 * Bls12377G1Mul::GAS_COST * discount as u64 / Bls12377G1MultiExp::MULTIPLIER
318 }
319}
320
321impl Precompile for Bls12377G1MultiExp {
322 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
326 let gas_cost = Bls12377G1MultiExp::calculate_gas_cost(handle.input().len());
327 handle.record_cost(gas_cost)?;
328
329 let k = handle.input().len() / 160;
330 if handle.input().is_empty() || handle.input().len() % 160 != 0 {
331 return Err(PrecompileFailure::Error {
332 exit_status: ExitError::Other("invalid input length".into()),
333 });
334 }
335
336 let input = handle.input();
337
338 let mut points = Vec::new();
339 let mut scalars = Vec::new();
340 for idx in 0..k {
342 let offset = idx * 160;
343 let p = decode_g1(input, offset)?;
345 let scalar = decode_fr(input, offset + 128);
347 points.push(p.into_affine());
348 scalars.push(scalar);
349 }
350
351 let r = G1Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
353 PrecompileFailure::Error {
354 exit_status: ExitError::Other("MSM failed".into()),
355 }
356 })?;
357
358 let output = encode_g1(r.into_affine());
360 Ok(PrecompileOutput {
361 exit_status: ExitSucceed::Returned,
362 output: output.to_vec(),
363 })
364 }
365}
366
367pub struct Bls12377G2Add;
369
370impl Bls12377G2Add {
371 const GAS_COST: u64 = 4_500;
373}
374
375impl Precompile for Bls12377G2Add {
376 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
380 handle.record_cost(Bls12377G2Add::GAS_COST)?;
381
382 let input = handle.input();
383 if input.len() != 512 {
384 return Err(PrecompileFailure::Error {
385 exit_status: ExitError::Other("invalid input length".into()),
386 });
387 }
388
389 let p0 = decode_g2(input, 0)?;
391 let p1 = decode_g2(input, 256)?;
393 let r = p0 + p1;
395 let output = encode_g2(r.into_affine());
397
398 Ok(PrecompileOutput {
399 exit_status: ExitSucceed::Returned,
400 output: output.to_vec(),
401 })
402 }
403}
404
405pub struct Bls12377G2Mul;
407
408impl Bls12377G2Mul {
409 const GAS_COST: u64 = 55_000;
411}
412
413impl Precompile for Bls12377G2Mul {
414 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
418 handle.record_cost(Bls12377G2Mul::GAS_COST)?;
419
420 let input = handle.input();
421 if input.len() != 288 {
422 return Err(PrecompileFailure::Error {
423 exit_status: ExitError::Other("invalid input length".into()),
424 });
425 }
426
427 let p = decode_g2(input, 0)?;
429 let e = decode_fr(input, 256);
431 let r = p.mul(e);
433 let output = encode_g2(r.into_affine());
435
436 Ok(PrecompileOutput {
437 exit_status: ExitSucceed::Returned,
438 output: output.to_vec(),
439 })
440 }
441}
442
443pub struct Bls12377G2MultiExp;
445
446impl Bls12377G2MultiExp {
447 const MULTIPLIER: u64 = 1_000;
448
449 fn calculate_gas_cost(input_len: usize) -> u64 {
451 let k = input_len / 288;
453 if k == 0 {
454 return 0;
455 }
456 let d_len = BLS12377_MULTIEXP_DISCOUNT_TABLE.len();
458 let discount = if k <= d_len {
459 BLS12377_MULTIEXP_DISCOUNT_TABLE[k - 1]
460 } else {
461 BLS12377_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
462 };
463 k as u64 * Bls12377G2Mul::GAS_COST * discount as u64 / Bls12377G2MultiExp::MULTIPLIER
465 }
466}
467
468impl Precompile for Bls12377G2MultiExp {
469 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
473 let gas_cost = Bls12377G2MultiExp::calculate_gas_cost(handle.input().len());
474 handle.record_cost(gas_cost)?;
475
476 let k = handle.input().len() / 288;
477 if handle.input().is_empty() || handle.input().len() % 288 != 0 {
478 return Err(PrecompileFailure::Error {
479 exit_status: ExitError::Other("invalid input length".into()),
480 });
481 }
482
483 let input = handle.input();
484
485 let mut points = Vec::new();
486 let mut scalars = Vec::new();
487 for idx in 0..k {
489 let offset = idx * 288;
490 let p = decode_g2(input, offset)?;
492 let scalar = decode_fr(input, offset + 256);
494 points.push(p.into_affine());
495 scalars.push(scalar);
496 }
497
498 let r = G2Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
500 PrecompileFailure::Error {
501 exit_status: ExitError::Other("MSM failed".into()),
502 }
503 })?;
504
505 let output = encode_g2(r.into_affine());
507 Ok(PrecompileOutput {
508 exit_status: ExitSucceed::Returned,
509 output: output.to_vec(),
510 })
511 }
512}
513
514pub struct Bls12377Pairing;
516
517impl Bls12377Pairing {
518 const BASE_GAS: u64 = 65_000;
520 const PER_PAIR_GAS: u64 = 55_000;
521}
522
523impl Precompile for Bls12377Pairing {
524 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
531 if handle.input().is_empty() || handle.input().len() % 384 != 0 {
532 return Err(PrecompileFailure::Error {
533 exit_status: ExitError::Other("invalid input length".into()),
534 });
535 }
536
537 let k = handle.input().len() / 384;
538 let gas_cost: u64 = Bls12377Pairing::BASE_GAS + (k as u64 * Bls12377Pairing::PER_PAIR_GAS);
539
540 handle.record_cost(gas_cost)?;
541
542 let input = handle.input();
543
544 let mut a = Vec::new();
545 let mut b = Vec::new();
546 for idx in 0..k {
548 let offset = idx * 384;
549 let g1 = decode_g1(input, offset)?;
551 let g2 = decode_g2(input, offset + 128)?;
553
554 if !g1.into_affine().is_in_correct_subgroup_assuming_on_curve() {
557 return Err(PrecompileFailure::Error {
558 exit_status: ExitError::Other("g1 point is not on correct subgroup".into()),
559 });
560 }
561 if !g2.into_affine().is_in_correct_subgroup_assuming_on_curve() {
562 return Err(PrecompileFailure::Error {
563 exit_status: ExitError::Other("g2 point is not on correct subgroup".into()),
564 });
565 }
566
567 a.push(g1);
568 b.push(g2);
569 }
570
571 let mut output = [0u8; 32];
572 if Bls12_377::multi_pairing(a, b).is_zero() {
574 output[31] = 1;
575 }
576
577 Ok(PrecompileOutput {
578 exit_status: ExitSucceed::Returned,
579 output: output.to_vec(),
580 })
581 }
582}
583
584pub struct Bls12377MapG1;
586
587impl Bls12377MapG1 {
588 const GAS_COST: u64 = 5_500;
589}
590
591impl Precompile for Bls12377MapG1 {
592 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
596 handle.record_cost(Bls12377MapG1::GAS_COST)?;
597
598 let input = handle.input();
599 if input.len() != 64 {
600 return Err(PrecompileFailure::Error {
601 exit_status: ExitError::Other("invalid input length".into()),
602 });
603 }
604
605 let fq = read_fq(input, 0)?;
606 let g1 = match map_to_curve_g1(fq) {
607 Ok(point) => point.clear_cofactor(),
608 Err(_) => {
609 return Err(PrecompileFailure::Error {
610 exit_status: ExitError::Other("map to curve failed".into()),
611 })
612 }
613 };
614
615 let output = encode_g1(g1);
616 Ok(PrecompileOutput {
617 exit_status: ExitSucceed::Returned,
618 output: output.to_vec(),
619 })
620 }
621}
622
623pub struct Bls12377MapG2;
625
626impl Bls12377MapG2 {
627 const GAS_COST: u64 = 75_000;
628}
629
630impl Precompile for Bls12377MapG2 {
631 fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
635 handle.record_cost(Bls12377MapG2::GAS_COST)?;
636
637 let input = handle.input();
638 if input.len() != 128 {
639 return Err(PrecompileFailure::Error {
640 exit_status: ExitError::Other("invalid input length".into()),
641 });
642 }
643
644 let fq2 = read_fq2(input, 0)?;
645 let g2 = match map_to_curve_g2(fq2) {
646 Ok(point) => point.clear_cofactor(),
647 Err(_) => {
648 return Err(PrecompileFailure::Error {
649 exit_status: ExitError::Other("map to curve failed".into()),
650 })
651 }
652 };
653
654 let output = encode_g2(g2);
655 Ok(PrecompileOutput {
656 exit_status: ExitSucceed::Returned,
657 output: output.to_vec(),
658 })
659 }
660}
661
662#[cfg(test)]
663mod tests;