trezoa_bn254/
lib.rs

1pub mod compression;
2pub mod prelude {
3    pub use crate::{consts::*, target_arch::*, AltBn128Error};
4}
5
6use {
7    bytemuck::{Pod, Zeroable},
8    consts::*,
9    thiserror::Error,
10};
11
12mod consts {
13    /// Input length for the add operation.
14    pub const ALT_BN128_ADDITION_INPUT_LEN: usize = 128;
15
16    /// Input length for the multiplication operation.
17    pub const ALT_BN128_MULTIPLICATION_INPUT_LEN: usize = 96;
18
19    /// Pair element length.
20    pub const ALT_BN128_PAIRING_ELEMENT_LEN: usize = 192;
21
22    /// Output length for the add operation.
23    pub const ALT_BN128_ADDITION_OUTPUT_LEN: usize = 64;
24
25    /// Output length for the multiplication operation.
26    pub const ALT_BN128_MULTIPLICATION_OUTPUT_LEN: usize = 64;
27
28    /// Output length for pairing operation.
29    pub const ALT_BN128_PAIRING_OUTPUT_LEN: usize = 32;
30
31    /// Size of the EC point field, in bytes.
32    pub const ALT_BN128_FIELD_SIZE: usize = 32;
33
34    /// Size of the EC point. `alt_bn128` point contains
35    /// the consistently united x and y fields as 64 bytes.
36    pub const ALT_BN128_POINT_SIZE: usize = 64;
37
38    pub const ALT_BN128_ADD: u64 = 0;
39    pub const ALT_BN128_SUB: u64 = 1;
40    pub const ALT_BN128_MUL: u64 = 2;
41    pub const ALT_BN128_PAIRING: u64 = 3;
42}
43
44// AltBn128Error must be removed once the
45// simplify_alt_bn128_syscall_error_codes feature gets activated
46#[derive(Debug, Error, Clone, PartialEq, Eq)]
47pub enum AltBn128Error {
48    #[error("The input data is invalid")]
49    InvalidInputData,
50    #[error("Invalid group data")]
51    GroupError,
52    #[error("Slice data is going out of input data bounds")]
53    SliceOutOfBounds,
54    #[error("Unexpected error")]
55    UnexpectedError,
56    #[error("Failed to convert a byte slice into a vector {0:?}")]
57    TryIntoVecError(Vec<u8>),
58    #[error("Failed to convert projective to affine g1")]
59    ProjectiveToG1Failed,
60}
61
62impl From<u64> for AltBn128Error {
63    fn from(v: u64) -> AltBn128Error {
64        match v {
65            1 => AltBn128Error::InvalidInputData,
66            2 => AltBn128Error::GroupError,
67            3 => AltBn128Error::SliceOutOfBounds,
68            4 => AltBn128Error::TryIntoVecError(Vec::new()),
69            5 => AltBn128Error::ProjectiveToG1Failed,
70            _ => AltBn128Error::UnexpectedError,
71        }
72    }
73}
74
75impl From<AltBn128Error> for u64 {
76    fn from(v: AltBn128Error) -> u64 {
77        // note: should never return 0, as it risks to be confused with syscall success
78        match v {
79            AltBn128Error::InvalidInputData => 1,
80            AltBn128Error::GroupError => 2,
81            AltBn128Error::SliceOutOfBounds => 3,
82            AltBn128Error::TryIntoVecError(_) => 4,
83            AltBn128Error::ProjectiveToG1Failed => 5,
84            AltBn128Error::UnexpectedError => 6,
85        }
86    }
87}
88
89use consts::{ALT_BN128_FIELD_SIZE as FIELD_SIZE, ALT_BN128_POINT_SIZE as G1_POINT_SIZE};
90
91/// The BN254 (BN128) group element in G1 as a POD type.
92///
93/// A group element in G1 consists of two field elements `(x, y)`. A `PodG1`
94/// type expects a group element to be encoded as `[le(x), le(y)]` where
95/// `le(..)` is the little-endian encoding of the input field element as used
96/// in the `ark-bn254` crate. Note that this differs from the EIP-197 standard,
97/// which specifies that the field elements are encoded as big-endian.
98///
99/// The Trezoa syscalls still expect the inputs to be encoded in big-endian as
100/// specified in EIP-197. The type `PodG1` is an intermediate type that
101/// facilitates the translation between the EIP-197 encoding and the arkworks
102/// implementation encoding.
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
104#[repr(transparent)]
105pub struct PodG1(pub [u8; G1_POINT_SIZE]);
106
107const G2_POINT_SIZE: usize = FIELD_SIZE * 4;
108
109/// The BN254 (BN128) group element in G2 as a POD type.
110///
111/// Elements in G2 is represented by 2 field-extension elements `(x, y)`. Each
112/// field-extension element itself is a degree 1 polynomial `x = x0 + x1*X`,
113/// `y = y0 + y1*X`. The EIP-197 standard encodes a G2 element as
114/// `[be(x1), be(x0), be(y1), be(y0)]` where `be(..)` is the big-endian
115/// encoding of the input field element. The `ark-bn254` crate encodes a G2
116/// element as `[le(x0), le(x1), le(y0), le(y1)]` where `le(..)` is the
117/// little-endian encoding of the input field element. Notably, in addition to
118/// the differences in the big-endian vs. little-endian encodings of field
119/// elements, the order of the polynomial field coefficients `x0`, `x1`, `y0`,
120/// and `y1` are different.
121///
122/// THe Trezoa syscalls still expect the inputs to be encoded as specified in
123/// EIP-197. The type `PodG2` is an intermediate type that facilitates the
124/// translation between the `EIP-197 encoding and the encoding used in the
125/// arkworks implementation.
126#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
127#[repr(transparent)]
128pub struct PodG2(pub [u8; G2_POINT_SIZE]);
129
130#[cfg(not(target_os = "trezoa"))]
131mod target_arch {
132    use {
133        super::*,
134        ark_bn254::{self, Config},
135        ark_ec::{self, models::bn::Bn, pairing::Pairing, AffineRepr},
136        ark_ff::{BigInteger, BigInteger256, One},
137        ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate},
138    };
139
140    type G1 = ark_bn254::g1::G1Affine;
141    type G2 = ark_bn254::g2::G2Affine;
142
143    impl PodG1 {
144        /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G1 and constructs a
145        /// `PodG1` struct that encodes the same bytes in little-endian.
146        fn from_be_bytes(be_bytes: &[u8]) -> Result<Self, AltBn128Error> {
147            if be_bytes.len() != G1_POINT_SIZE {
148                return Err(AltBn128Error::SliceOutOfBounds);
149            }
150            let mut pod_bytes = [0u8; G1_POINT_SIZE];
151            reverse_copy(&be_bytes[..FIELD_SIZE], &mut pod_bytes[..FIELD_SIZE])?;
152            reverse_copy(&be_bytes[FIELD_SIZE..], &mut pod_bytes[FIELD_SIZE..])?;
153            Ok(Self(pod_bytes))
154        }
155    }
156
157    impl PodG2 {
158        /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G2
159        /// and constructs a `PodG2` struct that encodes the same bytes in
160        /// little-endian.
161        fn from_be_bytes(be_bytes: &[u8]) -> Result<Self, AltBn128Error> {
162            if be_bytes.len() != G2_POINT_SIZE {
163                return Err(AltBn128Error::SliceOutOfBounds);
164            }
165            // note the cross order
166            const SOURCE_X1_INDEX: usize = 0;
167            const SOURCE_X0_INDEX: usize = SOURCE_X1_INDEX.saturating_add(FIELD_SIZE);
168            const SOURCE_Y1_INDEX: usize = SOURCE_X0_INDEX.saturating_add(FIELD_SIZE);
169            const SOURCE_Y0_INDEX: usize = SOURCE_Y1_INDEX.saturating_add(FIELD_SIZE);
170
171            const TARGET_X0_INDEX: usize = 0;
172            const TARGET_X1_INDEX: usize = TARGET_X0_INDEX.saturating_add(FIELD_SIZE);
173            const TARGET_Y0_INDEX: usize = TARGET_X1_INDEX.saturating_add(FIELD_SIZE);
174            const TARGET_Y1_INDEX: usize = TARGET_Y0_INDEX.saturating_add(FIELD_SIZE);
175
176            let mut pod_bytes = [0u8; G2_POINT_SIZE];
177            reverse_copy(
178                &be_bytes[SOURCE_X1_INDEX..SOURCE_X1_INDEX.saturating_add(FIELD_SIZE)],
179                &mut pod_bytes[TARGET_X1_INDEX..TARGET_X1_INDEX.saturating_add(FIELD_SIZE)],
180            )?;
181            reverse_copy(
182                &be_bytes[SOURCE_X0_INDEX..SOURCE_X0_INDEX.saturating_add(FIELD_SIZE)],
183                &mut pod_bytes[TARGET_X0_INDEX..TARGET_X0_INDEX.saturating_add(FIELD_SIZE)],
184            )?;
185            reverse_copy(
186                &be_bytes[SOURCE_Y1_INDEX..SOURCE_Y1_INDEX.saturating_add(FIELD_SIZE)],
187                &mut pod_bytes[TARGET_Y1_INDEX..TARGET_Y1_INDEX.saturating_add(FIELD_SIZE)],
188            )?;
189            reverse_copy(
190                &be_bytes[SOURCE_Y0_INDEX..SOURCE_Y0_INDEX.saturating_add(FIELD_SIZE)],
191                &mut pod_bytes[TARGET_Y0_INDEX..TARGET_Y0_INDEX.saturating_add(FIELD_SIZE)],
192            )?;
193            Ok(Self(pod_bytes))
194        }
195    }
196
197    impl TryFrom<PodG1> for G1 {
198        type Error = AltBn128Error;
199
200        fn try_from(bytes: PodG1) -> Result<Self, Self::Error> {
201            if bytes.0 == [0u8; 64] {
202                return Ok(G1::zero());
203            }
204            let g1 = Self::deserialize_with_mode(
205                &*[&bytes.0[..], &[0u8][..]].concat(),
206                Compress::No,
207                Validate::Yes,
208            );
209
210            match g1 {
211                Ok(g1) => {
212                    if !g1.is_on_curve() {
213                        Err(AltBn128Error::GroupError)
214                    } else {
215                        Ok(g1)
216                    }
217                }
218                Err(_) => Err(AltBn128Error::InvalidInputData),
219            }
220        }
221    }
222
223    impl TryFrom<PodG2> for G2 {
224        type Error = AltBn128Error;
225
226        fn try_from(bytes: PodG2) -> Result<Self, Self::Error> {
227            if bytes.0 == [0u8; 128] {
228                return Ok(G2::zero());
229            }
230            let g2 = Self::deserialize_with_mode(
231                &*[&bytes.0[..], &[0u8][..]].concat(),
232                Compress::No,
233                Validate::Yes,
234            );
235
236            match g2 {
237                Ok(g2) => {
238                    if !g2.is_on_curve() {
239                        Err(AltBn128Error::GroupError)
240                    } else {
241                        Ok(g2)
242                    }
243                }
244                Err(_) => Err(AltBn128Error::InvalidInputData),
245            }
246        }
247    }
248
249    pub fn alt_bn128_addition(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
250        if input.len() > ALT_BN128_ADDITION_INPUT_LEN {
251            return Err(AltBn128Error::InvalidInputData);
252        }
253
254        let mut input = input.to_vec();
255        input.resize(ALT_BN128_ADDITION_INPUT_LEN, 0);
256
257        let p: G1 = PodG1::from_be_bytes(&input[..64])?.try_into()?;
258        let q: G1 = PodG1::from_be_bytes(&input[64..ALT_BN128_ADDITION_INPUT_LEN])?.try_into()?;
259
260        #[allow(clippy::arithmetic_side_effects)]
261        let result_point = p + q;
262
263        let mut result_point_data = [0u8; ALT_BN128_ADDITION_OUTPUT_LEN];
264        let result_point_affine: G1 = result_point.into();
265        result_point_affine
266            .x
267            .serialize_with_mode(&mut result_point_data[..32], Compress::No)
268            .map_err(|_| AltBn128Error::InvalidInputData)?;
269        result_point_affine
270            .y
271            .serialize_with_mode(&mut result_point_data[32..], Compress::No)
272            .map_err(|_| AltBn128Error::InvalidInputData)?;
273
274        Ok(convert_endianness_64(&result_point_data[..]))
275    }
276
277    pub fn alt_bn128_multiplication(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
278        alt_bn128_apply_multiplication(input, ALT_BN128_MULTIPLICATION_INPUT_LEN)
279    }
280
281    pub fn alt_bn128_multiplication_128(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
282        alt_bn128_apply_multiplication(input, 128) // hard-code length; we will remove this function in the future
283    }
284
285    fn alt_bn128_apply_multiplication(
286        input: &[u8],
287        expected_length: usize,
288    ) -> Result<Vec<u8>, AltBn128Error> {
289        if input.len() > expected_length {
290            return Err(AltBn128Error::InvalidInputData);
291        }
292
293        let mut input = input.to_vec();
294        input.resize(expected_length, 0);
295
296        let p: G1 = PodG1::from_be_bytes(&input[..64])?.try_into()?;
297        let mut fr_bytes = [0u8; 32];
298        reverse_copy(&input[64..96], &mut fr_bytes)?;
299        let fr = BigInteger256::deserialize_uncompressed_unchecked(fr_bytes.as_slice())
300            .map_err(|_| AltBn128Error::InvalidInputData)?;
301
302        let result_point: G1 = p.mul_bigint(fr).into();
303
304        let mut result_point_data = [0u8; ALT_BN128_MULTIPLICATION_OUTPUT_LEN];
305
306        result_point
307            .x
308            .serialize_with_mode(&mut result_point_data[..32], Compress::No)
309            .map_err(|_| AltBn128Error::InvalidInputData)?;
310        result_point
311            .y
312            .serialize_with_mode(&mut result_point_data[32..], Compress::No)
313            .map_err(|_| AltBn128Error::InvalidInputData)?;
314
315        Ok(convert_endianness_64(
316            &result_point_data[..ALT_BN128_MULTIPLICATION_OUTPUT_LEN],
317        ))
318    }
319
320    pub fn alt_bn128_pairing(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
321        if input
322            .len()
323            .checked_rem(consts::ALT_BN128_PAIRING_ELEMENT_LEN)
324            .is_none()
325        {
326            return Err(AltBn128Error::InvalidInputData);
327        }
328
329        let ele_len = input.len().saturating_div(ALT_BN128_PAIRING_ELEMENT_LEN);
330
331        let mut vec_pairs: Vec<(G1, G2)> = Vec::with_capacity(ele_len);
332        for chunk in input.chunks(ALT_BN128_PAIRING_ELEMENT_LEN).take(ele_len) {
333            let (p_bytes, q_bytes) = chunk.split_at(G1_POINT_SIZE);
334
335            let g1 = PodG1::from_be_bytes(p_bytes)?.try_into()?;
336            let g2 = PodG2::from_be_bytes(q_bytes)?.try_into()?;
337
338            vec_pairs.push((g1, g2));
339        }
340
341        let mut result = BigInteger256::from(0u64);
342        let res = <Bn<Config> as Pairing>::multi_pairing(
343            vec_pairs.iter().map(|pair| pair.0),
344            vec_pairs.iter().map(|pair| pair.1),
345        );
346
347        if res.0 == ark_bn254::Fq12::one() {
348            result = BigInteger256::from(1u64);
349        }
350
351        let output = result.to_bytes_be();
352        Ok(output)
353    }
354
355    fn convert_endianness_64(bytes: &[u8]) -> Vec<u8> {
356        bytes
357            .chunks(32)
358            .flat_map(|b| b.iter().copied().rev().collect::<Vec<u8>>())
359            .collect::<Vec<u8>>()
360    }
361
362    /// Copies a `source` byte slice into a `destination` byte slice in reverse order.
363    fn reverse_copy(source: &[u8], destination: &mut [u8]) -> Result<(), AltBn128Error> {
364        if source.len() != destination.len() {
365            return Err(AltBn128Error::SliceOutOfBounds);
366        }
367        for (source_index, destination_index) in source.iter().rev().zip(destination.iter_mut()) {
368            *destination_index = *source_index;
369        }
370        Ok(())
371    }
372}
373
374#[cfg(target_os = "trezoa")]
375mod target_arch {
376    use {super::*, trezoa_define_syscall::definitions as syscalls};
377
378    pub fn alt_bn128_addition(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
379        if input.len() > ALT_BN128_ADDITION_INPUT_LEN {
380            return Err(AltBn128Error::InvalidInputData);
381        }
382        let mut result_buffer = [0; ALT_BN128_ADDITION_OUTPUT_LEN];
383        let result = unsafe {
384            syscalls::trz_alt_bn128_group_op(
385                ALT_BN128_ADD,
386                input as *const _ as *const u8,
387                input.len() as u64,
388                &mut result_buffer as *mut _ as *mut u8,
389            )
390        };
391
392        match result {
393            0 => Ok(result_buffer.to_vec()),
394            _ => Err(AltBn128Error::UnexpectedError),
395        }
396    }
397
398    pub fn alt_bn128_multiplication(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
399        if input.len() > ALT_BN128_MULTIPLICATION_INPUT_LEN {
400            return Err(AltBn128Error::InvalidInputData);
401        }
402        let mut result_buffer = [0u8; ALT_BN128_POINT_SIZE];
403        let result = unsafe {
404            syscalls::trz_alt_bn128_group_op(
405                ALT_BN128_MUL,
406                input as *const _ as *const u8,
407                input.len() as u64,
408                &mut result_buffer as *mut _ as *mut u8,
409            )
410        };
411
412        match result {
413            0 => Ok(result_buffer.to_vec()),
414            _ => Err(AltBn128Error::UnexpectedError),
415        }
416    }
417
418    pub fn alt_bn128_pairing(input: &[u8]) -> Result<Vec<u8>, AltBn128Error> {
419        if input
420            .len()
421            .checked_rem(consts::ALT_BN128_PAIRING_ELEMENT_LEN)
422            .is_none()
423        {
424            return Err(AltBn128Error::InvalidInputData);
425        }
426        let mut result_buffer = [0u8; 32];
427        let result = unsafe {
428            syscalls::trz_alt_bn128_group_op(
429                ALT_BN128_PAIRING,
430                input as *const _ as *const u8,
431                input.len() as u64,
432                &mut result_buffer as *mut _ as *mut u8,
433            )
434        };
435
436        match result {
437            0 => Ok(result_buffer.to_vec()),
438            _ => Err(AltBn128Error::UnexpectedError),
439        }
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use {
446        crate::{prelude::*, PodG1},
447        ark_bn254::g1::G1Affine,
448        ark_ec::AffineRepr,
449        ark_serialize::{CanonicalSerialize, Compress},
450    };
451
452    #[test]
453    fn zero_serialization_test() {
454        let zero = G1Affine::zero();
455        let mut result_point_data = [0u8; 64];
456        zero.x
457            .serialize_with_mode(&mut result_point_data[..32], Compress::No)
458            .map_err(|_| AltBn128Error::InvalidInputData)
459            .unwrap();
460        zero.y
461            .serialize_with_mode(&mut result_point_data[32..], Compress::No)
462            .map_err(|_| AltBn128Error::InvalidInputData)
463            .unwrap();
464        assert_eq!(result_point_data, [0u8; 64]);
465
466        let p: G1Affine = PodG1(result_point_data[..64].try_into().unwrap())
467            .try_into()
468            .unwrap();
469        assert_eq!(p, zero);
470    }
471
472    #[test]
473    fn alt_bn128_pairing_invalid_length() {
474        use ark_ff::{BigInteger, BigInteger256};
475
476        let input = [0; 193];
477        let result = alt_bn128_pairing(&input);
478        assert!(result.is_ok());
479        let expected = BigInteger256::from(1u64).to_bytes_be();
480        assert_eq!(result.unwrap(), expected);
481    }
482}