Skip to main content

revm_precompile/bn254/
arkworks.rs

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