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 PrecompileHalt,
8};
9use blst::{
10 blst_bendian_from_fp, blst_final_exp, blst_fp, blst_fp12, blst_fp12_is_one, blst_fp2,
11 blst_fp_from_bendian, blst_map_to_g1, blst_map_to_g2, blst_miller_loop_n, 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 // SAFETY: blst_scalar is repr(C) with a single `b: [u8; 32]` field.
186 let scalars_bytes =
187 unsafe { core::slice::from_raw_parts(scalars.as_ptr() as *const u8, scalars.len() * 32) };
188 // Perform multi-scalar multiplication
189 let multiexp = g1_points.mult(scalars_bytes, SCALAR_LENGTH_BITS);
190
191 // Convert result back to affine coordinates
192 p1_to_affine(&multiexp)
193}
194
195/// Performs multi-scalar multiplication (MSM) for G2 points
196///
197/// Takes a vector of G2 points and corresponding scalars, and returns their weighted sum
198///
199/// Note: Scalars are expected to be in Big Endian format.
200/// This method assumes that `g2_points` does not contain any points at infinity.
201#[inline]
202fn p2_msm(g2_points: Vec<blst_p2_affine>, scalars: Vec<blst_scalar>) -> blst_p2_affine {
203 assert_eq!(
204 g2_points.len(),
205 scalars.len(),
206 "number of scalars should equal the number of g2 points"
207 );
208
209 // When no inputs are given, we return the point at infinity.
210 // This case can only trigger, if the initial MSM pairs
211 // all had, either a zero scalar or the point at infinity.
212 //
213 // The precompile will return an error, if the initial input
214 // was empty, in accordance with EIP-2537.
215 if g2_points.is_empty() {
216 return blst_p2_affine::default();
217 }
218
219 // When there is only a single point, we use a simpler scalar multiplication
220 // procedure
221 if g2_points.len() == 1 {
222 return p2_scalar_mul(&g2_points[0], &scalars[0]);
223 }
224
225 // SAFETY: blst_scalar is repr(C) with a single `b: [u8; 32]` field.
226 let scalars_bytes =
227 unsafe { core::slice::from_raw_parts(scalars.as_ptr() as *const u8, scalars.len() * 32) };
228
229 // Perform multi-scalar multiplication
230 let multiexp = g2_points.mult(scalars_bytes, SCALAR_LENGTH_BITS);
231
232 // Convert result back to affine coordinates
233 p2_to_affine(&multiexp)
234}
235
236/// Maps a field element to a G1 point
237///
238/// Takes a field element (blst_fp) and returns the corresponding G1 point in affine form
239#[inline]
240fn map_fp_to_g1(fp: &blst_fp) -> blst_p1_affine {
241 // Create a new G1 point in Jacobian coordinates
242 let mut p = blst_p1::default();
243
244 // Map the field element to a point on the curve
245 // SAFETY: `p` and `fp` are blst values
246 // Third argument is unused if null
247 unsafe { blst_map_to_g1(&mut p, fp, core::ptr::null()) };
248
249 // Convert to affine coordinates
250 p1_to_affine(&p)
251}
252
253/// Maps a field element to a G2 point
254///
255/// Takes a field element (blst_fp2) and returns the corresponding G2 point in affine form
256#[inline]
257fn map_fp2_to_g2(fp2: &blst_fp2) -> blst_p2_affine {
258 // Create a new G2 point in Jacobian coordinates
259 let mut p = blst_p2::default();
260
261 // Map the field element to a point on the curve
262 // SAFETY: `p` and `fp2` are blst values
263 // Third argument is unused if null
264 unsafe { blst_map_to_g2(&mut p, fp2, core::ptr::null()) };
265
266 // Convert to affine coordinates
267 p2_to_affine(&p)
268}
269
270/// final_exp computes the final exponentiation on an fp12 element
271#[inline]
272fn final_exp(f: &blst_fp12) -> blst_fp12 {
273 let mut result = blst_fp12::default();
274
275 // SAFETY: All arguments are valid blst types
276 unsafe { blst_final_exp(&mut result, f) }
277
278 result
279}
280
281/// is_fp12_one checks if an fp12 element equals
282/// multiplicative identity element, one
283#[inline]
284fn is_fp12_one(f: &blst_fp12) -> bool {
285 // SAFETY: argument is a valid blst type
286 unsafe { blst_fp12_is_one(f) }
287}
288
289/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and
290/// returns true if the result is equal to the identity element.
291///
292/// Note: `pairs` must not contain points at infinity. Callers must skip such
293/// pairs (their pairing is the identity element): `blst_miller_loop_n`, unlike
294/// the per-pair `blst_miller_loop`, does not special-case infinity and would
295/// feed the all-zero point representation into the line evaluations.
296#[inline]
297pub(crate) fn pairing_check(pairs: &[(blst_p1_affine, blst_p2_affine)]) -> bool {
298 // When no inputs are given, we return true
299 // This case can only trigger, if the initial pairing components
300 // all had, either the G1 element as the point at infinity
301 // or the G2 element as the point at infinity.
302 //
303 // The precompile will return an error, if the initial input
304 // was empty, in accordance with EIP-2537.
305 if pairs.is_empty() {
306 return true;
307 }
308
309 // Fused multi-miller loop over all pairs, matching the arkworks backend's
310 // `multi_pairing`. We use the raw FFI, not blst's `miller_loop_n` wrapper, which
311 // threads (undesirable in a precompile, unavailable on no_std).
312 let (g1_points, g2_points): (Vec<blst_p1_affine>, Vec<blst_p2_affine>) =
313 pairs.iter().copied().unzip();
314
315 // `blst_miller_loop_n` takes null-terminated arrays of pointers to point arrays.
316 let qs: [*const blst_p2_affine; 2] = [g2_points.as_ptr(), core::ptr::null()];
317 let ps: [*const blst_p1_affine; 2] = [g1_points.as_ptr(), core::ptr::null()];
318
319 let mut acc = blst_fp12::default();
320 // SAFETY: `qs`/`ps` are null-terminated arrays over `pairs.len()` valid points.
321 unsafe { blst_miller_loop_n(&mut acc, qs.as_ptr(), ps.as_ptr(), pairs.len()) };
322
323 is_fp12_one(&final_exp(&acc))
324}
325
326/// Encodes a G1 point in affine format into byte slice.
327///
328/// Note: The encoded bytes are in Big Endian format.
329fn encode_g1_point(input: &blst_p1_affine) -> [u8; G1_LENGTH] {
330 let mut out = [0u8; G1_LENGTH];
331 fp_to_bytes(&mut out[..FP_LENGTH], &input.x);
332 fp_to_bytes(&mut out[FP_LENGTH..], &input.y);
333 out
334}
335
336/// Encodes a single finite field element into byte slice.
337///
338/// Note: The encoded bytes are in Big Endian format.
339fn fp_to_bytes(out: &mut [u8], input: &blst_fp) {
340 if out.len() != FP_LENGTH {
341 return;
342 }
343 // SAFETY: Out length is checked previously, `input` is a blst value.
344 unsafe { blst_bendian_from_fp(out.as_mut_ptr(), input) };
345}
346
347/// Returns a `blst_p1_affine` from the provided byte slices, which represent the x and y
348/// affine coordinates of the point.
349///
350/// Note: Coordinates are expected to be in Big Endian format.
351///
352/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
353/// See [read_fp] for more information.
354/// - If the point is not on the curve, an error is returned.
355fn decode_g1_on_curve(
356 p0_x: &[u8; FP_LENGTH],
357 p0_y: &[u8; FP_LENGTH],
358) -> Result<blst_p1_affine, PrecompileHalt> {
359 let out = blst_p1_affine {
360 x: read_fp(p0_x)?,
361 y: read_fp(p0_y)?,
362 };
363
364 // From EIP-2537:
365 //
366 // Error cases:
367 //
368 // * An input is neither a point on the G1 elliptic curve nor the infinity point
369 //
370 // SAFETY: Out is a blst value.
371 if unsafe { !blst_p1_affine_on_curve(&out) } {
372 return Err(PrecompileHalt::Bls12381G1NotOnCurve);
373 }
374
375 Ok(out)
376}
377
378/// Extracts a G1 point in Affine format from the x and y coordinates.
379///
380/// Note: Coordinates are expected to be in Big Endian format.
381/// By default, subgroup checks are performed.
382fn read_g1(x: &[u8; FP_LENGTH], y: &[u8; FP_LENGTH]) -> Result<blst_p1_affine, PrecompileHalt> {
383 _extract_g1_input(x, y, true)
384}
385/// Extracts a G1 point in Affine format from the x and y coordinates
386/// without performing a subgroup check.
387///
388/// Note: Coordinates are expected to be in Big Endian format.
389/// Skipping subgroup checks can introduce security issues.
390/// This method should only be called if:
391/// - The EIP specifies that no subgroup check should be performed
392/// - One can be certain that the point is in the correct subgroup.
393fn read_g1_no_subgroup_check(
394 x: &[u8; FP_LENGTH],
395 y: &[u8; FP_LENGTH],
396) -> Result<blst_p1_affine, PrecompileHalt> {
397 _extract_g1_input(x, y, false)
398}
399/// Extracts a G1 point in Affine format from the x and y coordinates.
400///
401/// Note: Coordinates are expected to be in Big Endian format.
402/// This function will perform a G1 subgroup check if `subgroup_check` is set to `true`.
403fn _extract_g1_input(
404 x: &[u8; FP_LENGTH],
405 y: &[u8; FP_LENGTH],
406 subgroup_check: bool,
407) -> Result<blst_p1_affine, PrecompileHalt> {
408 let out = decode_g1_on_curve(x, y)?;
409
410 if subgroup_check {
411 // NB: Subgroup checks
412 //
413 // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
414 //
415 // Implementations SHOULD use the optimized subgroup check method:
416 //
417 // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
418 //
419 // On any input that fail the subgroup check, the precompile MUST return an error.
420 //
421 // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
422 // use endomorphism acceleration.
423 if unsafe { !blst_p1_affine_in_g1(&out) } {
424 return Err(PrecompileHalt::Bls12381G1NotInSubgroup);
425 }
426 }
427 Ok(out)
428}
429
430/// Encodes a G2 point in affine format into byte slice.
431///
432/// Note: The encoded bytes are in Big Endian format.
433fn encode_g2_point(input: &blst_p2_affine) -> [u8; G2_LENGTH] {
434 let mut out = [0u8; G2_LENGTH];
435 fp_to_bytes(&mut out[..FP_LENGTH], &input.x.fp[0]);
436 fp_to_bytes(&mut out[FP_LENGTH..2 * FP_LENGTH], &input.x.fp[1]);
437 fp_to_bytes(&mut out[2 * FP_LENGTH..3 * FP_LENGTH], &input.y.fp[0]);
438 fp_to_bytes(&mut out[3 * FP_LENGTH..4 * FP_LENGTH], &input.y.fp[1]);
439 out
440}
441
442/// Returns a `blst_p2_affine` from the provided byte slices, which represent the x and y
443/// affine coordinates of the point.
444///
445/// Note: Coordinates are expected to be in Big Endian format.
446///
447/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
448/// See [read_fp2] for more information.
449/// - If the point is not on the curve, an error is returned.
450fn decode_g2_on_curve(
451 x1: &[u8; FP_LENGTH],
452 x2: &[u8; FP_LENGTH],
453 y1: &[u8; FP_LENGTH],
454 y2: &[u8; FP_LENGTH],
455) -> Result<blst_p2_affine, PrecompileHalt> {
456 let out = blst_p2_affine {
457 x: read_fp2(x1, x2)?,
458 y: read_fp2(y1, y2)?,
459 };
460
461 // From EIP-2537:
462 //
463 // Error cases:
464 //
465 // * An input is neither a point on the G2 elliptic curve nor the infinity point
466 //
467 // SAFETY: Out is a blst value.
468 if unsafe { !blst_p2_affine_on_curve(&out) } {
469 return Err(PrecompileHalt::Bls12381G2NotOnCurve);
470 }
471
472 Ok(out)
473}
474
475/// Creates a blst_fp2 element from two field elements.
476///
477/// Field elements are expected to be in Big Endian format.
478/// Returns an error if either of the input field elements is not canonical.
479fn read_fp2(
480 input_1: &[u8; FP_LENGTH],
481 input_2: &[u8; FP_LENGTH],
482) -> Result<blst_fp2, PrecompileHalt> {
483 let fp_1 = read_fp(input_1)?;
484 let fp_2 = read_fp(input_2)?;
485
486 let fp2 = blst_fp2 { fp: [fp_1, fp_2] };
487
488 Ok(fp2)
489}
490/// Extracts a G2 point in Affine format from the x and y coordinates.
491///
492/// Note: Coordinates are expected to be in Big Endian format.
493/// By default, subgroup checks are performed.
494fn read_g2(
495 a_x_0: &[u8; FP_LENGTH],
496 a_x_1: &[u8; FP_LENGTH],
497 a_y_0: &[u8; FP_LENGTH],
498 a_y_1: &[u8; FP_LENGTH],
499) -> Result<blst_p2_affine, PrecompileHalt> {
500 _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, true)
501}
502/// Extracts a G2 point in Affine format from the x and y coordinates
503/// without performing a subgroup check.
504///
505/// Note: Coordinates are expected to be in Big Endian format.
506/// Skipping subgroup checks can introduce security issues.
507/// This method should only be called if:
508/// - The EIP specifies that no subgroup check should be performed
509/// - One can be certain that the point is in the correct subgroup.
510fn read_g2_no_subgroup_check(
511 a_x_0: &[u8; FP_LENGTH],
512 a_x_1: &[u8; FP_LENGTH],
513 a_y_0: &[u8; FP_LENGTH],
514 a_y_1: &[u8; FP_LENGTH],
515) -> Result<blst_p2_affine, PrecompileHalt> {
516 _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, false)
517}
518/// Extracts a G2 point in Affine format from the x and y coordinates.
519///
520/// Note: Coordinates are expected to be in Big Endian format.
521/// This function will perform a G2 subgroup check if `subgroup_check` is set to `true`.
522fn _extract_g2_input(
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 subgroup_check: bool,
528) -> Result<blst_p2_affine, PrecompileHalt> {
529 let out = decode_g2_on_curve(a_x_0, a_x_1, a_y_0, a_y_1)?;
530
531 if subgroup_check {
532 // NB: Subgroup checks
533 //
534 // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
535 //
536 // Implementations SHOULD use the optimized subgroup check method:
537 //
538 // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
539 //
540 // On any input that fail the subgroup check, the precompile MUST return an error.
541 //
542 // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
543 // use endomorphism acceleration.
544 if unsafe { !blst_p2_affine_in_g2(&out) } {
545 return Err(PrecompileHalt::Bls12381G2NotInSubgroup);
546 }
547 }
548 Ok(out)
549}
550
551/// Checks whether or not the input represents a canonical field element
552/// returning the field element if successful.
553///
554/// Note: The field element is expected to be in big endian format.
555fn read_fp(input: &[u8; FP_LENGTH]) -> Result<blst_fp, PrecompileHalt> {
556 // Performs the check for canonical field elements
557 if !is_valid_be(input) {
558 return Err(PrecompileHalt::NonCanonicalFp);
559 }
560 let mut fp = blst_fp::default();
561 // SAFETY: `input` has fixed length, and `fp` is a blst value.
562 unsafe {
563 blst_fp_from_bendian(&mut fp, input.as_ptr());
564 }
565
566 Ok(fp)
567}
568
569/// Extracts a scalar from a 32 byte slice representation, decoding the input as a Big Endian
570/// unsigned integer. If the input is not exactly 32 bytes long, an error is returned.
571///
572/// From [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537):
573/// * A scalar for the multiplication operation is encoded as 32 bytes by performing BigEndian
574/// encoding of the corresponding (unsigned) integer.
575///
576/// We do not check that the scalar is a canonical Fr element, because the EIP specifies:
577/// * The corresponding integer is not required to be less than or equal than main subgroup order
578/// `q`.
579fn read_scalar(input: &[u8]) -> Result<blst_scalar, PrecompileHalt> {
580 if input.len() != SCALAR_LENGTH {
581 return Err(PrecompileHalt::Bls12381ScalarInputLength);
582 }
583
584 let mut out = blst_scalar::default();
585 // SAFETY: `input` length is checked previously, out is a blst value.
586 unsafe {
587 // Note: We do not use `blst_scalar_fr_check` here because, from EIP-2537:
588 //
589 // * The corresponding integer is not required to be less than or equal than main subgroup
590 // order `q`.
591 blst_scalar_from_bendian(&mut out, input.as_ptr())
592 };
593
594 Ok(out)
595}
596
597/// Checks if the input is a valid big-endian representation of a field element.
598fn is_valid_be(input: &[u8; 48]) -> bool {
599 *input < MODULUS_REPR
600}
601
602// Byte-oriented versions of the functions for external API compatibility
603
604/// Performs point addition on two G1 points taking byte coordinates.
605#[inline]
606pub(crate) fn p1_add_affine_bytes(
607 a: G1Point,
608 b: G1Point,
609) -> Result<[u8; G1_LENGTH], crate::PrecompileHalt> {
610 let (a_x, a_y) = a;
611 let (b_x, b_y) = b;
612 // Parse first point
613 let p1 = read_g1_no_subgroup_check(&a_x, &a_y)?;
614
615 // Parse second point
616 let p2 = read_g1_no_subgroup_check(&b_x, &b_y)?;
617
618 // Perform addition
619 let result = p1_add_affine(&p1, &p2);
620
621 // Encode result
622 Ok(encode_g1_point(&result))
623}
624
625/// Performs point addition on two G2 points taking byte coordinates.
626#[inline]
627pub(crate) fn p2_add_affine_bytes(
628 a: G2Point,
629 b: G2Point,
630) -> Result<[u8; G2_LENGTH], crate::PrecompileHalt> {
631 let (a_x_0, a_x_1, a_y_0, a_y_1) = a;
632 let (b_x_0, b_x_1, b_y_0, b_y_1) = b;
633 // Parse first point
634 let p1 = read_g2_no_subgroup_check(&a_x_0, &a_x_1, &a_y_0, &a_y_1)?;
635
636 // Parse second point
637 let p2 = read_g2_no_subgroup_check(&b_x_0, &b_x_1, &b_y_0, &b_y_1)?;
638
639 // Perform addition
640 let result = p2_add_affine(&p1, &p2);
641
642 // Encode result
643 Ok(encode_g2_point(&result))
644}
645
646/// Maps a field element to a G1 point from bytes
647#[inline]
648pub(crate) fn map_fp_to_g1_bytes(
649 fp_bytes: &[u8; FP_LENGTH],
650) -> Result<[u8; G1_LENGTH], crate::PrecompileHalt> {
651 let fp = read_fp(fp_bytes)?;
652 let result = map_fp_to_g1(&fp);
653 Ok(encode_g1_point(&result))
654}
655
656/// Maps field elements to a G2 point from bytes
657#[inline]
658pub(crate) fn map_fp2_to_g2_bytes(
659 fp2_x: &[u8; FP_LENGTH],
660 fp2_y: &[u8; FP_LENGTH],
661) -> Result<[u8; G2_LENGTH], crate::PrecompileHalt> {
662 let fp2 = read_fp2(fp2_x, fp2_y)?;
663 let result = map_fp2_to_g2(&fp2);
664 Ok(encode_g2_point(&result))
665}
666
667/// Performs multi-scalar multiplication (MSM) for G1 points taking byte inputs.
668#[inline]
669pub(crate) fn p1_msm_bytes(
670 point_scalar_pairs: impl Iterator<Item = Result<G1PointScalar, crate::PrecompileHalt>>,
671) -> Result<[u8; G1_LENGTH], crate::PrecompileHalt> {
672 let (lower, _) = point_scalar_pairs.size_hint();
673 let mut g1_points = Vec::with_capacity(lower);
674 let mut scalars = Vec::with_capacity(lower);
675
676 // Parse all points and scalars
677 for pair_result in point_scalar_pairs {
678 let ((x, y), scalar_bytes) = pair_result?;
679
680 // NB: MSM requires subgroup check
681 let point = read_g1(&x, &y)?;
682
683 // Skip zero scalars after validating the point
684 if scalar_bytes.iter().all(|&b| b == 0) {
685 continue;
686 }
687
688 let scalar = read_scalar(&scalar_bytes)?;
689 g1_points.push(point);
690 scalars.push(scalar);
691 }
692
693 // Return point at infinity if no pairs were provided or all scalars were zero
694 if g1_points.is_empty() {
695 return Ok([0u8; G1_LENGTH]);
696 }
697
698 // Perform MSM
699 let result = p1_msm(g1_points, scalars);
700
701 // Encode result
702 Ok(encode_g1_point(&result))
703}
704
705/// Performs multi-scalar multiplication (MSM) for G2 points taking byte inputs.
706#[inline]
707pub(crate) fn p2_msm_bytes(
708 point_scalar_pairs: impl Iterator<Item = Result<G2PointScalar, crate::PrecompileHalt>>,
709) -> Result<[u8; G2_LENGTH], crate::PrecompileHalt> {
710 let (lower, _) = point_scalar_pairs.size_hint();
711 let mut g2_points = Vec::with_capacity(lower);
712 let mut scalars = Vec::with_capacity(lower);
713
714 // Parse all points and scalars
715 for pair_result in point_scalar_pairs {
716 let ((x_0, x_1, y_0, y_1), scalar_bytes) = pair_result?;
717
718 // NB: MSM requires subgroup check
719 let point = read_g2(&x_0, &x_1, &y_0, &y_1)?;
720
721 // Skip zero scalars after validating the point
722 if scalar_bytes.iter().all(|&b| b == 0) {
723 continue;
724 }
725
726 let scalar = read_scalar(&scalar_bytes)?;
727 g2_points.push(point);
728 scalars.push(scalar);
729 }
730
731 // Return point at infinity if no pairs were provided or all scalars were zero
732 if g2_points.is_empty() {
733 return Ok([0u8; G2_LENGTH]);
734 }
735
736 // Perform MSM
737 let result = p2_msm(g2_points, scalars);
738
739 // Encode result
740 Ok(encode_g2_point(&result))
741}
742
743/// pairing_check_bytes performs a pairing check on a list of G1 and G2 point pairs taking byte inputs.
744#[inline]
745pub(crate) fn pairing_check_bytes(pairs: &[PairingPair]) -> Result<bool, crate::PrecompileHalt> {
746 super::pairing_common::pairing_check_bytes_generic(pairs, read_g1, read_g2, pairing_check)
747}