[solana-bn254-syscall] Add solana-bn254-syscall crate (#13)

* add `solana-bn254-syscall` crate

* use `Validate::Yes` for `G1::deserialize_with_mode`

* fix crate name

* inherit dependencies from workspace

* update edition to 2021

* remove unnecessary `is_on_curve` check

* add `#[inline(always)]`

* make `PodG1` and `PodG2` pub(crate)

* remove unnecessary `is_on_curve` check

* remove custom logic for legacy versions

* `convert_endianness` -> `swap_endianness`

* simplify `swap_endianness`

* update function return types to arrays instead of vecs

* copy over docs

* update cargo lock

* copy over unit tests for serialization

* clean up docs for the syscall implementation functions

* Apply suggestions from code review

Co-authored-by: Stanislav Ladyzhenskiy <LStan@users.noreply.github.com>

* Update syscall/bn254-syscall/src/multiplication.rs

Co-authored-by: Stanislav Ladyzhenskiy <LStan@users.noreply.github.com>

* cargo lock

* remove `all-features` and `rustdoc-args`

* Apply suggestions from code review

Co-authored-by: Stanislav Ladyzhenskiy <LStan@users.noreply.github.com>

* Update syscall/bn254-syscall/Cargo.toml

Co-authored-by: Stanislav Ladyzhenskiy <LStan@users.noreply.github.com>

* remove `include = ...`

---------

Co-authored-by: Stanislav Ladyzhenskiy <LStan@users.noreply.github.com>
This commit is contained in:
Sam Kim 2026-05-19 08:56:06 +09:00 committed by GitHub
parent 7170a1147d
commit ed4a212bcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 848 additions and 2 deletions

212
Cargo.lock generated
View file

@ -2,6 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.4"
@ -35,6 +47,136 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "ark-bn254"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc"
dependencies = [
"ark-ec",
"ark-ff",
"ark-std",
]
[[package]]
name = "ark-ec"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce"
dependencies = [
"ahash",
"ark-ff",
"ark-poly",
"ark-serialize",
"ark-std",
"educe",
"fnv",
"hashbrown 0.15.5",
"itertools 0.13.0",
"num-bigint",
"num-integer",
"num-traits",
"zeroize",
]
[[package]]
name = "ark-ff"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
dependencies = [
"ark-ff-asm",
"ark-ff-macros",
"ark-serialize",
"ark-std",
"arrayvec",
"digest 0.10.7",
"educe",
"itertools 0.13.0",
"num-bigint",
"num-traits",
"paste",
"zeroize",
]
[[package]]
name = "ark-ff-asm"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "ark-ff-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
dependencies = [
"num-bigint",
"num-traits",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ark-poly"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27"
dependencies = [
"ahash",
"ark-ff",
"ark-serialize",
"ark-std",
"educe",
"fnv",
"hashbrown 0.15.5",
]
[[package]]
name = "ark-serialize"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
dependencies = [
"ark-serialize-derive",
"ark-std",
"arrayvec",
"digest 0.10.7",
"num-bigint",
]
[[package]]
name = "ark-serialize-derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ark-std"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
dependencies = [
"num-traits",
"rand 0.8.6",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@ -152,6 +294,9 @@ name = "bytemuck"
version = "1.25.0" version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
dependencies = [
"bytemuck_derive",
]
[[package]] [[package]]
name = "bytemuck_derive" name = "bytemuck_derive"
@ -482,12 +627,44 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "educe"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
dependencies = [
"enum-ordinalize",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "enum-ordinalize"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0"
dependencies = [
"enum-ordinalize-derive",
]
[[package]]
name = "enum-ordinalize-derive"
version = "4.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -742,6 +919,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.14.0" version = "0.14.0"
@ -1232,7 +1418,7 @@ dependencies = [
"bitflags", "bitflags",
"num-traits", "num-traits",
"rand 0.9.4", "rand 0.9.4",
"rand_chacha", "rand_chacha 0.9.0",
"rand_xorshift 0.4.0", "rand_xorshift 0.4.0",
"regex-syntax", "regex-syntax",
"rusty-fork", "rusty-fork",
@ -1279,6 +1465,7 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"rand_chacha 0.3.1",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
@ -1288,7 +1475,7 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha 0.9.0",
"rand_core 0.9.5", "rand_core 0.9.5",
] ]
@ -1303,6 +1490,16 @@ dependencies = [
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.9.0" version = "0.9.0"
@ -1572,6 +1769,17 @@ dependencies = [
"pairing", "pairing",
] ]
[[package]]
name = "solana-bn254-syscall"
version = "0.1.0"
dependencies = [
"ark-bn254",
"ark-ec",
"ark-ff",
"ark-serialize",
"bytemuck",
]
[[package]] [[package]]
name = "solana-curve25519-cuda" name = "solana-curve25519-cuda"
version = "0.1.0" version = "0.1.0"

View file

@ -4,6 +4,7 @@ members = [
"curve25519/solana-ed25519", "curve25519/solana-ed25519",
"experimental/ed25519-pokos", "experimental/ed25519-pokos",
"syscall/bls12-381-syscall", "syscall/bls12-381-syscall",
"syscall/bn254-syscall",
] ]
resolver = "2" resolver = "2"
@ -15,6 +16,10 @@ license = "Apache-2.0"
rust-version = "1.89.0" # solana platform-tools rust version rust-version = "1.89.0" # solana platform-tools rust version
[workspace.dependencies] [workspace.dependencies]
ark-bn254 = "0.5.0"
ark-ec = "0.5.0"
ark-ff = "0.5.0"
ark-serialize = "0.5.0"
bincode = "1" bincode = "1"
blst = "0.3.16" blst = "0.3.16"
blstrs = "0.7.1" blstrs = "0.7.1"

View file

@ -0,0 +1,20 @@
[package]
name = "solana-bn254-syscall"
description = "Solana BN254 Syscall"
documentation = "https://docs.rs/solana-bn254-syscall"
version = "0.1.0"
authors = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
edition = "2021"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
ark-bn254 = { workspace = true }
ark-ec = { workspace = true }
ark-ff = { workspace = true }
ark-serialize = { workspace = true }
bytemuck = { workspace = true, features = ["derive"] }

View file

@ -0,0 +1,143 @@
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},
};
/// 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 g2 add operation.
pub const ALT_BN128_G2_ADDITION_INPUT_SIZE: usize = ALT_BN128_G2_POINT_SIZE * 2; // 256
/// The enum is used to version changes to the `alt_bn128_versioned_g1_addition` function.
pub enum VersionedG1Addition {
V0,
}
/// The enum is used to version changes to the `alt_bn128_versioned_g2_addition` function.
pub enum VersionedG2Addition {
V0,
}
/// The implementation of the `sol_alt_bn128_group_op` syscall G1 addition operation
/// (group operation index 0x00 for BE input/output, 0x80 for LE input/output).
///
/// **Security Note**
///
/// Because the BN254 G1 group has a cofactor of 1, the subgroup check is equivalent
/// to verifying the point is on the curve. This function fully validates the input point.
///
/// **Warning**
///
/// This is consensus-critical Agave validator code. Modifying this
/// function can result in a network fork. See the [crate-level documentation](crate)
/// for strict guidelines on SIMD approvals and versioning.
pub fn alt_bn128_versioned_g1_addition(
_version: VersionedG1Addition,
input: &[u8],
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()?,
),
};
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),
}
}
/// The implementation of the `sol_alt_bn128_group_op` syscall G2 addition operation
/// (group operation index 0x04 for BE input/output, 0x84 for LE input/output).
///
/// **Security Note**
///
/// Unlike G1, which has a cofactor of 1, the group G2 has a high cofactor.
/// This G2 addition function validates only the curve equation; it does not perform
/// a subgroup (coset) check.
///
/// **Warning**
///
/// This is consensus-critical Agave validator code. Modifying this function can
/// result in a network fork. See the [crate-level documentation](crate) for strict
/// guidelines on SIMD approvals and versioning.
pub fn alt_bn128_versioned_g2_addition(
_version: VersionedG2Addition,
input: &[u8],
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()?,
),
};
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::<ALT_BN128_FQ2_SIZE, ALT_BN128_G2_POINT_SIZE>(result_point_data))
}
Endianness::LE => Some(result_point_data),
}
}

View file

@ -0,0 +1,225 @@
//! # Solana BN254 Syscalls
//!
//! ** Consensus-Critical Validator Code**
//!
//! The syscall implementations in this crate are intended to be used by the
//! Agave validator client and exist primarily for validator code. Solana
//! programs or other downstream projects should use the functions from the
//! `solana-bn254` crate in the `solana-sdk` instead.
//!
//! Developers should be extremely careful when modifying these functions, as a
//! breaking change can result in a fork in the Solana cluster. Any such change
//! requires an approved Solana SIMD. Subsequently, a new version variant must
//! be added, and the new logic must be scoped to that variant.
pub mod addition;
pub mod multiplication;
pub mod pairing;
use {
ark_ec::AffineRepr,
ark_serialize::{CanonicalDeserialize, Compress, Validate},
bytemuck::{Pod, Zeroable},
};
/// Size of the EC point field, in bytes.
pub const ALT_BN128_FIELD_SIZE: usize = 32;
/// Size of the extension field element (Fq2), in bytes.
pub const ALT_BN128_FQ2_SIZE: usize = ALT_BN128_FIELD_SIZE * 2;
/// Size of the EC point. `alt_bn128` point contains
/// the consistently united x and y fields as 64 bytes.
pub const ALT_BN128_G1_POINT_SIZE: usize = ALT_BN128_FIELD_SIZE * 2;
/// Elements in G2 is represented by 2 field-extension elements `(x, y)`.
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.
///
/// `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.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
#[repr(transparent)]
pub(crate) 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
/// 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.
///
/// `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.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
#[repr(transparent)]
pub(crate) 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;
pub enum Endianness {
BE,
LE,
}
/// This function swaps the endianness of each element within the input byte array.
/// It splits the input byte array of size `ARRAY_SIZE` into chunks of `CHUNK_SIZE`
/// and reverses the byte order within each chunk.
///
/// Typical use cases:
/// - `swap_endianness::<32, 64>` for a G1 point
/// - `swap_endianness::<64, 128>` for a G2 point
/// - `swap_endianness::<32, 32>` for a scalar
pub(crate) fn swap_endianness<const CHUNK_SIZE: usize, const ARRAY_SIZE: usize>(
mut bytes: [u8; ARRAY_SIZE],
) -> [u8; ARRAY_SIZE] {
debug_assert!(
ARRAY_SIZE.is_multiple_of(CHUNK_SIZE),
"ARRAY_SIZE must be a multiple of CHUNK_SIZE"
);
for chunk in bytes.chunks_exact_mut(CHUNK_SIZE) {
chunk.reverse();
}
bytes
}
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<G1> {
// pre-handle point at infinity
if self.0 == [0u8; 64] {
return Some(G1::zero());
}
// 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);
// 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<Self> {
let pod_bytes = swap_endianness::<ALT_BN128_FIELD_SIZE, ALT_BN128_G1_POINT_SIZE>(
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<Self> {
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<G2> {
// pre-handle point at infinity
if self.0 == [0u8; 128] {
return Some(G2::zero());
}
// 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);
// Validate::Yes performs the necessary subgroup checks
G2::deserialize_with_mode(&buf[..], Compress::No, Validate::Yes).ok()
}
/// 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<G2> {
// pre-handle point at infinity
if self.0 == [0u8; 128] {
return Some(G2::zero());
}
// 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);
// Skips the expensive subgroup check
let g2 = G2::deserialize_with_mode(&buf[..], Compress::No, Validate::No).ok()?;
// 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<Self> {
let pod_bytes = swap_endianness::<ALT_BN128_FQ2_SIZE, ALT_BN128_G2_POINT_SIZE>(
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<Self> {
le_bytes.try_into().ok().map(Self)
}
}
#[cfg(test)]
mod tests {
use {
crate::PodG1,
ark_bn254::g1::G1Affine,
ark_ec::AffineRepr,
ark_serialize::{CanonicalSerialize, Compress},
};
#[test]
fn zero_serialization_test() {
let zero = G1Affine::zero();
let mut result_point_data = [0u8; 64];
zero.x
.serialize_with_mode(&mut result_point_data[..32], Compress::No)
.unwrap();
zero.y
.serialize_with_mode(&mut result_point_data[32..], Compress::No)
.unwrap();
assert_eq!(result_point_data, [0u8; 64]);
let p: G1Affine = PodG1(result_point_data[..64].try_into().unwrap())
.into_affine()
.unwrap();
assert_eq!(p, zero);
}
}

View file

@ -0,0 +1,159 @@
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_ec::{self, AffineRepr},
ark_ff::BigInteger256,
ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress},
};
/// Input size for the g1 multiplication operation.
pub const ALT_BN128_G1_MULTIPLICATION_INPUT_SIZE: usize =
ALT_BN128_G1_POINT_SIZE + ALT_BN128_FIELD_SIZE; // 96
/// Input size for the g2 multiplication operation.
pub const ALT_BN128_G2_MULTIPLICATION_INPUT_SIZE: usize =
ALT_BN128_G2_POINT_SIZE + ALT_BN128_FIELD_SIZE; // 160
/// The enum is used to version changes to the `alt_bn128_versioned_g1_multiplication` function.
pub enum VersionedG1Multiplication {
V0,
/// SIMD-0222 - Fix alt-bn128-multiplication Syscall Length Check
V1,
}
/// The enum is used to version changes to the `alt_bn128_versioned_g2_multiplication` function.
pub enum VersionedG2Multiplication {
V0,
}
/// The implementation of the `sol_alt_bn128_group_op` syscall G1 multiplication operation
/// (group operation index 0x02 for BE input/output, 0x82 for LE input/output).
///
/// **Security Note**
///
/// Because the BN254 G1 group has a cofactor of 1, the subgroup check is equivalent
/// to verifying the point is on the curve. This function fully validates the input point.
///
/// **Warning**
///
/// This is consensus-critical Agave validator code. Modifying this function can
/// result in a network fork. See the [crate-level documentation](crate) for strict
/// guidelines on SIMD approvals and versioning.
pub fn alt_bn128_versioned_g1_multiplication(
version: VersionedG1Multiplication,
input: &[u8],
endianness: Endianness,
) -> Option<[u8; ALT_BN128_G1_POINT_SIZE]> {
// 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,
};
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::<ALT_BN128_FIELD_SIZE, ALT_BN128_FIELD_SIZE>(fr_bytes_array)
}
Endianness::LE => fr_bytes_array,
};
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),
}
}
/// The implementation of the `sol_alt_bn128_group_op` syscall G2 multiplication operation
/// (group operation index 0x06 for BE input/output, 0x86 for LE input/output).
///
/// **Security Note**
///
/// Full subgroup (coset) validation is performed on the provided G2 point.
///
/// **Warning**
///
/// This is consensus-critical Agave validator code. Modifying this function can
/// result in a network fork. See the [crate-level documentation](crate) for strict
/// guidelines on SIMD approvals and versioning.
pub fn alt_bn128_versioned_g2_multiplication(
_version: VersionedG2Multiplication,
input: &[u8],
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::<ALT_BN128_FIELD_SIZE, ALT_BN128_FIELD_SIZE>(fr_bytes_array)
}
Endianness::LE => fr_bytes_array,
};
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::<ALT_BN128_FQ2_SIZE, ALT_BN128_G2_POINT_SIZE>(result_point_data))
}
Endianness::LE => Some(result_point_data),
}
}

View file

@ -0,0 +1,86 @@
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},
};
/// Pair element size.
pub const ALT_BN128_PAIRING_ELEMENT_SIZE: usize = ALT_BN128_G1_POINT_SIZE + ALT_BN128_G2_POINT_SIZE; // 192
/// 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.
pub enum VersionedPairing {
V0,
/// SIMD-0334 - Fix alt_bn128_pairing Syscall Length Check
V1,
}
/// 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).
///
/// **Security Note**
///
/// Full subgroup (coset) validation is performed on all provided G2 points. For G1 points,
/// because the cofactor is 1, this validation is equivalent to a standard on-curve check.
///
/// **Warning**
///
/// This is consensus-critical Agave validator code. Modifying this function can
/// result in a network fork. See the [crate-level documentation](crate) for strict
/// guidelines on SIMD approvals and versioning.
pub fn alt_bn128_versioned_pairing(
version: VersionedPairing,
input: &[u8],
endianness: Endianness,
) -> Option<[u8; ALT_BN128_PAIRING_OUTPUT_SIZE]> {
// 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()?,
),
};
vec_pairs.push((g1, g2));
}
let res = <Bn<Config> as Pairing>::multi_pairing(
vec_pairs.iter().map(|pair| pair.0),
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()?,
};
Some(output)
}