revm_precompile/bls12_381/blst.rs
1// This module contains a safe wrapper around the blst library.
2
3use super::{G1Point, G2Point, PairingPair};
4use crate::{
5 bls12_381::{G1PointScalar, G2PointScalar},
6 bls12_381_const::{FP_LENGTH, G1_LENGTH, G2_LENGTH, SCALAR_LENGTH, SCALAR_LENGTH_BITS},
7 PrecompileError,
8};
9use blst::{
10 blst_bendian_from_fp, blst_final_exp, blst_fp, blst_fp12, blst_fp12_is_one, blst_fp12_mul,
11 blst_fp2, blst_fp_from_bendian, blst_map_to_g1, blst_map_to_g2, blst_miller_loop, blst_p1,
12 blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve,
13 blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine, blst_p2, blst_p2_add_or_double_affine,
14 blst_p2_affine, blst_p2_affine_in_g2, blst_p2_affine_on_curve, blst_p2_from_affine,
15 blst_p2_mult, blst_p2_to_affine, blst_scalar, blst_scalar_from_bendian, MultiPoint,
16};
17use std::vec::Vec;
18
19// Big-endian non-Montgomery form.
20const MODULUS_REPR: [u8; 48] = [
21 0x1a, 0x01, 0x11, 0xea, 0x39, 0x7f, 0xe6, 0x9a, 0x4b, 0x1b, 0xa7, 0xb6, 0x43, 0x4b, 0xac, 0xd7,
22 0x64, 0x77, 0x4b, 0x84, 0xf3, 0x85, 0x12, 0xbf, 0x67, 0x30, 0xd2, 0xa0, 0xf6, 0xb0, 0xf6, 0x24,
23 0x1e, 0xab, 0xff, 0xfe, 0xb1, 0x53, 0xff, 0xff, 0xb9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xab,
24];
25
26#[inline]
27pub(crate) fn p1_to_affine(p: &blst_p1) -> blst_p1_affine {
28 let mut p_affine = blst_p1_affine::default();
29 // SAFETY: both inputs are valid blst types
30 unsafe { blst_p1_to_affine(&mut p_affine, p) };
31 p_affine
32}
33
34#[inline]
35pub(crate) fn p1_from_affine(p_affine: &blst_p1_affine) -> blst_p1 {
36 let mut p = blst_p1::default();
37 // SAFETY: both inputs are valid blst types
38 unsafe { blst_p1_from_affine(&mut p, p_affine) };
39 p
40}
41
42#[inline]
43pub(crate) fn p1_add_or_double(p: &blst_p1, p_affine: &blst_p1_affine) -> blst_p1 {
44 let mut result = blst_p1::default();
45 // SAFETY: all inputs are valid blst types
46 unsafe { blst_p1_add_or_double_affine(&mut result, p, p_affine) };
47 result
48}
49
50#[inline]
51pub(crate) fn p2_to_affine(p: &blst_p2) -> blst_p2_affine {
52 let mut p_affine = blst_p2_affine::default();
53 // SAFETY: both inputs are valid blst types
54 unsafe { blst_p2_to_affine(&mut p_affine, p) };
55 p_affine
56}
57
58#[inline]
59pub(crate) fn p2_from_affine(p_affine: &blst_p2_affine) -> blst_p2 {
60 let mut p = blst_p2::default();
61 // SAFETY: both inputs are valid blst types
62 unsafe { blst_p2_from_affine(&mut p, p_affine) };
63 p
64}
65
66#[inline]
67pub(crate) fn p2_add_or_double(p: &blst_p2, p_affine: &blst_p2_affine) -> blst_p2 {
68 let mut result = blst_p2::default();
69 // SAFETY: all inputs are valid blst types
70 unsafe { blst_p2_add_or_double_affine(&mut result, p, p_affine) };
71 result
72}
73
74/// p1_add_affine adds two G1 points in affine form, returning the result in affine form
75///
76/// Note: `a` and `b` can be the same, ie this method is safe to call if one wants
77/// to essentially double a point
78#[inline]
79fn p1_add_affine(a: &blst_p1_affine, b: &blst_p1_affine) -> blst_p1_affine {
80 // Convert first point to Jacobian coordinates
81 let a_jacobian = p1_from_affine(a);
82
83 // Add second point (in affine) to first point (in Jacobian)
84 let sum_jacobian = p1_add_or_double(&a_jacobian, b);
85
86 // Convert result back to affine coordinates
87 p1_to_affine(&sum_jacobian)
88}
89
90/// Add two G2 points in affine form, returning the result in affine form
91#[inline]
92fn p2_add_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_affine {
93 // Convert first point to Jacobian coordinates
94 let a_jacobian = p2_from_affine(a);
95
96 // Add second point (in affine) to first point (in Jacobian)
97 let sum_jacobian = p2_add_or_double(&a_jacobian, b);
98
99 // Convert result back to affine coordinates
100 p2_to_affine(&sum_jacobian)
101}
102
103/// Performs a G1 scalar multiplication
104///
105/// Takes a G1 point in affine form and a scalar, and returns the result
106/// of the scalar multiplication in affine form
107///
108/// Note: The scalar is expected to be in Big Endian format.
109#[inline]
110pub(crate) fn p1_scalar_mul(p: &blst_p1_affine, scalar: &blst_scalar) -> blst_p1_affine {
111 // Convert point to Jacobian coordinates
112 let p_jacobian = p1_from_affine(p);
113
114 let mut result = blst_p1::default();
115
116 // SAFETY: all inputs are valid blst types
117 unsafe {
118 blst_p1_mult(
119 &mut result,
120 &p_jacobian,
121 scalar.b.as_ptr(),
122 scalar.b.len() * 8,
123 )
124 };
125
126 // Convert result back to affine coordinates
127 p1_to_affine(&result)
128}
129
130/// Performs a G2 scalar multiplication
131///
132/// Takes a G2 point in affine form and a scalar, and returns the result
133/// of the scalar multiplication in affine form
134///
135/// Note: The scalar is expected to be in Big Endian format.
136#[inline]
137pub(crate) fn p2_scalar_mul(p: &blst_p2_affine, scalar: &blst_scalar) -> blst_p2_affine {
138 // Convert point to Jacobian coordinates
139 let p_jacobian = p2_from_affine(p);
140
141 let mut result = blst_p2::default();
142 // SAFETY: all inputs are valid blst types
143 unsafe {
144 blst_p2_mult(
145 &mut result,
146 &p_jacobian,
147 scalar.b.as_ptr(),
148 scalar.b.len() * 8,
149 )
150 };
151
152 // Convert result back to affine coordinates
153 p2_to_affine(&result)
154}
155
156/// Performs multi-scalar multiplication (MSM) for G1 points
157///
158/// Takes a vector of G1 points and corresponding scalars, and returns their weighted sum
159///
160/// Note: This method assumes that `g1_points` does not contain any points at infinity.
161#[inline]
162fn p1_msm(g1_points: Vec<blst_p1_affine>, scalars: Vec<blst_scalar>) -> blst_p1_affine {
163 assert_eq!(
164 g1_points.len(),
165 scalars.len(),
166 "number of scalars should equal the number of g1 points"
167 );
168
169 // When no inputs are given, we return the point at infinity.
170 // This case can only trigger, if the initial MSM pairs
171 // all had, either a zero scalar or the point at infinity.
172 //
173 // The precompile will return an error, if the initial input
174 // was empty, in accordance with EIP-2537.
175 if g1_points.is_empty() {
176 return blst_p1_affine::default();
177 }
178
179 // When there is only a single point, we use a simpler scalar multiplication
180 // procedure
181 if g1_points.len() == 1 {
182 return p1_scalar_mul(&g1_points[0], &scalars[0]);
183 }
184
185 let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect();
186 // Perform multi-scalar multiplication
187 let multiexp = g1_points.mult(&scalars_bytes, SCALAR_LENGTH_BITS);
188
189 // Convert result back to affine coordinates
190 p1_to_affine(&multiexp)
191}
192
193/// Performs multi-scalar multiplication (MSM) for G2 points
194///
195/// Takes a vector of G2 points and corresponding scalars, and returns their weighted sum
196///
197/// Note: Scalars are expected to be in Big Endian format.
198/// This method assumes that `g2_points` does not contain any points at infinity.
199#[inline]
200fn p2_msm(g2_points: Vec<blst_p2_affine>, scalars: Vec<blst_scalar>) -> blst_p2_affine {
201 assert_eq!(
202 g2_points.len(),
203 scalars.len(),
204 "number of scalars should equal the number of g2 points"
205 );
206
207 // When no inputs are given, we return the point at infinity.
208 // This case can only trigger, if the initial MSM pairs
209 // all had, either a zero scalar or the point at infinity.
210 //
211 // The precompile will return an error, if the initial input
212 // was empty, in accordance with EIP-2537.
213 if g2_points.is_empty() {
214 return blst_p2_affine::default();
215 }
216
217 // When there is only a single point, we use a simpler scalar multiplication
218 // procedure
219 if g2_points.len() == 1 {
220 return p2_scalar_mul(&g2_points[0], &scalars[0]);
221 }
222
223 let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect();
224
225 // Perform multi-scalar multiplication
226 let multiexp = g2_points.mult(&scalars_bytes, SCALAR_LENGTH_BITS);
227
228 // Convert result back to affine coordinates
229 p2_to_affine(&multiexp)
230}
231
232/// Maps a field element to a G1 point
233///
234/// Takes a field element (blst_fp) and returns the corresponding G1 point in affine form
235#[inline]
236fn map_fp_to_g1(fp: &blst_fp) -> blst_p1_affine {
237 // Create a new G1 point in Jacobian coordinates
238 let mut p = blst_p1::default();
239
240 // Map the field element to a point on the curve
241 // SAFETY: `p` and `fp` are blst values
242 // Third argument is unused if null
243 unsafe { blst_map_to_g1(&mut p, fp, core::ptr::null()) };
244
245 // Convert to affine coordinates
246 p1_to_affine(&p)
247}
248
249/// Maps a field element to a G2 point
250///
251/// Takes a field element (blst_fp2) and returns the corresponding G2 point in affine form
252#[inline]
253fn map_fp2_to_g2(fp2: &blst_fp2) -> blst_p2_affine {
254 // Create a new G2 point in Jacobian coordinates
255 let mut p = blst_p2::default();
256
257 // Map the field element to a point on the curve
258 // SAFETY: `p` and `fp2` are blst values
259 // Third argument is unused if null
260 unsafe { blst_map_to_g2(&mut p, fp2, core::ptr::null()) };
261
262 // Convert to affine coordinates
263 p2_to_affine(&p)
264}
265
266/// Computes a single miller loop for a given G1, G2 pair
267#[inline]
268fn compute_miller_loop(g1: &blst_p1_affine, g2: &blst_p2_affine) -> blst_fp12 {
269 let mut result = blst_fp12::default();
270
271 // SAFETY: All arguments are valid blst types
272 unsafe { blst_miller_loop(&mut result, g2, g1) }
273
274 result
275}
276
277/// multiply_fp12 multiplies two fp12 elements
278#[inline]
279fn multiply_fp12(a: &blst_fp12, b: &blst_fp12) -> blst_fp12 {
280 let mut result = blst_fp12::default();
281
282 // SAFETY: All arguments are valid blst types
283 unsafe { blst_fp12_mul(&mut result, a, b) }
284
285 result
286}
287
288/// final_exp computes the final exponentiation on an fp12 element
289#[inline]
290fn final_exp(f: &blst_fp12) -> blst_fp12 {
291 let mut result = blst_fp12::default();
292
293 // SAFETY: All arguments are valid blst types
294 unsafe { blst_final_exp(&mut result, f) }
295
296 result
297}
298
299/// is_fp12_one checks if an fp12 element equals
300/// multiplicative identity element, one
301#[inline]
302fn is_fp12_one(f: &blst_fp12) -> bool {
303 // SAFETY: argument is a valid blst type
304 unsafe { blst_fp12_is_one(f) }
305}
306
307/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and
308/// returns true if the result is equal to the identity element.
309#[inline]
310pub(crate) fn pairing_check(pairs: &[(blst_p1_affine, blst_p2_affine)]) -> bool {
311 // When no inputs are given, we return true
312 // This case can only trigger, if the initial pairing components
313 // all had, either the G1 element as the point at infinity
314 // or the G2 element as the point at infinity.
315 //
316 // The precompile will return an error, if the initial input
317 // was empty, in accordance with EIP-2537.
318 if pairs.is_empty() {
319 return true;
320 }
321 // Compute the miller loop for the first pair
322 let (first_g1, first_g2) = &pairs[0];
323 let mut acc = compute_miller_loop(first_g1, first_g2);
324
325 // For the remaining pairs, compute miller loop and multiply with the accumulated result
326 for (g1, g2) in pairs.iter().skip(1) {
327 let ml = compute_miller_loop(g1, g2);
328 acc = multiply_fp12(&acc, &ml);
329 }
330
331 // Apply final exponentiation and check if result is 1
332 let final_result = final_exp(&acc);
333
334 // Check if the result is one (identity element)
335 is_fp12_one(&final_result)
336}
337
338/// Encodes a G1 point in affine format into byte slice.
339///
340/// Note: The encoded bytes are in Big Endian format.
341fn encode_g1_point(input: &blst_p1_affine) -> [u8; G1_LENGTH] {
342 let mut out = [0u8; G1_LENGTH];
343 fp_to_bytes(&mut out[..FP_LENGTH], &input.x);
344 fp_to_bytes(&mut out[FP_LENGTH..], &input.y);
345 out
346}
347
348/// Encodes a single finite field element into byte slice.
349///
350/// Note: The encoded bytes are in Big Endian format.
351fn fp_to_bytes(out: &mut [u8], input: &blst_fp) {
352 if out.len() != FP_LENGTH {
353 return;
354 }
355 // SAFETY: Out length is checked previously, `input` is a blst value.
356 unsafe { blst_bendian_from_fp(out.as_mut_ptr(), input) };
357}
358
359/// Returns a `blst_p1_affine` from the provided byte slices, which represent the x and y
360/// affine coordinates of the point.
361///
362/// Note: Coordinates are expected to be in Big Endian format.
363///
364/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
365/// See [read_fp] for more information.
366/// - If the point is not on the curve, an error is returned.
367fn decode_g1_on_curve(
368 p0_x: &[u8; FP_LENGTH],
369 p0_y: &[u8; FP_LENGTH],
370) -> Result<blst_p1_affine, PrecompileError> {
371 let out = blst_p1_affine {
372 x: read_fp(p0_x)?,
373 y: read_fp(p0_y)?,
374 };
375
376 // From EIP-2537:
377 //
378 // Error cases:
379 //
380 // * An input is neither a point on the G1 elliptic curve nor the infinity point
381 //
382 // SAFETY: Out is a blst value.
383 if unsafe { !blst_p1_affine_on_curve(&out) } {
384 return Err(PrecompileError::Bls12381G1NotOnCurve);
385 }
386
387 Ok(out)
388}
389
390/// Extracts a G1 point in Affine format from the x and y coordinates.
391///
392/// Note: Coordinates are expected to be in Big Endian format.
393/// By default, subgroup checks are performed.
394fn read_g1(x: &[u8; FP_LENGTH], y: &[u8; FP_LENGTH]) -> Result<blst_p1_affine, PrecompileError> {
395 _extract_g1_input(x, y, true)
396}
397/// Extracts a G1 point in Affine format from the x and y coordinates
398/// without performing a subgroup check.
399///
400/// Note: Coordinates are expected to be in Big Endian format.
401/// Skipping subgroup checks can introduce security issues.
402/// This method should only be called if:
403/// - The EIP specifies that no subgroup check should be performed
404/// - One can be certain that the point is in the correct subgroup.
405fn read_g1_no_subgroup_check(
406 x: &[u8; FP_LENGTH],
407 y: &[u8; FP_LENGTH],
408) -> Result<blst_p1_affine, PrecompileError> {
409 _extract_g1_input(x, y, false)
410}
411/// Extracts a G1 point in Affine format from the x and y coordinates.
412///
413/// Note: Coordinates are expected to be in Big Endian format.
414/// This function will perform a G1 subgroup check if `subgroup_check` is set to `true`.
415fn _extract_g1_input(
416 x: &[u8; FP_LENGTH],
417 y: &[u8; FP_LENGTH],
418 subgroup_check: bool,
419) -> Result<blst_p1_affine, PrecompileError> {
420 let out = decode_g1_on_curve(x, y)?;
421
422 if subgroup_check {
423 // NB: Subgroup checks
424 //
425 // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
426 //
427 // Implementations SHOULD use the optimized subgroup check method:
428 //
429 // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
430 //
431 // On any input that fail the subgroup check, the precompile MUST return an error.
432 //
433 // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
434 // use endomorphism acceleration.
435 if unsafe { !blst_p1_affine_in_g1(&out) } {
436 return Err(PrecompileError::Bls12381G1NotInSubgroup);
437 }
438 }
439 Ok(out)
440}
441
442/// Encodes a G2 point in affine format into byte slice.
443///
444/// Note: The encoded bytes are in Big Endian format.
445fn encode_g2_point(input: &blst_p2_affine) -> [u8; G2_LENGTH] {
446 let mut out = [0u8; G2_LENGTH];
447 fp_to_bytes(&mut out[..FP_LENGTH], &input.x.fp[0]);
448 fp_to_bytes(&mut out[FP_LENGTH..2 * FP_LENGTH], &input.x.fp[1]);
449 fp_to_bytes(&mut out[2 * FP_LENGTH..3 * FP_LENGTH], &input.y.fp[0]);
450 fp_to_bytes(&mut out[3 * FP_LENGTH..4 * FP_LENGTH], &input.y.fp[1]);
451 out
452}
453
454/// Returns a `blst_p2_affine` from the provided byte slices, which represent the x and y
455/// affine coordinates of the point.
456///
457/// Note: Coordinates are expected to be in Big Endian format.
458///
459/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
460/// See [read_fp2] for more information.
461/// - If the point is not on the curve, an error is returned.
462fn decode_g2_on_curve(
463 x1: &[u8; FP_LENGTH],
464 x2: &[u8; FP_LENGTH],
465 y1: &[u8; FP_LENGTH],
466 y2: &[u8; FP_LENGTH],
467) -> Result<blst_p2_affine, PrecompileError> {
468 let out = blst_p2_affine {
469 x: read_fp2(x1, x2)?,
470 y: read_fp2(y1, y2)?,
471 };
472
473 // From EIP-2537:
474 //
475 // Error cases:
476 //
477 // * An input is neither a point on the G2 elliptic curve nor the infinity point
478 //
479 // SAFETY: Out is a blst value.
480 if unsafe { !blst_p2_affine_on_curve(&out) } {
481 return Err(PrecompileError::Bls12381G2NotOnCurve);
482 }
483
484 Ok(out)
485}
486
487/// Creates a blst_fp2 element from two field elements.
488///
489/// Field elements are expected to be in Big Endian format.
490/// Returns an error if either of the input field elements is not canonical.
491fn read_fp2(
492 input_1: &[u8; FP_LENGTH],
493 input_2: &[u8; FP_LENGTH],
494) -> Result<blst_fp2, PrecompileError> {
495 let fp_1 = read_fp(input_1)?;
496 let fp_2 = read_fp(input_2)?;
497
498 let fp2 = blst_fp2 { fp: [fp_1, fp_2] };
499
500 Ok(fp2)
501}
502/// Extracts a G2 point in Affine format from the x and y coordinates.
503///
504/// Note: Coordinates are expected to be in Big Endian format.
505/// By default, subgroup checks are performed.
506fn read_g2(
507 a_x_0: &[u8; FP_LENGTH],
508 a_x_1: &[u8; FP_LENGTH],
509 a_y_0: &[u8; FP_LENGTH],
510 a_y_1: &[u8; FP_LENGTH],
511) -> Result<blst_p2_affine, PrecompileError> {
512 _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, true)
513}
514/// Extracts a G2 point in Affine format from the x and y coordinates
515/// without performing a subgroup check.
516///
517/// Note: Coordinates are expected to be in Big Endian format.
518/// Skipping subgroup checks can introduce security issues.
519/// This method should only be called if:
520/// - The EIP specifies that no subgroup check should be performed
521/// - One can be certain that the point is in the correct subgroup.
522fn read_g2_no_subgroup_check(
523 a_x_0: &[u8; FP_LENGTH],
524 a_x_1: &[u8; FP_LENGTH],
525 a_y_0: &[u8; FP_LENGTH],
526 a_y_1: &[u8; FP_LENGTH],
527) -> Result<blst_p2_affine, PrecompileError> {
528 _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, false)
529}
530/// Extracts a G2 point in Affine format from the x and y coordinates.
531///
532/// Note: Coordinates are expected to be in Big Endian format.
533/// This function will perform a G2 subgroup check if `subgroup_check` is set to `true`.
534fn _extract_g2_input(
535 a_x_0: &[u8; FP_LENGTH],
536 a_x_1: &[u8; FP_LENGTH],
537 a_y_0: &[u8; FP_LENGTH],
538 a_y_1: &[u8; FP_LENGTH],
539 subgroup_check: bool,
540) -> Result<blst_p2_affine, PrecompileError> {
541 let out = decode_g2_on_curve(a_x_0, a_x_1, a_y_0, a_y_1)?;
542
543 if subgroup_check {
544 // NB: Subgroup checks
545 //
546 // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
547 //
548 // Implementations SHOULD use the optimized subgroup check method:
549 //
550 // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
551 //
552 // On any input that fail the subgroup check, the precompile MUST return an error.
553 //
554 // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
555 // use endomorphism acceleration.
556 if unsafe { !blst_p2_affine_in_g2(&out) } {
557 return Err(PrecompileError::Bls12381G2NotInSubgroup);
558 }
559 }
560 Ok(out)
561}
562
563/// Checks whether or not the input represents a canonical field element
564/// returning the field element if successful.
565///
566/// Note: The field element is expected to be in big endian format.
567fn read_fp(input: &[u8; FP_LENGTH]) -> Result<blst_fp, PrecompileError> {
568 if !is_valid_be(input) {
569 return Err(PrecompileError::NonCanonicalFp);
570 }
571 let mut fp = blst_fp::default();
572 // SAFETY: `input` has fixed length, and `fp` is a blst value.
573 unsafe {
574 // This performs the check for canonical field elements
575 blst_fp_from_bendian(&mut fp, input.as_ptr());
576 }
577
578 Ok(fp)
579}
580
581/// Extracts a scalar from a 32 byte slice representation, decoding the input as a Big Endian
582/// unsigned integer. If the input is not exactly 32 bytes long, an error is returned.
583///
584/// From [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537):
585/// * A scalar for the multiplication operation is encoded as 32 bytes by performing BigEndian
586/// encoding of the corresponding (unsigned) integer.
587///
588/// We do not check that the scalar is a canonical Fr element, because the EIP specifies:
589/// * The corresponding integer is not required to be less than or equal than main subgroup order
590/// `q`.
591fn read_scalar(input: &[u8]) -> Result<blst_scalar, PrecompileError> {
592 if input.len() != SCALAR_LENGTH {
593 return Err(PrecompileError::Bls12381ScalarInputLength);
594 }
595
596 let mut out = blst_scalar::default();
597 // SAFETY: `input` length is checked previously, out is a blst value.
598 unsafe {
599 // Note: We do not use `blst_scalar_fr_check` here because, from EIP-2537:
600 //
601 // * The corresponding integer is not required to be less than or equal than main subgroup
602 // order `q`.
603 blst_scalar_from_bendian(&mut out, input.as_ptr())
604 };
605
606 Ok(out)
607}
608
609/// Checks if the input is a valid big-endian representation of a field element.
610fn is_valid_be(input: &[u8; 48]) -> bool {
611 *input < MODULUS_REPR
612}
613
614// Byte-oriented versions of the functions for external API compatibility
615
616/// Performs point addition on two G1 points taking byte coordinates.
617#[inline]
618pub(crate) fn p1_add_affine_bytes(
619 a: G1Point,
620 b: G1Point,
621) -> Result<[u8; G1_LENGTH], crate::PrecompileError> {
622 let (a_x, a_y) = a;
623 let (b_x, b_y) = b;
624 // Parse first point
625 let p1 = read_g1_no_subgroup_check(&a_x, &a_y)?;
626
627 // Parse second point
628 let p2 = read_g1_no_subgroup_check(&b_x, &b_y)?;
629
630 // Perform addition
631 let result = p1_add_affine(&p1, &p2);
632
633 // Encode result
634 Ok(encode_g1_point(&result))
635}
636
637/// Performs point addition on two G2 points taking byte coordinates.
638#[inline]
639pub(crate) fn p2_add_affine_bytes(
640 a: G2Point,
641 b: G2Point,
642) -> Result<[u8; G2_LENGTH], crate::PrecompileError> {
643 let (a_x_0, a_x_1, a_y_0, a_y_1) = a;
644 let (b_x_0, b_x_1, b_y_0, b_y_1) = b;
645 // Parse first point
646 let p1 = read_g2_no_subgroup_check(&a_x_0, &a_x_1, &a_y_0, &a_y_1)?;
647
648 // Parse second point
649 let p2 = read_g2_no_subgroup_check(&b_x_0, &b_x_1, &b_y_0, &b_y_1)?;
650
651 // Perform addition
652 let result = p2_add_affine(&p1, &p2);
653
654 // Encode result
655 Ok(encode_g2_point(&result))
656}
657
658/// Maps a field element to a G1 point from bytes
659#[inline]
660pub(crate) fn map_fp_to_g1_bytes(
661 fp_bytes: &[u8; FP_LENGTH],
662) -> Result<[u8; G1_LENGTH], crate::PrecompileError> {
663 let fp = read_fp(fp_bytes)?;
664 let result = map_fp_to_g1(&fp);
665 Ok(encode_g1_point(&result))
666}
667
668/// Maps field elements to a G2 point from bytes
669#[inline]
670pub(crate) fn map_fp2_to_g2_bytes(
671 fp2_x: &[u8; FP_LENGTH],
672 fp2_y: &[u8; FP_LENGTH],
673) -> Result<[u8; G2_LENGTH], crate::PrecompileError> {
674 let fp2 = read_fp2(fp2_x, fp2_y)?;
675 let result = map_fp2_to_g2(&fp2);
676 Ok(encode_g2_point(&result))
677}
678
679/// Performs multi-scalar multiplication (MSM) for G1 points taking byte inputs.
680#[inline]
681pub(crate) fn p1_msm_bytes(
682 point_scalar_pairs: impl Iterator<Item = Result<G1PointScalar, crate::PrecompileError>>,
683) -> Result<[u8; G1_LENGTH], crate::PrecompileError> {
684 let mut g1_points = Vec::new();
685 let mut scalars = Vec::new();
686
687 // Parse all points and scalars
688 for pair_result in point_scalar_pairs {
689 let ((x, y), scalar_bytes) = pair_result?;
690
691 // NB: MSM requires subgroup check
692 let point = read_g1(&x, &y)?;
693
694 // Skip zero scalars after validating the point
695 if scalar_bytes.iter().all(|&b| b == 0) {
696 continue;
697 }
698
699 let scalar = read_scalar(&scalar_bytes)?;
700 g1_points.push(point);
701 scalars.push(scalar);
702 }
703
704 // Return point at infinity if no pairs were provided or all scalars were zero
705 if g1_points.is_empty() {
706 return Ok([0u8; G1_LENGTH]);
707 }
708
709 // Perform MSM
710 let result = p1_msm(g1_points, scalars);
711
712 // Encode result
713 Ok(encode_g1_point(&result))
714}
715
716/// Performs multi-scalar multiplication (MSM) for G2 points taking byte inputs.
717#[inline]
718pub(crate) fn p2_msm_bytes(
719 point_scalar_pairs: impl Iterator<Item = Result<G2PointScalar, crate::PrecompileError>>,
720) -> Result<[u8; G2_LENGTH], crate::PrecompileError> {
721 let mut g2_points = Vec::new();
722 let mut scalars = Vec::new();
723
724 // Parse all points and scalars
725 for pair_result in point_scalar_pairs {
726 let ((x_0, x_1, y_0, y_1), scalar_bytes) = pair_result?;
727
728 // NB: MSM requires subgroup check
729 let point = read_g2(&x_0, &x_1, &y_0, &y_1)?;
730
731 // Skip zero scalars after validating the point
732 if scalar_bytes.iter().all(|&b| b == 0) {
733 continue;
734 }
735
736 let scalar = read_scalar(&scalar_bytes)?;
737 g2_points.push(point);
738 scalars.push(scalar);
739 }
740
741 // Return point at infinity if no pairs were provided or all scalars were zero
742 if g2_points.is_empty() {
743 return Ok([0u8; G2_LENGTH]);
744 }
745
746 // Perform MSM
747 let result = p2_msm(g2_points, scalars);
748
749 // Encode result
750 Ok(encode_g2_point(&result))
751}
752
753/// pairing_check_bytes performs a pairing check on a list of G1 and G2 point pairs taking byte inputs.
754#[inline]
755pub(crate) fn pairing_check_bytes(pairs: &[PairingPair]) -> Result<bool, crate::PrecompileError> {
756 if pairs.is_empty() {
757 return Ok(true);
758 }
759
760 let mut parsed_pairs = Vec::with_capacity(pairs.len());
761 for ((g1_x, g1_y), (g2_x_0, g2_x_1, g2_y_0, g2_y_1)) in pairs {
762 // Check if G1 point is zero (point at infinity)
763 let g1_is_zero = g1_x.iter().all(|&b| b == 0) && g1_y.iter().all(|&b| b == 0);
764
765 // Check if G2 point is zero (point at infinity)
766 let g2_is_zero = g2_x_0.iter().all(|&b| b == 0)
767 && g2_x_1.iter().all(|&b| b == 0)
768 && g2_y_0.iter().all(|&b| b == 0)
769 && g2_y_1.iter().all(|&b| b == 0);
770
771 // Skip this pair if either point is at infinity as it's a no-op
772 if g1_is_zero || g2_is_zero {
773 // Still need to validate the non-zero point if one exists
774 if !g1_is_zero {
775 let _ = read_g1(g1_x, g1_y)?;
776 }
777 if !g2_is_zero {
778 let _ = read_g2(g2_x_0, g2_x_1, g2_y_0, g2_y_1)?;
779 }
780 continue;
781 }
782
783 let g1_point = read_g1(g1_x, g1_y)?;
784 let g2_point = read_g2(g2_x_0, g2_x_1, g2_y_0, g2_y_1)?;
785 parsed_pairs.push((g1_point, g2_point));
786 }
787
788 // If all pairs were filtered out, return true (identity element)
789 if parsed_pairs.is_empty() {
790 return Ok(true);
791 }
792
793 Ok(pairing_check(&parsed_pairs))
794}