revm_precompile/bn254/
arkworks.rs

1//! BN128 precompile using Arkworks BLS12-381 implementation.
2use super::{FQ2_LEN, FQ_LEN, G1_LEN, SCALAR_LEN};
3use crate::PrecompileError;
4use std::vec::Vec;
5
6use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine};
7use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
8use ark_ff::{One, PrimeField, Zero};
9use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
10
11/// Reads a single `Fq` field element from the input slice.
12///
13/// Takes a byte slice and attempts to interpret the first 32 bytes as an
14/// elliptic curve field element. Returns an error if the bytes do not form
15/// a valid field element.
16///
17/// # Panics
18///
19/// Panics if the input is not at least 32 bytes long.
20#[inline]
21fn read_fq(input_be: &[u8]) -> Result<Fq, PrecompileError> {
22    assert_eq!(input_be.len(), FQ_LEN, "input must be {FQ_LEN} bytes");
23
24    let mut input_le = [0u8; FQ_LEN];
25    input_le.copy_from_slice(input_be);
26
27    // Reverse in-place to convert from big-endian to little-endian.
28    input_le.reverse();
29
30    Fq::deserialize_uncompressed(&input_le[..])
31        .map_err(|_| PrecompileError::Bn254FieldPointNotAMember)
32}
33/// Reads a Fq2 (quadratic extension field element) from the input slice.
34///
35/// Parses two consecutive Fq field elements as the real and imaginary parts
36/// of an Fq2 element.
37/// The second component is parsed before the first, ie if a we represent an
38/// element in Fq2 as (x,y) -- `y` is parsed before `x`
39///
40/// # Panics
41///
42/// Panics if the input is not at least 64 bytes long.
43#[inline]
44fn read_fq2(input: &[u8]) -> Result<Fq2, PrecompileError> {
45    let y = read_fq(&input[..FQ_LEN])?;
46    let x = read_fq(&input[FQ_LEN..2 * FQ_LEN])?;
47
48    Ok(Fq2::new(x, y))
49}
50
51/// Creates a new `G1` point from the given `x` and `y` coordinates.
52///
53/// Constructs a point on the G1 curve from its affine coordinates.
54///
55/// Note: The point at infinity which is represented as (0,0) is
56/// handled specifically because `AffineG1` is not capable of
57/// representing such a point.
58/// In particular, when we convert from `AffineG1` to `G1`, the point
59/// will be (0,0,1) instead of (0,1,0)
60#[inline]
61fn new_g1_point(px: Fq, py: Fq) -> Result<G1Affine, PrecompileError> {
62    if px.is_zero() && py.is_zero() {
63        Ok(G1Affine::zero())
64    } else {
65        // We cannot use `G1Affine::new` because that triggers an assert if the point is not on the curve.
66        let point = G1Affine::new_unchecked(px, py);
67        if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() {
68            return Err(PrecompileError::Bn254AffineGFailedToCreate);
69        }
70        Ok(point)
71    }
72}
73
74/// Creates a new `G2` point from the given Fq2 coordinates.
75///
76/// G2 points in BN254 are defined over a quadratic extension field Fq2.
77/// This function takes two Fq2 elements representing the x and y coordinates
78/// and creates a G2 point.
79///
80/// Note: The point at infinity which is represented as (0,0) is
81/// handled specifically because `AffineG2` is not capable of
82/// representing such a point.
83/// In particular, when we convert from `AffineG2` to `G2`, the point
84/// will be (0,0,1) instead of (0,1,0)
85#[inline]
86fn new_g2_point(x: Fq2, y: Fq2) -> Result<G2Affine, PrecompileError> {
87    let point = if x.is_zero() && y.is_zero() {
88        G2Affine::zero()
89    } else {
90        // We cannot use `G1Affine::new` because that triggers an assert if the point is not on the curve.
91        let point = G2Affine::new_unchecked(x, y);
92        if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() {
93            return Err(PrecompileError::Bn254AffineGFailedToCreate);
94        }
95        point
96    };
97
98    Ok(point)
99}
100
101/// Reads a G1 point from the input slice.
102///
103/// Parses a G1 point from a byte slice by reading two consecutive field elements
104/// representing the x and y coordinates.
105///
106/// # Panics
107///
108/// Panics if the input is not at least 64 bytes long.
109#[inline]
110pub(super) fn read_g1_point(input: &[u8]) -> Result<G1Affine, PrecompileError> {
111    let px = read_fq(&input[0..FQ_LEN])?;
112    let py = read_fq(&input[FQ_LEN..2 * FQ_LEN])?;
113    new_g1_point(px, py)
114}
115
116/// Encodes a G1 point into a byte array.
117///
118/// Converts a G1 point in Jacobian coordinates to affine coordinates and
119/// serializes the x and y coordinates as big-endian byte arrays.
120///
121/// Note: If the point is the point at infinity, this function returns
122/// all zeroes.
123#[inline]
124pub(super) fn encode_g1_point(point: G1Affine) -> [u8; G1_LEN] {
125    let mut output = [0u8; G1_LEN];
126    let Some((x, y)) = point.xy() else {
127        return output;
128    };
129
130    let mut x_bytes = [0u8; FQ_LEN];
131    x.serialize_uncompressed(&mut x_bytes[..])
132        .expect("Failed to serialize x coordinate");
133
134    let mut y_bytes = [0u8; FQ_LEN];
135    y.serialize_uncompressed(&mut y_bytes[..])
136        .expect("Failed to serialize x coordinate");
137
138    // Convert to big endian by reversing the bytes.
139    x_bytes.reverse();
140    y_bytes.reverse();
141
142    // Place x in the first half, y in the second half.
143    output[0..FQ_LEN].copy_from_slice(&x_bytes);
144    output[FQ_LEN..(FQ_LEN * 2)].copy_from_slice(&y_bytes);
145
146    output
147}
148
149/// Reads a G2 point from the input slice.
150///
151/// Parses a G2 point from a byte slice by reading four consecutive Fq field elements
152/// representing the two Fq2 coordinates (x and y) of the G2 point.
153///
154/// # Panics
155///
156/// Panics if the input is not at least 128 bytes long.
157#[inline]
158pub(super) fn read_g2_point(input: &[u8]) -> Result<G2Affine, PrecompileError> {
159    let ba = read_fq2(&input[0..FQ2_LEN])?;
160    let bb = read_fq2(&input[FQ2_LEN..2 * FQ2_LEN])?;
161    new_g2_point(ba, bb)
162}
163
164/// Reads a scalar from the input slice
165///
166/// Note: The scalar does not need to be canonical.
167///
168/// # Panics
169///
170/// If `input.len()` is not equal to [`SCALAR_LEN`].
171#[inline]
172pub(super) fn read_scalar(input: &[u8]) -> Fr {
173    assert_eq!(
174        input.len(),
175        SCALAR_LEN,
176        "unexpected scalar length. got {}, expected {SCALAR_LEN}",
177        input.len()
178    );
179    Fr::from_be_bytes_mod_order(input)
180}
181
182/// Performs point addition on two G1 points.
183#[inline]
184pub(crate) fn g1_point_add(p1_bytes: &[u8], p2_bytes: &[u8]) -> Result<[u8; 64], PrecompileError> {
185    let p1 = read_g1_point(p1_bytes)?;
186    let p2 = read_g1_point(p2_bytes)?;
187
188    let p1_jacobian: G1Projective = p1.into();
189
190    let p3 = p1_jacobian + p2;
191    let output = encode_g1_point(p3.into_affine());
192
193    Ok(output)
194}
195
196/// Performs a G1 scalar multiplication.
197#[inline]
198pub(crate) fn g1_point_mul(
199    point_bytes: &[u8],
200    fr_bytes: &[u8],
201) -> Result<[u8; 64], PrecompileError> {
202    let p = read_g1_point(point_bytes)?;
203    let fr = read_scalar(fr_bytes);
204
205    let big_int = fr.into_bigint();
206    let result = p.mul_bigint(big_int);
207
208    let output = encode_g1_point(result.into_affine());
209
210    Ok(output)
211}
212
213/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and
214/// returns true if the result is equal to the identity element.
215///
216/// Note: If the input is empty, this function returns true.
217/// This is different to EIP2537 which disallows the empty input.
218#[inline]
219pub(crate) fn pairing_check(pairs: &[(&[u8], &[u8])]) -> Result<bool, PrecompileError> {
220    let mut g1_points = Vec::with_capacity(pairs.len());
221    let mut g2_points = Vec::with_capacity(pairs.len());
222
223    for (g1_bytes, g2_bytes) in pairs {
224        let g1 = read_g1_point(g1_bytes)?;
225        let g2 = read_g2_point(g2_bytes)?;
226
227        // Skip pairs where either point is at infinity
228        if !g1.is_zero() && !g2.is_zero() {
229            g1_points.push(g1);
230            g2_points.push(g2);
231        }
232    }
233
234    if g1_points.is_empty() {
235        return Ok(true);
236    }
237
238    let pairing_result = Bn254::multi_pairing(&g1_points, &g2_points);
239    Ok(pairing_result.0.is_one())
240}