From e554149ceb949611ba5f573a05a40afe1aa6895e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 19 May 2026 09:33:28 +0900 Subject: [PATCH 1/4] clean up pairing result check --- syscall/bn254-syscall/src/pairing.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/syscall/bn254-syscall/src/pairing.rs b/syscall/bn254-syscall/src/pairing.rs index b089966..c2ffe47 100644 --- a/syscall/bn254-syscall/src/pairing.rs +++ b/syscall/bn254-syscall/src/pairing.rs @@ -71,16 +71,13 @@ pub fn alt_bn128_versioned_pairing( vec_pairs.iter().map(|pair| pair.1), ); - let result = if res.0 == ark_bn254::Fq12::one() { - BigInteger256::from(1u64) - } else { - BigInteger256::from(0u64) - }; - - let output = match endianness { - Endianness::BE => result.to_bytes_be().try_into().ok()?, - Endianness::LE => result.to_bytes_le().try_into().ok()?, - }; + let mut output = [0u8; ALT_BN128_PAIRING_OUTPUT_SIZE]; + if res.0 == ark_bn254::Fq12::one() { + match endianness { + Endianness::BE => output[ALT_BN128_PAIRING_OUTPUT_SIZE - 1] = 1, + Endianness::LE => output[0] = 1, + } + } Some(output) } From 4d7b1a22af637f6ae3426bca9c4b3d86bb4296c2 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 19 May 2026 09:45:46 +0900 Subject: [PATCH 2/4] refactor out serialization logic --- syscall/bn254-syscall/src/addition.rs | 48 +++------------------ syscall/bn254-syscall/src/lib.rs | 48 +++++++++++++++++++++ syscall/bn254-syscall/src/multiplication.rs | 45 +++---------------- syscall/bn254-syscall/src/pairing.rs | 2 +- 4 files changed, 59 insertions(+), 84 deletions(-) diff --git a/syscall/bn254-syscall/src/addition.rs b/syscall/bn254-syscall/src/addition.rs index 6f3a8eb..fe22914 100644 --- a/syscall/bn254-syscall/src/addition.rs +++ b/syscall/bn254-syscall/src/addition.rs @@ -1,9 +1,6 @@ -use { - crate::{ - swap_endianness, Endianness, PodG1, PodG2, ALT_BN128_FIELD_SIZE, ALT_BN128_FQ2_SIZE, - ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, G1, G2, - }, - ark_serialize::{CanonicalSerialize, Compress}, +use crate::{ + serialize_g1, serialize_g2, Endianness, PodG1, PodG2, ALT_BN128_G1_POINT_SIZE, + ALT_BN128_G2_POINT_SIZE, }; /// Input size for the g1 add operation. @@ -65,25 +62,7 @@ pub fn alt_bn128_versioned_g1_addition( ), }; - let result_point_affine: G1 = (p + q).into(); - - let mut result_point_data = [0u8; ALT_BN128_G1_POINT_SIZE]; - result_point_affine - .x - .serialize_with_mode(&mut result_point_data[..ALT_BN128_FIELD_SIZE], Compress::No) - .ok()?; - result_point_affine - .y - .serialize_with_mode(&mut result_point_data[ALT_BN128_FIELD_SIZE..], Compress::No) - .ok()?; - - match endianness { - Endianness::BE => Some(swap_endianness::< - ALT_BN128_FIELD_SIZE, - ALT_BN128_G1_POINT_SIZE, - >(result_point_data)), - Endianness::LE => Some(result_point_data), - } + serialize_g1((p + q).into(), endianness) } /// The implementation of the `sol_alt_bn128_group_op` syscall G2 addition operation @@ -122,22 +101,5 @@ pub fn alt_bn128_versioned_g2_addition( ), }; - let result_point_affine: G2 = (p + q).into(); - - let mut result_point_data = [0u8; ALT_BN128_G2_POINT_SIZE]; - result_point_affine - .x - .serialize_with_mode(&mut result_point_data[..ALT_BN128_FQ2_SIZE], Compress::No) - .ok()?; - result_point_affine - .y - .serialize_with_mode(&mut result_point_data[ALT_BN128_FQ2_SIZE..], Compress::No) - .ok()?; - - match endianness { - Endianness::BE => { - Some(swap_endianness::(result_point_data)) - } - Endianness::LE => Some(result_point_data), - } + serialize_g2((p + q).into(), endianness) } diff --git a/syscall/bn254-syscall/src/lib.rs b/syscall/bn254-syscall/src/lib.rs index e8376e0..6d7bc0d 100644 --- a/syscall/bn254-syscall/src/lib.rs +++ b/syscall/bn254-syscall/src/lib.rs @@ -18,6 +18,7 @@ pub mod pairing; use { ark_ec::AffineRepr, + ark_serialize::CanonicalSerialize, ark_serialize::{CanonicalDeserialize, Compress, Validate}, bytemuck::{Pod, Zeroable}, }; @@ -196,6 +197,53 @@ impl PodG2 { } } +#[inline] +pub(crate) fn serialize_g1( + point: G1, + endianness: Endianness, +) -> Option<[u8; ALT_BN128_G1_POINT_SIZE]> { + let mut data = [0u8; ALT_BN128_G1_POINT_SIZE]; + point + .x + .serialize_with_mode(&mut data[..ALT_BN128_FIELD_SIZE], Compress::No) + .ok()?; + point + .y + .serialize_with_mode(&mut data[ALT_BN128_FIELD_SIZE..], Compress::No) + .ok()?; + + match endianness { + Endianness::BE => Some(swap_endianness::< + ALT_BN128_FIELD_SIZE, + ALT_BN128_G1_POINT_SIZE, + >(data)), + Endianness::LE => Some(data), + } +} + +#[inline] +pub(crate) fn serialize_g2( + point: G2, + endianness: Endianness, +) -> Option<[u8; ALT_BN128_G2_POINT_SIZE]> { + let mut data = [0u8; ALT_BN128_G2_POINT_SIZE]; + point + .x + .serialize_with_mode(&mut data[..ALT_BN128_FQ2_SIZE], Compress::No) + .ok()?; + point + .y + .serialize_with_mode(&mut data[ALT_BN128_FQ2_SIZE..], Compress::No) + .ok()?; + + match endianness { + Endianness::BE => { + Some(swap_endianness::(data)) + } + Endianness::LE => Some(data), + } +} + #[cfg(test)] mod tests { use { diff --git a/syscall/bn254-syscall/src/multiplication.rs b/syscall/bn254-syscall/src/multiplication.rs index 5eae9fc..1603ca5 100644 --- a/syscall/bn254-syscall/src/multiplication.rs +++ b/syscall/bn254-syscall/src/multiplication.rs @@ -1,11 +1,11 @@ use { crate::{ - swap_endianness, Endianness, PodG1, PodG2, ALT_BN128_FIELD_SIZE, ALT_BN128_FQ2_SIZE, - ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, G1, G2, + serialize_g1, serialize_g2, swap_endianness, Endianness, PodG1, PodG2, + ALT_BN128_FIELD_SIZE, ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, }, ark_ec::{self, AffineRepr}, ark_ff::BigInteger256, - ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}, + ark_serialize::CanonicalDeserialize, }; /// Input size for the g1 multiplication operation. @@ -80,25 +80,7 @@ pub fn alt_bn128_versioned_g1_multiplication( }; let fr = BigInteger256::deserialize_uncompressed_unchecked(fr_bytes_proper.as_slice()).ok()?; - let result_point_affine: G1 = p.mul_bigint(fr).into(); - - let mut result_point_data = [0u8; ALT_BN128_G1_POINT_SIZE]; - result_point_affine - .x - .serialize_with_mode(&mut result_point_data[..ALT_BN128_FIELD_SIZE], Compress::No) - .ok()?; - result_point_affine - .y - .serialize_with_mode(&mut result_point_data[ALT_BN128_FIELD_SIZE..], Compress::No) - .ok()?; - - match endianness { - Endianness::BE => Some(swap_endianness::< - ALT_BN128_FIELD_SIZE, - ALT_BN128_G1_POINT_SIZE, - >(result_point_data)), - Endianness::LE => Some(result_point_data), - } + serialize_g1(p.mul_bigint(fr).into(), endianness) } /// The implementation of the `sol_alt_bn128_group_op` syscall G2 multiplication operation @@ -138,22 +120,5 @@ pub fn alt_bn128_versioned_g2_multiplication( }; let fr = BigInteger256::deserialize_uncompressed_unchecked(fr_bytes_proper.as_slice()).ok()?; - let result_point_affine: G2 = p.mul_bigint(fr).into(); - - let mut result_point_data = [0u8; ALT_BN128_G2_POINT_SIZE]; - result_point_affine - .x - .serialize_with_mode(&mut result_point_data[..ALT_BN128_FQ2_SIZE], Compress::No) - .ok()?; - result_point_affine - .y - .serialize_with_mode(&mut result_point_data[ALT_BN128_FQ2_SIZE..], Compress::No) - .ok()?; - - match endianness { - Endianness::BE => { - Some(swap_endianness::(result_point_data)) - } - Endianness::LE => Some(result_point_data), - } + serialize_g2(p.mul_bigint(fr).into(), endianness) } diff --git a/syscall/bn254-syscall/src/pairing.rs b/syscall/bn254-syscall/src/pairing.rs index c2ffe47..c2f4af7 100644 --- a/syscall/bn254-syscall/src/pairing.rs +++ b/syscall/bn254-syscall/src/pairing.rs @@ -2,7 +2,7 @@ use { crate::{Endianness, PodG1, PodG2, ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, G1, G2}, ark_bn254::{self, Config}, ark_ec::{bn::Bn, pairing::Pairing}, - ark_ff::{BigInteger, BigInteger256, One}, + ark_ff::One, }; /// Pair element size. From a4e9974efe0acc727a9fa2701b022eddf378cc86 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 21 May 2026 09:33:12 +0900 Subject: [PATCH 3/4] use `PodG1` and `PodG2` as inputs --- syscall/bn254-syscall/src/addition.rs | 54 ++------ syscall/bn254-syscall/src/lib.rs | 144 ++++++++++---------- syscall/bn254-syscall/src/multiplication.rs | 71 ++-------- syscall/bn254-syscall/src/pairing.rs | 80 ++++++----- 4 files changed, 143 insertions(+), 206 deletions(-) diff --git a/syscall/bn254-syscall/src/addition.rs b/syscall/bn254-syscall/src/addition.rs index fe22914..aa5dcb6 100644 --- a/syscall/bn254-syscall/src/addition.rs +++ b/syscall/bn254-syscall/src/addition.rs @@ -34,33 +34,12 @@ pub enum VersionedG2Addition { /// for strict guidelines on SIMD approvals and versioning. pub fn alt_bn128_versioned_g1_addition( _version: VersionedG1Addition, - input: &[u8], + p: &PodG1, + q: &PodG1, endianness: Endianness, -) -> Option<[u8; ALT_BN128_G1_POINT_SIZE]> { - let is_valid_len = match endianness { - Endianness::BE => input.len() <= ALT_BN128_G1_ADDITION_INPUT_SIZE, - Endianness::LE => input.len() == ALT_BN128_G1_ADDITION_INPUT_SIZE, - }; - - if !is_valid_len { - return None; - } - - let mut padded_input = [0u8; ALT_BN128_G1_ADDITION_INPUT_SIZE]; - padded_input[..input.len()].copy_from_slice(input); - - let (p_bytes, q_bytes) = padded_input.split_at(ALT_BN128_G1_POINT_SIZE); - - let (p, q) = match endianness { - Endianness::BE => ( - PodG1::from_be_bytes(p_bytes)?.into_affine()?, - PodG1::from_be_bytes(q_bytes)?.into_affine()?, - ), - Endianness::LE => ( - PodG1::from_le_bytes(p_bytes)?.into_affine()?, - PodG1::from_le_bytes(q_bytes)?.into_affine()?, - ), - }; +) -> Option { + let p = p.deserialize_affine(endianness)?; + let q = q.deserialize_affine(endianness)?; serialize_g1((p + q).into(), endianness) } @@ -81,25 +60,12 @@ pub fn alt_bn128_versioned_g1_addition( /// guidelines on SIMD approvals and versioning. pub fn alt_bn128_versioned_g2_addition( _version: VersionedG2Addition, - input: &[u8], + p: &PodG2, + q: &PodG2, endianness: Endianness, -) -> Option<[u8; ALT_BN128_G2_POINT_SIZE]> { - if input.len() != ALT_BN128_G2_ADDITION_INPUT_SIZE { - return None; - } - - let (p_bytes, q_bytes) = input.split_at(ALT_BN128_G2_POINT_SIZE); - - let (p, q) = match endianness { - Endianness::BE => ( - PodG2::from_be_bytes(p_bytes)?.into_affine_unchecked()?, - PodG2::from_be_bytes(q_bytes)?.into_affine_unchecked()?, - ), - Endianness::LE => ( - PodG2::from_le_bytes(p_bytes)?.into_affine_unchecked()?, - PodG2::from_le_bytes(q_bytes)?.into_affine_unchecked()?, - ), - }; +) -> Option { + let p = p.deserialize_affine_unchecked(endianness)?; + let q = q.deserialize_affine_unchecked(endianness)?; serialize_g2((p + q).into(), endianness) } diff --git a/syscall/bn254-syscall/src/lib.rs b/syscall/bn254-syscall/src/lib.rs index 6d7bc0d..55b7be0 100644 --- a/syscall/bn254-syscall/src/lib.rs +++ b/syscall/bn254-syscall/src/lib.rs @@ -49,7 +49,7 @@ pub const ALT_BN128_G2_POINT_SIZE: usize = ALT_BN128_FQ2_SIZE * 2; /// respectively. #[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] -pub(crate) struct PodG1(pub [u8; ALT_BN128_G1_POINT_SIZE]); +pub struct PodG1(pub [u8; ALT_BN128_G1_POINT_SIZE]); /// The BN254 (BN128) group element in G2 as a POD type. /// @@ -69,11 +69,12 @@ pub(crate) struct PodG1(pub [u8; ALT_BN128_G1_POINT_SIZE]); /// respectively. #[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] -pub(crate) struct PodG2(pub [u8; ALT_BN128_G2_POINT_SIZE]); +pub struct PodG2(pub [u8; ALT_BN128_G2_POINT_SIZE]); pub(crate) type G1 = ark_bn254::g1::G1Affine; pub(crate) type G2 = ark_bn254::g2::G2Affine; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Endianness { BE, LE, @@ -105,52 +106,49 @@ impl PodG1 { /// Deserializes to an affine point in G1. /// Because G1 has a cofactor of 1, the subgroup check is equivalent to the /// on-curve check. - pub(crate) fn into_affine(self) -> Option { + pub(crate) fn deserialize_affine(&self, endianness: Endianness) -> Option { // pre-handle point at infinity - if self.0 == [0u8; 64] { + if self.0 == [0u8; ALT_BN128_G1_POINT_SIZE] { return Some(G1::zero()); } + let le_bytes = match endianness { + Endianness::BE => { + swap_endianness::(self.0) + } + Endianness::LE => self.0, + }; + // The ark-serialize uncompressed format expects 64 bytes of coordinates // plus a 1-byte metadata flag. We append a 0 byte to indicate infinity = false. - let mut buf = [0u8; 65]; - buf[..64].copy_from_slice(&self.0); + let mut buf = [0u8; ALT_BN128_G1_POINT_SIZE + 1]; + buf[..ALT_BN128_G1_POINT_SIZE].copy_from_slice(&le_bytes); // Validate::Yes performs the necessary subgroup checks G1::deserialize_with_mode(&buf[..], Compress::No, Validate::Yes).ok() } - - /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G1 and constructs a - /// `PodG1` struct that encodes the same bytes in little-endian. - #[inline(always)] - pub(crate) fn from_be_bytes(be_bytes: &[u8]) -> Option { - let pod_bytes = swap_endianness::( - be_bytes.try_into().ok()?, - ); - Some(Self(pod_bytes)) - } - - /// Takes in a little-endian byte encoding of a group element in G1 and constructs a - /// `PodG1` struct that encodes the same bytes internally. - #[inline(always)] - pub(crate) fn from_le_bytes(le_bytes: &[u8]) -> Option { - le_bytes.try_into().ok().map(Self) - } } impl PodG2 { /// Deserializes to an affine point in G2. /// This function performs both the curve equation check and the subgroup check. - pub(crate) fn into_affine(self) -> Option { + pub(crate) fn deserialize_affine(&self, endianness: Endianness) -> Option { // pre-handle point at infinity - if self.0 == [0u8; 128] { + if self.0 == [0u8; ALT_BN128_G2_POINT_SIZE] { return Some(G2::zero()); } + let le_bytes = match endianness { + Endianness::BE => { + swap_endianness::(self.0) + } + Endianness::LE => self.0, + }; + // The ark-serialize uncompressed format expects 128 bytes of coordinates // plus a 1-byte metadata flag. We append a 0 byte to indicate infinity = false. - let mut buf = [0u8; 129]; - buf[..128].copy_from_slice(&self.0); + let mut buf = [0u8; ALT_BN128_G2_POINT_SIZE + 1]; + buf[..ALT_BN128_G2_POINT_SIZE].copy_from_slice(&le_bytes); // Validate::Yes performs the necessary subgroup checks G2::deserialize_with_mode(&buf[..], Compress::No, Validate::Yes).ok() @@ -158,18 +156,25 @@ impl PodG2 { /// Deserializes to an affine point in G2. /// This function performs the curve equation check, but skips the subgroup check. - pub(crate) fn into_affine_unchecked(self) -> Option { + pub(crate) fn deserialize_affine_unchecked(&self, endianness: Endianness) -> Option { // pre-handle point at infinity - if self.0 == [0u8; 128] { + if self.0 == [0u8; ALT_BN128_G2_POINT_SIZE] { return Some(G2::zero()); } + let le_bytes = match endianness { + Endianness::BE => { + swap_endianness::(self.0) + } + Endianness::LE => self.0, + }; + // The `ark-serialize` uncompressed format for affine points expects the // x and y coordinates (128-bytes total) followed by a 1-byte metadata flag. // We explicitly handle point at infinity above, so we append `0` to indicate // `infinity = false`. - let mut buf = [0u8; 129]; - buf[..128].copy_from_slice(&self.0); + let mut buf = [0u8; ALT_BN128_G2_POINT_SIZE + 1]; + buf[..ALT_BN128_G2_POINT_SIZE].copy_from_slice(&le_bytes); // Skips the expensive subgroup check let g2 = G2::deserialize_with_mode(&buf[..], Compress::No, Validate::No).ok()?; @@ -177,31 +182,10 @@ impl PodG2 { // Still check if point is on the curve g2.is_on_curve().then_some(g2) } - - /// Takes in an EIP-197 (big-endian) byte encoding of a group element in G2 - /// and constructs a `PodG2` struct that encodes the same bytes in - /// little-endian. - #[inline(always)] - pub(crate) fn from_be_bytes(be_bytes: &[u8]) -> Option { - let pod_bytes = swap_endianness::( - be_bytes.try_into().ok()?, - ); - Some(Self(pod_bytes)) - } - - /// Takes in a little-endian byte encoding of a group element in G2 and constructs a - /// `PodG2` struct that encodes the same bytes internally. - #[inline(always)] - pub(crate) fn from_le_bytes(le_bytes: &[u8]) -> Option { - le_bytes.try_into().ok().map(Self) - } } #[inline] -pub(crate) fn serialize_g1( - point: G1, - endianness: Endianness, -) -> Option<[u8; ALT_BN128_G1_POINT_SIZE]> { +pub(crate) fn serialize_g1(point: G1, endianness: Endianness) -> Option { let mut data = [0u8; ALT_BN128_G1_POINT_SIZE]; point .x @@ -212,20 +196,16 @@ pub(crate) fn serialize_g1( .serialize_with_mode(&mut data[ALT_BN128_FIELD_SIZE..], Compress::No) .ok()?; - match endianness { - Endianness::BE => Some(swap_endianness::< - ALT_BN128_FIELD_SIZE, - ALT_BN128_G1_POINT_SIZE, - >(data)), - Endianness::LE => Some(data), - } + let final_bytes = match endianness { + Endianness::BE => swap_endianness::(data), + Endianness::LE => data, + }; + + Some(PodG1(final_bytes)) } #[inline] -pub(crate) fn serialize_g2( - point: G2, - endianness: Endianness, -) -> Option<[u8; ALT_BN128_G2_POINT_SIZE]> { +pub(crate) fn serialize_g2(point: G2, endianness: Endianness) -> Option { let mut data = [0u8; ALT_BN128_G2_POINT_SIZE]; point .x @@ -236,18 +216,37 @@ pub(crate) fn serialize_g2( .serialize_with_mode(&mut data[ALT_BN128_FQ2_SIZE..], Compress::No) .ok()?; - match endianness { - Endianness::BE => { - Some(swap_endianness::(data)) - } - Endianness::LE => Some(data), + let final_bytes = match endianness { + Endianness::BE => swap_endianness::(data), + Endianness::LE => data, + }; + + Some(PodG2(final_bytes)) +} + +/// The BN254 (BN128) scalar field element as a POD type. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct PodScalar(pub [u8; ALT_BN128_FIELD_SIZE]); + +impl PodScalar { + /// Deserializes the bytes into an uncompressed ark-ff BigInteger256. + pub(crate) fn deserialize_bigint( + &self, + endianness: Endianness, + ) -> Option { + let le_bytes = match endianness { + Endianness::BE => swap_endianness::(self.0), + Endianness::LE => self.0, + }; + ark_ff::BigInteger256::deserialize_uncompressed_unchecked(&le_bytes[..]).ok() } } #[cfg(test)] mod tests { use { - crate::PodG1, + crate::{Endianness, PodG1}, ark_bn254::g1::G1Affine, ark_ec::AffineRepr, ark_serialize::{CanonicalSerialize, Compress}, @@ -265,9 +264,8 @@ mod tests { .unwrap(); assert_eq!(result_point_data, [0u8; 64]); - let p: G1Affine = PodG1(result_point_data[..64].try_into().unwrap()) - .into_affine() - .unwrap(); + let pod_point = PodG1(result_point_data[..64].try_into().unwrap()); + let p: G1Affine = pod_point.deserialize_affine(Endianness::LE).unwrap(); assert_eq!(p, zero); } } diff --git a/syscall/bn254-syscall/src/multiplication.rs b/syscall/bn254-syscall/src/multiplication.rs index 1603ca5..fc9894f 100644 --- a/syscall/bn254-syscall/src/multiplication.rs +++ b/syscall/bn254-syscall/src/multiplication.rs @@ -1,11 +1,9 @@ use { crate::{ - serialize_g1, serialize_g2, swap_endianness, Endianness, PodG1, PodG2, - ALT_BN128_FIELD_SIZE, ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, + serialize_g1, serialize_g2, Endianness, PodG1, PodG2, PodScalar, ALT_BN128_FIELD_SIZE, + ALT_BN128_G1_POINT_SIZE, ALT_BN128_G2_POINT_SIZE, }, ark_ec::{self, AffineRepr}, - ark_ff::BigInteger256, - ark_serialize::CanonicalDeserialize, }; /// Input size for the g1 multiplication operation. @@ -43,44 +41,19 @@ pub enum VersionedG2Multiplication { /// guidelines on SIMD approvals and versioning. pub fn alt_bn128_versioned_g1_multiplication( version: VersionedG1Multiplication, - input: &[u8], + p: &PodG1, + scalar: &PodScalar, endianness: Endianness, -) -> Option<[u8; ALT_BN128_G1_POINT_SIZE]> { +) -> Option { // reject deprecated variants if matches!(version, VersionedG1Multiplication::V0) { return None; } - let is_valid_len = match endianness { - Endianness::BE => input.len() <= ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE, - Endianness::LE => input.len() == ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE, - }; + let p = p.deserialize_affine(endianness)?; + let scalar = scalar.deserialize_bigint(endianness)?; - if !is_valid_len { - return None; - } - - let mut padded_input = [0u8; ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE]; - padded_input[..input.len()].copy_from_slice(input); - - let (p_bytes, remainder) = padded_input.split_at(ALT_BN128_G1_POINT_SIZE); - let (fr_bytes, _) = remainder.split_at(ALT_BN128_FIELD_SIZE); - - let p = match endianness { - Endianness::BE => PodG1::from_be_bytes(p_bytes)?.into_affine()?, - Endianness::LE => PodG1::from_le_bytes(p_bytes)?.into_affine()?, - }; - - let fr_bytes_array: [u8; ALT_BN128_FIELD_SIZE] = fr_bytes.try_into().ok()?; - let fr_bytes_proper = match endianness { - Endianness::BE => { - swap_endianness::(fr_bytes_array) - } - Endianness::LE => fr_bytes_array, - }; - let fr = BigInteger256::deserialize_uncompressed_unchecked(fr_bytes_proper.as_slice()).ok()?; - - serialize_g1(p.mul_bigint(fr).into(), endianness) + serialize_g1(p.mul_bigint(scalar).into(), endianness) } /// The implementation of the `sol_alt_bn128_group_op` syscall G2 multiplication operation @@ -97,28 +70,12 @@ pub fn alt_bn128_versioned_g1_multiplication( /// guidelines on SIMD approvals and versioning. pub fn alt_bn128_versioned_g2_multiplication( _version: VersionedG2Multiplication, - input: &[u8], + p: &PodG2, + scalar: &PodScalar, endianness: Endianness, -) -> Option<[u8; ALT_BN128_G2_POINT_SIZE]> { - if input.len() != ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE { - return None; - } - - let (p_bytes, fr_bytes) = input.split_at(ALT_BN128_G2_POINT_SIZE); - - let p = match endianness { - Endianness::BE => PodG2::from_be_bytes(p_bytes)?.into_affine()?, - Endianness::LE => PodG2::from_le_bytes(p_bytes)?.into_affine()?, - }; - - let fr_bytes_array: [u8; ALT_BN128_FIELD_SIZE] = fr_bytes.try_into().ok()?; - let fr_bytes_proper = match endianness { - Endianness::BE => { - swap_endianness::(fr_bytes_array) - } - Endianness::LE => fr_bytes_array, - }; - let fr = BigInteger256::deserialize_uncompressed_unchecked(fr_bytes_proper.as_slice()).ok()?; +) -> Option { + let p = p.deserialize_affine(endianness)?; + let scalar = scalar.deserialize_bigint(endianness)?; - serialize_g2(p.mul_bigint(fr).into(), endianness) + serialize_g2(p.mul_bigint(scalar).into(), endianness) } diff --git a/syscall/bn254-syscall/src/pairing.rs b/syscall/bn254-syscall/src/pairing.rs index c2f4af7..9fd405b 100644 --- a/syscall/bn254-syscall/src/pairing.rs +++ b/syscall/bn254-syscall/src/pairing.rs @@ -3,6 +3,7 @@ use { ark_bn254::{self, Config}, ark_ec::{bn::Bn, pairing::Pairing}, ark_ff::One, + bytemuck::{Pod, Zeroable}, }; /// Pair element size. @@ -18,6 +19,45 @@ pub enum VersionedPairing { V1, } +/// A combined POD struct representing a (G1, G2) pairing element. +/// +/// The size is exactly 192 bytes (64 bytes for G1 + 128 bytes for G2). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] +#[repr(C)] +pub struct PodPair { + pub g1: PodG1, + pub g2: PodG2, +} + +/// The output of a BN254 pairing operation as a POD type. +/// +/// Logically, the result of a pairing check is just a single byte (a boolean +/// indicating success or failure). However, for historical reasons (Ethereum +/// EIP-197 compatibility), the output is padded to exactly 32 bytes. +/// +/// Depending on the requested endianness, a successful pairing sets a `1` byte +/// at different ends of the array: +/// - Big-Endian (BE): The `1` is placed at the very end of the array (index 31). +/// - Little-Endian (LE): The `1` is placed at the very beginning of the array (index 0). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] +#[repr(transparent)] +pub struct PodPairingOutput(pub [u8; ALT_BN128_PAIRING_OUTPUT_SIZE]); + +impl PodPairingOutput { + /// Constructs a `PodPairingOutput` from a boolean result and the requested endianness. + #[inline] + pub(crate) fn from_bool(is_success: bool, endianness: Endianness) -> Self { + let mut output = [0u8; ALT_BN128_PAIRING_OUTPUT_SIZE]; + if is_success { + match endianness { + Endianness::BE => output[ALT_BN128_PAIRING_OUTPUT_SIZE - 1] = 1, + Endianness::LE => output[0] = 1, + } + } + Self(output) + } +} + /// The implementation of the `sol_alt_bn128_group_op` syscall pairing operation /// (group operation index 0x03 for BE input/output, 0x83 for LE input/output). /// @@ -33,36 +73,19 @@ pub enum VersionedPairing { /// guidelines on SIMD approvals and versioning. pub fn alt_bn128_versioned_pairing( version: VersionedPairing, - input: &[u8], + pairs: &[PodPair], endianness: Endianness, -) -> Option<[u8; ALT_BN128_PAIRING_OUTPUT_SIZE]> { +) -> Option { // reject deprecated variants if matches!(version, VersionedPairing::V0) { return None; } - #[allow(clippy::manual_is_multiple_of)] - if input.len() % ALT_BN128_PAIRING_ELEMENT_SIZE != 0 { - return None; - } - - let chunks = input.chunks_exact(ALT_BN128_PAIRING_ELEMENT_SIZE); - let mut vec_pairs: Vec<(G1, G2)> = Vec::with_capacity(chunks.len()); - - for chunk in chunks { - let (p_bytes, q_bytes) = chunk.split_at(ALT_BN128_G1_POINT_SIZE); - - let (g1, g2) = match endianness { - Endianness::BE => ( - PodG1::from_be_bytes(p_bytes)?.into_affine()?, - PodG2::from_be_bytes(q_bytes)?.into_affine()?, - ), - Endianness::LE => ( - PodG1::from_le_bytes(p_bytes)?.into_affine()?, - PodG2::from_le_bytes(q_bytes)?.into_affine()?, - ), - }; + let mut vec_pairs: Vec<(G1, G2)> = Vec::with_capacity(pairs.len()); + for pair in pairs { + let g1 = pair.g1.deserialize_affine(endianness)?; + let g2 = pair.g2.deserialize_affine(endianness)?; vec_pairs.push((g1, g2)); } @@ -71,13 +94,6 @@ pub fn alt_bn128_versioned_pairing( vec_pairs.iter().map(|pair| pair.1), ); - let mut output = [0u8; ALT_BN128_PAIRING_OUTPUT_SIZE]; - if res.0 == ark_bn254::Fq12::one() { - match endianness { - Endianness::BE => output[ALT_BN128_PAIRING_OUTPUT_SIZE - 1] = 1, - Endianness::LE => output[0] = 1, - } - } - - Some(output) + let is_success = res.0 == ark_bn254::Fq12::one(); + Some(PodPairingOutput::from_bool(is_success, endianness)) } From 313ab3281e376e47acde4b31c8cd9f7589557a03 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Thu, 21 May 2026 09:59:36 +0900 Subject: [PATCH 4/4] clean up docs a little --- syscall/bn254-syscall/src/addition.rs | 10 +++--- syscall/bn254-syscall/src/lib.rs | 35 +++++++++------------ syscall/bn254-syscall/src/multiplication.rs | 10 +++--- syscall/bn254-syscall/src/pairing.rs | 5 +-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/syscall/bn254-syscall/src/addition.rs b/syscall/bn254-syscall/src/addition.rs index aa5dcb6..0902afa 100644 --- a/syscall/bn254-syscall/src/addition.rs +++ b/syscall/bn254-syscall/src/addition.rs @@ -3,18 +3,20 @@ use crate::{ ALT_BN128_G2_POINT_SIZE, }; -/// Input size for the g1 add operation. -pub const ALT_BN128_G1_ADDITION_INPUT_SIZE: usize = ALT_BN128_G1_POINT_SIZE * 2; // 128 +/// Input size for the g1 add operation (128 bytes). +pub const ALT_BN128_G1_ADDITION_INPUT_SIZE: usize = ALT_BN128_G1_POINT_SIZE * 2; -/// Input size for the g2 add operation. -pub const ALT_BN128_G2_ADDITION_INPUT_SIZE: usize = ALT_BN128_G2_POINT_SIZE * 2; // 256 +/// Input size for the g2 add operation (256 bytes). +pub const ALT_BN128_G2_ADDITION_INPUT_SIZE: usize = ALT_BN128_G2_POINT_SIZE * 2; /// The enum is used to version changes to the `alt_bn128_versioned_g1_addition` function. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VersionedG1Addition { V0, } /// The enum is used to version changes to the `alt_bn128_versioned_g2_addition` function. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VersionedG2Addition { V0, } diff --git a/syscall/bn254-syscall/src/lib.rs b/syscall/bn254-syscall/src/lib.rs index 55b7be0..50de7f0 100644 --- a/syscall/bn254-syscall/src/lib.rs +++ b/syscall/bn254-syscall/src/lib.rs @@ -38,35 +38,30 @@ pub const ALT_BN128_G2_POINT_SIZE: usize = ALT_BN128_FQ2_SIZE * 2; /// The BN254 (BN128) group element in G1 as a POD type. /// -/// A group element in G1 consists of two field elements `(x, y)`. A `PodG1` -/// type expects a group element to be encoded as `[le(x), le(y)]` where -/// `le(..)` is the little-endian encoding of the input field element as used -/// in the `ark-bn254` crate. Note that this differs from the EIP-197 standard, -/// which specifies that the field elements are encoded as big-endian. +/// A group element in G1 consists of two field elements `(x, y)`. `PodG1` +/// acts as an encoding-agnostic transparent byte container for syscall inputs. /// -/// `PodG1` can be constructed from both big-endian (EIP-197) and little-endian -/// (ark-bn254) encodings using `from_be_bytes` and `from_le_bytes` methods, -/// respectively. +/// The interpretation of these bytes depends on the provided `Endianness` flag: +/// - Little-endian (`ark-bn254`): Encoded as `[le(x), le(y)]`. +/// - Big-endiann (EIP-197): Encoded as `[be(x), be(y)]`. #[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] pub struct PodG1(pub [u8; ALT_BN128_G1_POINT_SIZE]); /// The BN254 (BN128) group element in G2 as a POD type. /// -/// Elements in G2 is represented by 2 field-extension elements `(x, y)`. Each +/// Elements in G2 are represented by 2 field-extension elements `(x, y)`. Each /// field-extension element itself is a degree 1 polynomial `x = x0 + x1*X`, -/// `y = y0 + y1*X`. The EIP-197 standard encodes a G2 element as -/// `[be(x1), be(x0), be(y1), be(y0)]` where `be(..)` is the big-endian -/// encoding of the input field element. The `ark-bn254` crate encodes a G2 -/// element as `[le(x0), le(x1), le(y0), le(y1)]` where `le(..)` is the -/// little-endian encoding of the input field element. Notably, in addition to -/// the differences in the big-endian vs. little-endian encodings of field -/// elements, the order of the polynomial field coefficients `x0`, `x1`, `y0`, -/// and `y1` are different. +/// `y = y0 + y1*X`. +/// +/// `PodG2` acts as an encoding-agnostic transparent byte container. The interpretation +/// of these bytes depends on the provided `Endianness` flag: +/// - Big-endian (EIP-197): Encodes a G2 element as `[be(x1), be(x0), be(y1), be(y0)]`. +/// - Little-endian (`ark-bn254`): Encodes a G2 element as `[le(x0), le(x1), le(y0), le(y1)]`. /// -/// `PodG2` can be constructed from both big-endian (EIP-197) and little-endian -/// (ark-bn254) encodings using `from_be_bytes` and `from_le_bytes` methods, -/// respectively. +/// Notably, in addition to the differences in the big-endian vs. little-endian encodings +/// of the field elements, the order of the polynomial field coefficients `x0`, `x1`, `y0`, +/// and `y1` are different. #[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)] #[repr(transparent)] pub struct PodG2(pub [u8; ALT_BN128_G2_POINT_SIZE]); diff --git a/syscall/bn254-syscall/src/multiplication.rs b/syscall/bn254-syscall/src/multiplication.rs index fc9894f..aaa8a6b 100644 --- a/syscall/bn254-syscall/src/multiplication.rs +++ b/syscall/bn254-syscall/src/multiplication.rs @@ -6,15 +6,16 @@ use { ark_ec::{self, AffineRepr}, }; -/// Input size for the g1 multiplication operation. +/// Input size for the g1 multiplication operation (96 bytes). pub const ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE: usize = - ALT_BN128_G1_POINT_SIZE + ALT_BN128_FIELD_SIZE; // 96 + ALT_BN128_G1_POINT_SIZE + ALT_BN128_FIELD_SIZE; -/// Input size for the g2 multiplication operation. +/// Input size for the g2 multiplication operation (160 bytes). pub const ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE: usize = - ALT_BN128_G2_POINT_SIZE + ALT_BN128_FIELD_SIZE; // 160 + ALT_BN128_G2_POINT_SIZE + ALT_BN128_FIELD_SIZE; /// The enum is used to version changes to the `alt_bn128_versioned_g1_multiplication` function. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VersionedG1Multiplication { V0, /// SIMD-0222 - Fix alt-bn128-multiplication Syscall Length Check @@ -22,6 +23,7 @@ pub enum VersionedG1Multiplication { } /// The enum is used to version changes to the `alt_bn128_versioned_g2_multiplication` function. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VersionedG2Multiplication { V0, } diff --git a/syscall/bn254-syscall/src/pairing.rs b/syscall/bn254-syscall/src/pairing.rs index 9fd405b..2e6ada0 100644 --- a/syscall/bn254-syscall/src/pairing.rs +++ b/syscall/bn254-syscall/src/pairing.rs @@ -6,13 +6,14 @@ use { bytemuck::{Pod, Zeroable}, }; -/// Pair element size. -pub const ALT_BN128_PAIRING_ELEMENT_SIZE: usize = ALT_BN128_G1_POINT_SIZE + ALT_BN128_G2_POINT_SIZE; // 192 +/// Pair element size (192 bytes). +pub const ALT_BN128_PAIRING_ELEMENT_SIZE: usize = ALT_BN128_G1_POINT_SIZE + ALT_BN128_G2_POINT_SIZE; /// Output size for pairing operation. pub const ALT_BN128_PAIRING_OUTPUT_SIZE: usize = 32; /// The enum is used to version changes to the `alt_bn128_versioned_pairing` function. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VersionedPairing { V0, /// SIMD-0334 - Fix alt_bn128_pairing Syscall Length Check