solana_bn254/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2pub(crate) mod addition;
3pub mod compression;
4pub(crate) mod multiplication;
5pub(crate) mod pairing;
6
7/// This module contains the versioned syscall implementations and is intended for use
8/// primarily by validator code.
9#[cfg(not(target_os = "solana"))]
10pub mod versioned {
11    pub use crate::{
12        addition::{
13            alt_bn128_versioned_g1_addition, alt_bn128_versioned_g2_addition, VersionedG1Addition,
14            VersionedG2Addition, ALT_BN128_G1_ADDITION_INPUT_SIZE, ALT_BN128_G1_ADD_BE,
15            ALT_BN128_G1_ADD_LE, ALT_BN128_G1_SUB_BE, ALT_BN128_G1_SUB_LE,
16            ALT_BN128_G2_ADDITION_INPUT_SIZE, ALT_BN128_G2_ADD_BE, ALT_BN128_G2_ADD_LE,
17            ALT_BN128_G2_SUB_BE, ALT_BN128_G2_SUB_LE,
18        },
19        consts::*,
20        multiplication::{
21            alt_bn128_versioned_g1_multiplication, alt_bn128_versioned_g2_multiplication,
22            VersionedG1Multiplication, VersionedG2Multiplication,
23            ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE, ALT_BN128_G1_MUL_BE, ALT_BN128_G1_MUL_LE,
24            ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE, ALT_BN128_G2_MUL_BE, ALT_BN128_G2_MUL_LE,
25        },
26        pairing::{
27            alt_bn128_versioned_pairing, VersionedPairing, ALT_BN128_PAIRING_BE,
28            ALT_BN128_PAIRING_ELEMENT_SIZE, ALT_BN128_PAIRING_LE, ALT_BN128_PAIRING_OUTPUT_SIZE,
29        },
30        target_arch::Endianness,
31    };
32    #[allow(deprecated)]
33    pub use crate::{
34        addition::{
35            ALT_BN128_ADD, ALT_BN128_ADDITION_INPUT_LEN, ALT_BN128_ADDITION_INPUT_SIZE,
36            ALT_BN128_ADDITION_OUTPUT_LEN, ALT_BN128_ADDITION_OUTPUT_SIZE, ALT_BN128_SUB,
37        },
38        multiplication::{
39            ALT_BN128_MUL, ALT_BN128_MULTIPLICATION_INPUT_LEN, ALT_BN128_MULTIPLICATION_INPUT_SIZE,
40            ALT_BN128_MULTIPLICATION_OUTPUT_LEN, ALT_BN128_MULTIPLICATION_OUTPUT_SIZE,
41        },
42        pairing::{ALT_BN128_PAIRING, ALT_BN128_PAIRING_ELEMENT_LEN, ALT_BN128_PAIRING_OUTPUT_LEN},
43    };
44}
45
46/// This module should be used by Solana programs or other downstream projects.
47pub mod prelude {
48    #[allow(deprecated)]
49    #[cfg(not(target_os = "solana"))]
50    pub use crate::multiplication::alt_bn128_multiplication_128; // to be removed in v4.0
51    #[allow(deprecated)]
52    pub use crate::{
53        addition::{
54            alt_bn128_addition, ALT_BN128_ADD, ALT_BN128_ADDITION_INPUT_LEN,
55            ALT_BN128_ADDITION_INPUT_SIZE, ALT_BN128_ADDITION_OUTPUT_LEN,
56            ALT_BN128_ADDITION_OUTPUT_SIZE, ALT_BN128_SUB,
57        },
58        multiplication::{
59            alt_bn128_multiplication, ALT_BN128_MUL, ALT_BN128_MULTIPLICATION_INPUT_LEN,
60            ALT_BN128_MULTIPLICATION_INPUT_SIZE, ALT_BN128_MULTIPLICATION_OUTPUT_LEN,
61            ALT_BN128_MULTIPLICATION_OUTPUT_SIZE,
62        },
63        pairing::{
64            alt_bn128_pairing, ALT_BN128_PAIRING, ALT_BN128_PAIRING_ELEMENT_LEN,
65            ALT_BN128_PAIRING_OUTPUT_LEN,
66        },
67    };
68    pub use crate::{
69        addition::{
70            alt_bn128_g1_addition_be, alt_bn128_g1_addition_le, alt_bn128_g2_addition_be,
71            alt_bn128_g2_addition_le, ALT_BN128_G1_ADDITION_INPUT_SIZE, ALT_BN128_G1_ADD_BE,
72            ALT_BN128_G1_ADD_LE, ALT_BN128_G1_SUB_BE, ALT_BN128_G1_SUB_LE,
73            ALT_BN128_G2_ADDITION_INPUT_SIZE, ALT_BN128_G2_ADD_BE, ALT_BN128_G2_ADD_LE,
74            ALT_BN128_G2_SUB_BE, ALT_BN128_G2_SUB_LE,
75        },
76        consts::*,
77        multiplication::{
78            alt_bn128_g1_multiplication_be, alt_bn128_g1_multiplication_le,
79            alt_bn128_g2_multiplication_be, alt_bn128_g2_multiplication_le,
80            ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE, ALT_BN128_G1_MUL_BE, ALT_BN128_G1_MUL_LE,
81            ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE, ALT_BN128_G2_MUL_BE, ALT_BN128_G2_MUL_LE,
82        },
83        pairing::{
84            alt_bn128_pairing_be, alt_bn128_pairing_le, ALT_BN128_PAIRING_BE,
85            ALT_BN128_PAIRING_ELEMENT_SIZE, ALT_BN128_PAIRING_LE, ALT_BN128_PAIRING_OUTPUT_SIZE,
86        },
87        AltBn128Error,
88    };
89}
90
91#[cfg(not(target_os = "solana"))]
92use bytemuck::{Pod, Zeroable};
93use thiserror::Error;
94
95mod consts {
96    /// Size of the EC point field, in bytes.
97    pub const ALT_BN128_FIELD_SIZE: usize = 32;
98
99    /// Size of the extension field element (Fq2), in bytes.
100    pub const ALT_BN128_FQ2_SIZE: usize = ALT_BN128_FIELD_SIZE * 2;
101
102    /// Size of the EC point. `alt_bn128` point contains
103    /// the consistently united x and y fields as 64 bytes.
104    pub const ALT_BN128_G1_POINT_SIZE: usize = ALT_BN128_FIELD_SIZE * 2;
105
106    #[deprecated(since = "3.1.0", note = "Please use `ALT_BN128_G1_POINT_SIZE` instead")]
107    pub const ALT_BN128_POINT_SIZE: usize = ALT_BN128_G1_POINT_SIZE;
108
109    /// Elements in G2 is represented by 2 field-extension elements `(x, y)`.
110    pub const ALT_BN128_G2_POINT_SIZE: usize = ALT_BN128_FQ2_SIZE * 2;
111}
112
113// AltBn128Error must be removed once the
114// simplify_alt_bn128_syscall_error_codes feature gets activated
115#[derive(Debug, Error, Clone, PartialEq, Eq)]
116pub enum AltBn128Error {
117    #[error("The input data is invalid")]
118    InvalidInputData,
119    #[error("Invalid group data")]
120    GroupError,
121    #[error("Slice data is going out of input data bounds")]
122    SliceOutOfBounds,
123    #[error("Unexpected error")]
124    UnexpectedError,
125    #[error("Failed to convert a byte slice into a vector {0:?}")]
126    TryIntoVecError(Vec<u8>),
127    #[error("Failed to convert projective to affine g1")]
128    ProjectiveToG1Failed,
129}
130
131impl From<u64> for AltBn128Error {
132    fn from(v: u64) -> AltBn128Error {
133        match v {
134            1 => AltBn128Error::InvalidInputData,
135            2 => AltBn128Error::GroupError,
136            3 => AltBn128Error::SliceOutOfBounds,
137            4 => AltBn128Error::TryIntoVecError(Vec::new()),
138            5 => AltBn128Error::ProjectiveToG1Failed,
139            _ => AltBn128Error::UnexpectedError,
140        }
141    }
142}
143
144impl From<AltBn128Error> for u64 {
145    fn from(v: AltBn128Error) -> u64 {
146        // note: should never return 0, as it risks to be confused with syscall success
147        match v {
148            AltBn128Error::InvalidInputData => 1,
149            AltBn128Error::GroupError => 2,
150            AltBn128Error::SliceOutOfBounds => 3,
151            AltBn128Error::TryIntoVecError(_) => 4,
152            AltBn128Error::ProjectiveToG1Failed => 5,
153            AltBn128Error::UnexpectedError => 6,
154        }
155    }
156}
157
158#[cfg(not(target_os = "solana"))]
159use consts::{
160    ALT_BN128_FIELD_SIZE as FIELD_SIZE, ALT_BN128_FQ2_SIZE as FQ2_SIZE,
161    ALT_BN128_G1_POINT_SIZE as G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE as G2_POINT_SIZE,
162};
163
164/// A bitmask used to indicate that an operation's input data is little-endian.
165pub(crate) const LE_FLAG: u64 = 0x80;
166
167/// The BN254 (BN128) group element in G1 as a POD type.
168///
169/// A group element in G1 consists of two field elements `(x, y)`. A `PodG1`
170/// type expects a group element to be encoded as `[le(x), le(y)]` where
171/// `le(..)` is the little-endian encoding of the input field element as used
172/// in the `ark-bn254` crate. Note that this differs from the EIP-197 standard,
173/// which specifies that the field elements are encoded as big-endian.
174///
175/// `PodG1` can be constructed from both big-endian (EIP-197) and little-endian
176/// (ark-bn254) encodings using `from_be_bytes` and `from_le_bytes` methods,
177/// respectively.
178#[cfg(not(target_os = "solana"))]
179#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
180#[repr(transparent)]
181pub struct PodG1(pub [u8; G1_POINT_SIZE]);
182
183/// The BN254 (BN128) group element in G2 as a POD type.
184///
185/// Elements in G2 is represented by 2 field-extension elements `(x, y)`. Each
186/// field-extension element itself is a degree 1 polynomial `x = x0 + x1*X`,
187/// `y = y0 + y1*X`. The EIP-197 standard encodes a G2 element as
188/// `[be(x1), be(x0), be(y1), be(y0)]` where `be(..)` is the big-endian
189/// encoding of the input field element. The `ark-bn254` crate encodes a G2
190/// element as `[le(x0), le(x1), le(y0), le(y1)]` where `le(..)` is the
191/// little-endian encoding of the input field element. Notably, in addition to
192/// the differences in the big-endian vs. little-endian encodings of field
193/// elements, the order of the polynomial field coefficients `x0`, `x1`, `y0`,
194/// and `y1` are different.
195///
196/// `PodG2` can be constructed from both big-endian (EIP-197) and little-endian
197/// (ark-bn254) encodings using `from_be_bytes` and `from_le_bytes` methods,
198/// respectively.
199#[cfg(not(target_os = "solana"))]
200#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
201#[repr(transparent)]
202pub struct PodG2(pub [u8; G2_POINT_SIZE]);
203
204#[cfg(not(target_os = "solana"))]
205mod target_arch {
206    use {
207        super::*,
208        ark_ec::{self, AffineRepr},
209        ark_serialize::{CanonicalDeserialize, Compress, Validate},
210    };
211
212    pub(crate) type G1 = ark_bn254::g1::G1Affine;
213    pub(crate) type G2 = ark_bn254::g2::G2Affine;
214
215    impl PodG1 {
216        /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G1 and constructs a
217        /// `PodG1` struct that encodes the same bytes in little-endian.
218        pub(crate) fn from_be_bytes(be_bytes: &[u8]) -> Result<Self, AltBn128Error> {
219            let pod_bytes = convert_endianness::<FIELD_SIZE, G1_POINT_SIZE>(
220                be_bytes
221                    .try_into()
222                    .map_err(|_| AltBn128Error::SliceOutOfBounds)?,
223            );
224            Ok(Self(pod_bytes))
225        }
226
227        /// Takes in a little-endian byte encoding of a group element in G1 and constructs a
228        /// `PodG1` struct that encodes the same bytes internally.
229        #[inline(always)]
230        pub(crate) fn from_le_bytes(le_bytes: &[u8]) -> Result<Self, AltBn128Error> {
231            Ok(Self(
232                le_bytes
233                    .try_into()
234                    .map_err(|_| AltBn128Error::SliceOutOfBounds)?,
235            ))
236        }
237    }
238
239    impl PodG2 {
240        /// Deserializes to an affine point in G2.
241        /// This function performs the curve equation check, but skips the subgroup check.
242        pub(crate) fn into_affine_unchecked(self) -> Result<G2, AltBn128Error> {
243            if self.0 == [0u8; 128] {
244                return Ok(G2::zero());
245            }
246
247            // Skips the expensive subgroup check
248            let g2 = G2::deserialize_with_mode(
249                &*[&self.0[..], &[0u8][..]].concat(),
250                Compress::No,
251                Validate::No,
252            )
253            .map_err(|_| AltBn128Error::InvalidInputData)?;
254
255            // Still check if point is on the curve
256            if !g2.is_on_curve() {
257                return Err(AltBn128Error::GroupError);
258            }
259
260            Ok(g2)
261        }
262
263        /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G2
264        /// and constructs a `PodG2` struct that encodes the same bytes in
265        /// little-endian.
266        pub(crate) fn from_be_bytes(be_bytes: &[u8]) -> Result<Self, AltBn128Error> {
267            let pod_bytes = convert_endianness::<FQ2_SIZE, G2_POINT_SIZE>(
268                be_bytes
269                    .try_into()
270                    .map_err(|_| AltBn128Error::SliceOutOfBounds)?,
271            );
272            Ok(Self(pod_bytes))
273        }
274
275        /// Takes in a little-endian byte encoding of a group element in G2 and constructs a
276        /// `PodG2` struct that encodes the same bytes internally.
277        #[inline(always)]
278        pub(crate) fn from_le_bytes(le_bytes: &[u8]) -> Result<Self, AltBn128Error> {
279            Ok(Self(
280                le_bytes
281                    .try_into()
282                    .map_err(|_| AltBn128Error::SliceOutOfBounds)?,
283            ))
284        }
285    }
286
287    impl TryFrom<PodG1> for G1 {
288        type Error = AltBn128Error;
289
290        fn try_from(bytes: PodG1) -> Result<Self, Self::Error> {
291            if bytes.0 == [0u8; 64] {
292                return Ok(G1::zero());
293            }
294            let g1 = Self::deserialize_with_mode(
295                &*[&bytes.0[..], &[0u8][..]].concat(),
296                Compress::No,
297                Validate::Yes,
298            );
299
300            match g1 {
301                Ok(g1) => {
302                    if !g1.is_on_curve() {
303                        Err(AltBn128Error::GroupError)
304                    } else {
305                        Ok(g1)
306                    }
307                }
308                Err(_) => Err(AltBn128Error::InvalidInputData),
309            }
310        }
311    }
312
313    impl TryFrom<PodG2> for G2 {
314        type Error = AltBn128Error;
315
316        fn try_from(bytes: PodG2) -> Result<Self, Self::Error> {
317            if bytes.0 == [0u8; 128] {
318                return Ok(G2::zero());
319            }
320            let g2 = Self::deserialize_with_mode(
321                &*[&bytes.0[..], &[0u8][..]].concat(),
322                Compress::No,
323                Validate::Yes,
324            );
325
326            match g2 {
327                Ok(g2) => {
328                    if !g2.is_on_curve() {
329                        Err(AltBn128Error::GroupError)
330                    } else {
331                        Ok(g2)
332                    }
333                }
334                Err(_) => Err(AltBn128Error::InvalidInputData),
335            }
336        }
337    }
338
339    pub enum Endianness {
340        BE,
341        LE,
342    }
343
344    /// This function converts between big-endian and little-endian formats.
345    /// It splits the input byte array of size `ARRAY_SIZE` into chunks of `CHUNK_SIZE`
346    /// and reverses the byte order within each chunk.
347    /// Typical use cases:
348    /// - convert_endianness::<32, 64>  to convert G1 points
349    /// - convert_endianness::<64, 128> to convert G2 points
350    /// - convert_endianness::<32, 32>  to convert scalars
351    pub fn convert_endianness<const CHUNK_SIZE: usize, const ARRAY_SIZE: usize>(
352        bytes: &[u8; ARRAY_SIZE],
353    ) -> [u8; ARRAY_SIZE] {
354        let reversed: [_; ARRAY_SIZE] = bytes
355            .chunks_exact(CHUNK_SIZE)
356            .flat_map(|chunk| chunk.iter().rev().copied())
357            .enumerate()
358            .fold([0u8; ARRAY_SIZE], |mut acc, (i, v)| {
359                acc[i] = v;
360                acc
361            });
362        reversed
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use {
369        crate::{prelude::*, PodG1},
370        ark_bn254::g1::G1Affine,
371        ark_ec::AffineRepr,
372        ark_serialize::{CanonicalSerialize, Compress},
373    };
374
375    #[test]
376    fn zero_serialization_test() {
377        let zero = G1Affine::zero();
378        let mut result_point_data = [0u8; 64];
379        zero.x
380            .serialize_with_mode(&mut result_point_data[..32], Compress::No)
381            .map_err(|_| AltBn128Error::InvalidInputData)
382            .unwrap();
383        zero.y
384            .serialize_with_mode(&mut result_point_data[32..], Compress::No)
385            .map_err(|_| AltBn128Error::InvalidInputData)
386            .unwrap();
387        assert_eq!(result_point_data, [0u8; 64]);
388
389        let p: G1Affine = PodG1(result_point_data[..64].try_into().unwrap())
390            .try_into()
391            .unwrap();
392        assert_eq!(p, zero);
393    }
394}