revm_precompile/bls12_381/
blst.rs

1// This module contains a safe wrapper around the blst library.
2
3use crate::{
4    bls12_381_const::{
5        FP_LENGTH, FP_PAD_BY, PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH, SCALAR_LENGTH,
6        SCALAR_LENGTH_BITS,
7    },
8    PrecompileError,
9};
10use blst::{
11    blst_bendian_from_fp, blst_final_exp, blst_fp, blst_fp12, blst_fp12_is_one, blst_fp12_mul,
12    blst_fp2, blst_fp_from_bendian, blst_map_to_g1, blst_map_to_g2, blst_miller_loop, blst_p1,
13    blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve,
14    blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine, blst_p2, blst_p2_add_or_double_affine,
15    blst_p2_affine, blst_p2_affine_in_g2, blst_p2_affine_on_curve, blst_p2_from_affine,
16    blst_p2_mult, blst_p2_to_affine, blst_scalar, blst_scalar_from_bendian, MultiPoint,
17};
18use std::string::ToString;
19use std::vec::Vec;
20
21// Big-endian non-Montgomery form.
22const MODULUS_REPR: [u8; 48] = [
23    0x1a, 0x01, 0x11, 0xea, 0x39, 0x7f, 0xe6, 0x9a, 0x4b, 0x1b, 0xa7, 0xb6, 0x43, 0x4b, 0xac, 0xd7,
24    0x64, 0x77, 0x4b, 0x84, 0xf3, 0x85, 0x12, 0xbf, 0x67, 0x30, 0xd2, 0xa0, 0xf6, 0xb0, 0xf6, 0x24,
25    0x1e, 0xab, 0xff, 0xfe, 0xb1, 0x53, 0xff, 0xff, 0xb9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xab,
26];
27
28#[inline]
29fn p1_to_affine(p: &blst_p1) -> blst_p1_affine {
30    let mut p_affine = blst_p1_affine::default();
31    // SAFETY: both inputs are valid blst types
32    unsafe { blst_p1_to_affine(&mut p_affine, p) };
33    p_affine
34}
35
36#[inline]
37fn p1_from_affine(p_affine: &blst_p1_affine) -> blst_p1 {
38    let mut p = blst_p1::default();
39    // SAFETY: both inputs are valid blst types
40    unsafe { blst_p1_from_affine(&mut p, p_affine) };
41    p
42}
43
44#[inline]
45fn p1_add_or_double(p: &blst_p1, p_affine: &blst_p1_affine) -> blst_p1 {
46    let mut result = blst_p1::default();
47    // SAFETY: all inputs are valid blst types
48    unsafe { blst_p1_add_or_double_affine(&mut result, p, p_affine) };
49    result
50}
51
52#[inline]
53fn p2_to_affine(p: &blst_p2) -> blst_p2_affine {
54    let mut p_affine = blst_p2_affine::default();
55    // SAFETY: both inputs are valid blst types
56    unsafe { blst_p2_to_affine(&mut p_affine, p) };
57    p_affine
58}
59
60#[inline]
61fn p2_from_affine(p_affine: &blst_p2_affine) -> blst_p2 {
62    let mut p = blst_p2::default();
63    // SAFETY: both inputs are valid blst types
64    unsafe { blst_p2_from_affine(&mut p, p_affine) };
65    p
66}
67
68#[inline]
69fn p2_add_or_double(p: &blst_p2, p_affine: &blst_p2_affine) -> blst_p2 {
70    let mut result = blst_p2::default();
71    // SAFETY: all inputs are valid blst types
72    unsafe { blst_p2_add_or_double_affine(&mut result, p, p_affine) };
73    result
74}
75
76/// p1_add_affine adds two G1 points in affine form, returning the result in affine form
77///
78/// Note: `a` and `b` can be the same, ie this method is safe to call if one wants
79/// to essentially double a point
80#[inline]
81pub(super) fn p1_add_affine(a: &blst_p1_affine, b: &blst_p1_affine) -> blst_p1_affine {
82    // Convert first point to Jacobian coordinates
83    let a_jacobian = p1_from_affine(a);
84
85    // Add second point (in affine) to first point (in Jacobian)
86    let sum_jacobian = p1_add_or_double(&a_jacobian, b);
87
88    // Convert result back to affine coordinates
89    p1_to_affine(&sum_jacobian)
90}
91
92/// Add two G2 points in affine form, returning the result in affine form
93#[inline]
94pub(super) fn p2_add_affine(a: &blst_p2_affine, b: &blst_p2_affine) -> blst_p2_affine {
95    // Convert first point to Jacobian coordinates
96    let a_jacobian = p2_from_affine(a);
97
98    // Add second point (in affine) to first point (in Jacobian)
99    let sum_jacobian = p2_add_or_double(&a_jacobian, b);
100
101    // Convert result back to affine coordinates
102    p2_to_affine(&sum_jacobian)
103}
104
105/// Performs a G1 scalar multiplication
106///
107/// Takes a G1 point in affine form and a scalar, and returns the result
108/// of the scalar multiplication in affine form
109///
110/// Note: The scalar is expected to be in Big Endian format.
111#[inline]
112fn p1_scalar_mul(p: &blst_p1_affine, scalar: &blst_scalar) -> blst_p1_affine {
113    // Convert point to Jacobian coordinates
114    let p_jacobian = p1_from_affine(p);
115
116    let mut result = blst_p1::default();
117
118    // SAFETY: all inputs are valid blst types
119    unsafe {
120        blst_p1_mult(
121            &mut result,
122            &p_jacobian,
123            scalar.b.as_ptr(),
124            scalar.b.len() * 8,
125        )
126    };
127
128    // Convert result back to affine coordinates
129    p1_to_affine(&result)
130}
131
132/// Performs a G2 scalar multiplication
133///
134/// Takes a G2 point in affine form and a scalar, and returns the result
135/// of the scalar multiplication in affine form
136///
137/// Note: The scalar is expected to be in Big Endian format.
138#[inline]
139fn p2_scalar_mul(p: &blst_p2_affine, scalar: &blst_scalar) -> blst_p2_affine {
140    // Convert point to Jacobian coordinates
141    let p_jacobian = p2_from_affine(p);
142
143    let mut result = blst_p2::default();
144    // SAFETY: all inputs are valid blst types
145    unsafe {
146        blst_p2_mult(
147            &mut result,
148            &p_jacobian,
149            scalar.b.as_ptr(),
150            scalar.b.len() * 8,
151        )
152    };
153
154    // Convert result back to affine coordinates
155    p2_to_affine(&result)
156}
157
158/// Performs multi-scalar multiplication (MSM) for G1 points
159///
160/// Takes a vector of G1 points and corresponding scalars, and returns their weighted sum
161///
162/// Note: This method assumes that `g1_points` does not contain any points at infinity.
163#[inline]
164pub(super) fn p1_msm(g1_points: Vec<blst_p1_affine>, scalars: Vec<blst_scalar>) -> blst_p1_affine {
165    assert_eq!(
166        g1_points.len(),
167        scalars.len(),
168        "number of scalars should equal the number of g1 points"
169    );
170
171    // When no inputs are given, we return the point at infinity.
172    // This case can only trigger, if the initial MSM pairs
173    // all had, either a zero scalar or the point at infinity.
174    //
175    // The precompile will return an error, if the initial input
176    // was empty, in accordance with EIP-2537.
177    if g1_points.is_empty() {
178        return blst_p1_affine::default();
179    }
180
181    // When there is only a single point, we use a simpler scalar multiplication
182    // procedure
183    if g1_points.len() == 1 {
184        return p1_scalar_mul(&g1_points[0], &scalars[0]);
185    }
186
187    let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect();
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]
202pub(super) fn 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    let scalars_bytes: Vec<_> = scalars.into_iter().flat_map(|s| s.b).collect();
226
227    // Perform multi-scalar multiplication
228    let multiexp = g2_points.mult(&scalars_bytes, SCALAR_LENGTH_BITS);
229
230    // Convert result back to affine coordinates
231    p2_to_affine(&multiexp)
232}
233
234/// Maps a field element to a G1 point
235///
236/// Takes a field element (blst_fp) and returns the corresponding G1 point in affine form
237#[inline]
238pub(super) fn map_fp_to_g1(fp: &blst_fp) -> blst_p1_affine {
239    // Create a new G1 point in Jacobian coordinates
240    let mut p = blst_p1::default();
241
242    // Map the field element to a point on the curve
243    // SAFETY: `p` and `fp` are blst values
244    // Third argument is unused if null
245    unsafe { blst_map_to_g1(&mut p, fp, core::ptr::null()) };
246
247    // Convert to affine coordinates
248    p1_to_affine(&p)
249}
250
251/// Maps a field element to a G2 point
252///
253/// Takes a field element (blst_fp2) and returns the corresponding G2 point in affine form
254#[inline]
255pub(super) fn map_fp2_to_g2(fp2: &blst_fp2) -> blst_p2_affine {
256    // Create a new G2 point in Jacobian coordinates
257    let mut p = blst_p2::default();
258
259    // Map the field element to a point on the curve
260    // SAFETY: `p` and `fp2` are blst values
261    // Third argument is unused if null
262    unsafe { blst_map_to_g2(&mut p, fp2, core::ptr::null()) };
263
264    // Convert to affine coordinates
265    p2_to_affine(&p)
266}
267
268/// Computes a single miller loop for a given G1, G2 pair
269#[inline]
270fn compute_miller_loop(g1: &blst_p1_affine, g2: &blst_p2_affine) -> blst_fp12 {
271    let mut result = blst_fp12::default();
272
273    // SAFETY: All arguments are valid blst types
274    unsafe { blst_miller_loop(&mut result, g2, g1) }
275
276    result
277}
278
279/// multiply_fp12 multiplies two fp12 elements
280#[inline]
281fn multiply_fp12(a: &blst_fp12, b: &blst_fp12) -> blst_fp12 {
282    let mut result = blst_fp12::default();
283
284    // SAFETY: All arguments are valid blst types
285    unsafe { blst_fp12_mul(&mut result, a, b) }
286
287    result
288}
289
290/// final_exp computes the final exponentiation on an fp12 element
291#[inline]
292fn final_exp(f: &blst_fp12) -> blst_fp12 {
293    let mut result = blst_fp12::default();
294
295    // SAFETY: All arguments are valid blst types
296    unsafe { blst_final_exp(&mut result, f) }
297
298    result
299}
300
301/// is_fp12_one checks if an fp12 element equals
302/// multiplicative identity element, one
303#[inline]
304fn is_fp12_one(f: &blst_fp12) -> bool {
305    // SAFETY: argument is a valid blst type
306    unsafe { blst_fp12_is_one(f) }
307}
308
309/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and
310/// returns true if the result is equal to the identity element.
311#[inline]
312pub(super) fn pairing_check(pairs: &[(blst_p1_affine, blst_p2_affine)]) -> bool {
313    // When no inputs are given, we return true
314    // This case can only trigger, if the initial pairing components
315    // all had, either the G1 element as the point at infinity
316    // or the G2 element as the point at infinity.
317    //
318    // The precompile will return an error, if the initial input
319    // was empty, in accordance with EIP-2537.
320    if pairs.is_empty() {
321        return true;
322    }
323    // Compute the miller loop for the first pair
324    let (first_g1, first_g2) = &pairs[0];
325    let mut acc = compute_miller_loop(first_g1, first_g2);
326
327    // For the remaining pairs, compute miller loop and multiply with the accumulated result
328    for (g1, g2) in pairs.iter().skip(1) {
329        let ml = compute_miller_loop(g1, g2);
330        acc = multiply_fp12(&acc, &ml);
331    }
332
333    // Apply final exponentiation and check if result is 1
334    let final_result = final_exp(&acc);
335
336    // Check if the result is one (identity element)
337    is_fp12_one(&final_result)
338}
339
340/// Encodes a G1 point in affine format into byte slice with padded elements.
341///
342/// Note: The encoded bytes are in Big Endian format.
343pub(super) fn encode_g1_point(input: &blst_p1_affine) -> [u8; PADDED_G1_LENGTH] {
344    let mut out = [0u8; PADDED_G1_LENGTH];
345    fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &input.x);
346    fp_to_bytes(&mut out[PADDED_FP_LENGTH..], &input.y);
347    out
348}
349
350/// Encodes a single finite field element into byte slice with padding.
351///
352/// Note: The encoded bytes are in Big Endian format.
353fn fp_to_bytes(out: &mut [u8], input: &blst_fp) {
354    if out.len() != PADDED_FP_LENGTH {
355        return;
356    }
357    let (padding, rest) = out.split_at_mut(FP_PAD_BY);
358    padding.fill(0);
359    // SAFETY: Out length is checked previously, `input` is a blst value.
360    unsafe { blst_bendian_from_fp(rest.as_mut_ptr(), input) };
361}
362
363/// Returns a `blst_p1_affine` from the provided byte slices, which represent the x and y
364/// affine coordinates of the point.
365///
366/// Note: Coordinates are expected to be in Big Endian format.
367///
368/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
369///   See [read_fp] for more information.
370/// - If the point is not on the curve, an error is returned.
371fn decode_g1_on_curve(
372    p0_x: &[u8; FP_LENGTH],
373    p0_y: &[u8; FP_LENGTH],
374) -> Result<blst_p1_affine, PrecompileError> {
375    let out = blst_p1_affine {
376        x: read_fp(p0_x)?,
377        y: read_fp(p0_y)?,
378    };
379
380    // From EIP-2537:
381    //
382    // Error cases:
383    //
384    // * An input is neither a point on the G1 elliptic curve nor the infinity point
385    //
386    // SAFETY: Out is a blst value.
387    if unsafe { !blst_p1_affine_on_curve(&out) } {
388        return Err(PrecompileError::Other(
389            "Element not on G1 curve".to_string(),
390        ));
391    }
392
393    Ok(out)
394}
395
396/// Extracts a G1 point in Affine format from the x and y coordinates.
397///
398/// Note: Coordinates are expected to be in Big Endian format.
399/// By default, subgroup checks are performed.
400pub(super) fn read_g1(
401    x: &[u8; FP_LENGTH],
402    y: &[u8; FP_LENGTH],
403) -> Result<blst_p1_affine, PrecompileError> {
404    _extract_g1_input(x, y, true)
405}
406/// Extracts a G1 point in Affine format from the x and y coordinates
407/// without performing a subgroup check.
408///
409/// Note: Coordinates are expected to be in Big Endian format.
410/// Skipping subgroup checks can introduce security issues.
411/// This method should only be called if:
412///     - The EIP specifies that no subgroup check should be performed
413///     - One can be certain that the point is in the correct subgroup.
414pub(super) fn read_g1_no_subgroup_check(
415    x: &[u8; FP_LENGTH],
416    y: &[u8; FP_LENGTH],
417) -> Result<blst_p1_affine, PrecompileError> {
418    _extract_g1_input(x, y, false)
419}
420/// Extracts a G1 point in Affine format from the x and y coordinates.
421///
422/// Note: Coordinates are expected to be in Big Endian format.
423/// This function will perform a G1 subgroup check if `subgroup_check` is set to `true`.
424fn _extract_g1_input(
425    x: &[u8; FP_LENGTH],
426    y: &[u8; FP_LENGTH],
427    subgroup_check: bool,
428) -> Result<blst_p1_affine, PrecompileError> {
429    let out = decode_g1_on_curve(x, y)?;
430
431    if subgroup_check {
432        // NB: Subgroup checks
433        //
434        // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
435        //
436        // Implementations SHOULD use the optimized subgroup check method:
437        //
438        // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
439        //
440        // On any input that fail the subgroup check, the precompile MUST return an error.
441        //
442        // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
443        // use endomorphism acceleration.
444        if unsafe { !blst_p1_affine_in_g1(&out) } {
445            return Err(PrecompileError::Other("Element not in G1".to_string()));
446        }
447    }
448    Ok(out)
449}
450
451/// Encodes a G2 point in affine format into byte slice with padded elements.
452///
453/// Note: The encoded bytes are in Big Endian format.
454pub(super) fn encode_g2_point(input: &blst_p2_affine) -> [u8; PADDED_G2_LENGTH] {
455    let mut out = [0u8; PADDED_G2_LENGTH];
456    fp_to_bytes(&mut out[..PADDED_FP_LENGTH], &input.x.fp[0]);
457    fp_to_bytes(
458        &mut out[PADDED_FP_LENGTH..2 * PADDED_FP_LENGTH],
459        &input.x.fp[1],
460    );
461    fp_to_bytes(
462        &mut out[2 * PADDED_FP_LENGTH..3 * PADDED_FP_LENGTH],
463        &input.y.fp[0],
464    );
465    fp_to_bytes(
466        &mut out[3 * PADDED_FP_LENGTH..4 * PADDED_FP_LENGTH],
467        &input.y.fp[1],
468    );
469    out
470}
471
472/// Returns a `blst_p2_affine` from the provided byte slices, which represent the x and y
473/// affine coordinates of the point.
474///
475/// Note: Coordinates are expected to be in Big Endian format.
476///
477/// - If the x or y coordinate do not represent a canonical field element, an error is returned.
478///   See [read_fp2] for more information.
479/// - If the point is not on the curve, an error is returned.
480fn decode_g2_on_curve(
481    x1: &[u8; FP_LENGTH],
482    x2: &[u8; FP_LENGTH],
483    y1: &[u8; FP_LENGTH],
484    y2: &[u8; FP_LENGTH],
485) -> Result<blst_p2_affine, PrecompileError> {
486    let out = blst_p2_affine {
487        x: read_fp2(x1, x2)?,
488        y: read_fp2(y1, y2)?,
489    };
490
491    // From EIP-2537:
492    //
493    // Error cases:
494    //
495    // * An input is neither a point on the G2 elliptic curve nor the infinity point
496    //
497    // SAFETY: Out is a blst value.
498    if unsafe { !blst_p2_affine_on_curve(&out) } {
499        return Err(PrecompileError::Other(
500            "Element not on G2 curve".to_string(),
501        ));
502    }
503
504    Ok(out)
505}
506
507/// Creates a blst_fp2 element from two field elements.
508///
509/// Field elements are expected to be in Big Endian format.
510/// Returns an error if either of the input field elements is not canonical.
511pub(super) fn read_fp2(
512    input_1: &[u8; FP_LENGTH],
513    input_2: &[u8; FP_LENGTH],
514) -> Result<blst_fp2, PrecompileError> {
515    let fp_1 = read_fp(input_1)?;
516    let fp_2 = read_fp(input_2)?;
517
518    let fp2 = blst_fp2 { fp: [fp_1, fp_2] };
519
520    Ok(fp2)
521}
522/// Extracts a G2 point in Affine format from the x and y coordinates.
523///
524/// Note: Coordinates are expected to be in Big Endian format.
525/// By default, subgroup checks are performed.
526pub(super) fn read_g2(
527    a_x_0: &[u8; FP_LENGTH],
528    a_x_1: &[u8; FP_LENGTH],
529    a_y_0: &[u8; FP_LENGTH],
530    a_y_1: &[u8; FP_LENGTH],
531) -> Result<blst_p2_affine, PrecompileError> {
532    _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, true)
533}
534/// Extracts a G2 point in Affine format from the x and y coordinates
535/// without performing a subgroup check.
536///
537/// Note: Coordinates are expected to be in Big Endian format.
538/// Skipping subgroup checks can introduce security issues.
539/// This method should only be called if:
540///     - The EIP specifies that no subgroup check should be performed
541///     - One can be certain that the point is in the correct subgroup.
542pub(super) fn read_g2_no_subgroup_check(
543    a_x_0: &[u8; FP_LENGTH],
544    a_x_1: &[u8; FP_LENGTH],
545    a_y_0: &[u8; FP_LENGTH],
546    a_y_1: &[u8; FP_LENGTH],
547) -> Result<blst_p2_affine, PrecompileError> {
548    _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, false)
549}
550/// Extracts a G2 point in Affine format from the x and y coordinates.
551///
552/// Note: Coordinates are expected to be in Big Endian format.
553/// This function will perform a G2 subgroup check if `subgroup_check` is set to `true`.
554fn _extract_g2_input(
555    a_x_0: &[u8; FP_LENGTH],
556    a_x_1: &[u8; FP_LENGTH],
557    a_y_0: &[u8; FP_LENGTH],
558    a_y_1: &[u8; FP_LENGTH],
559    subgroup_check: bool,
560) -> Result<blst_p2_affine, PrecompileError> {
561    let out = decode_g2_on_curve(a_x_0, a_x_1, a_y_0, a_y_1)?;
562
563    if subgroup_check {
564        // NB: Subgroup checks
565        //
566        // Scalar multiplications, MSMs and pairings MUST perform a subgroup check.
567        //
568        // Implementations SHOULD use the optimized subgroup check method:
569        //
570        // https://eips.ethereum.org/assets/eip-2537/fast_subgroup_checks
571        //
572        // On any input that fail the subgroup check, the precompile MUST return an error.
573        //
574        // As endomorphism acceleration requires input on the correct subgroup, implementers MAY
575        // use endomorphism acceleration.
576        if unsafe { !blst_p2_affine_in_g2(&out) } {
577            return Err(PrecompileError::Other("Element not in G2".to_string()));
578        }
579    }
580    Ok(out)
581}
582
583/// Checks whether or not the input represents a canonical field element
584/// returning the field element if successful.
585///
586/// Note: The field element is expected to be in big endian format.
587pub(super) fn read_fp(input: &[u8; FP_LENGTH]) -> Result<blst_fp, PrecompileError> {
588    if !is_valid_be(input) {
589        return Err(PrecompileError::Other("non-canonical fp value".to_string()));
590    }
591    let mut fp = blst_fp::default();
592    // SAFETY: `input` has fixed length, and `fp` is a blst value.
593    unsafe {
594        // This performs the check for canonical field elements
595        blst_fp_from_bendian(&mut fp, input.as_ptr());
596    }
597
598    Ok(fp)
599}
600
601/// Extracts a scalar from a 32 byte slice representation, decoding the input as a Big Endian
602/// unsigned integer. If the input is not exactly 32 bytes long, an error is returned.
603///
604/// From [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537):
605/// * A scalar for the multiplication operation is encoded as 32 bytes by performing BigEndian
606///   encoding of the corresponding (unsigned) integer.
607///
608/// We do not check that the scalar is a canonical Fr element, because the EIP specifies:
609/// * The corresponding integer is not required to be less than or equal than main subgroup order
610///   `q`.
611pub(super) fn read_scalar(input: &[u8]) -> Result<blst_scalar, PrecompileError> {
612    if input.len() != SCALAR_LENGTH {
613        return Err(PrecompileError::Other(format!(
614            "Input should be {SCALAR_LENGTH} bytes, was {}",
615            input.len()
616        )));
617    }
618
619    let mut out = blst_scalar::default();
620    // SAFETY: `input` length is checked previously, out is a blst value.
621    unsafe {
622        // Note: We do not use `blst_scalar_fr_check` here because, from EIP-2537:
623        //
624        // * The corresponding integer is not required to be less than or equal than main subgroup
625        // order `q`.
626        blst_scalar_from_bendian(&mut out, input.as_ptr())
627    };
628
629    Ok(out)
630}
631
632/// Checks if the input is a valid big-endian representation of a field element.
633fn is_valid_be(input: &[u8; 48]) -> bool {
634    *input < MODULUS_REPR
635}