mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-04 20:24:04 +00:00
[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:
parent
7170a1147d
commit
ed4a212bcb
7 changed files with 848 additions and 2 deletions
212
Cargo.lock
generated
212
Cargo.lock
generated
|
|
@ -2,6 +2,18 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
|
|
@ -35,6 +47,136 @@ version = "1.0.102"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
|
|
@ -152,6 +294,9 @@ name = "bytemuck"
|
|||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
|
|
@ -482,12 +627,44 @@ dependencies = [
|
|||
"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]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
|
|
@ -742,6 +919,15 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
|
|
@ -1232,7 +1418,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"num-traits",
|
||||
"rand 0.9.4",
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_xorshift 0.4.0",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
|
|
@ -1279,6 +1465,7 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
|
|
@ -1288,7 +1475,7 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
|
|
@ -1303,6 +1490,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
|
|
@ -1572,6 +1769,17 @@ dependencies = [
|
|||
"pairing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-bn254-syscall"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ark-bn254",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-serialize",
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solana-curve25519-cuda"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ members = [
|
|||
"curve25519/solana-ed25519",
|
||||
"experimental/ed25519-pokos",
|
||||
"syscall/bls12-381-syscall",
|
||||
"syscall/bn254-syscall",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
|
@ -15,6 +16,10 @@ license = "Apache-2.0"
|
|||
rust-version = "1.89.0" # solana platform-tools rust version
|
||||
|
||||
[workspace.dependencies]
|
||||
ark-bn254 = "0.5.0"
|
||||
ark-ec = "0.5.0"
|
||||
ark-ff = "0.5.0"
|
||||
ark-serialize = "0.5.0"
|
||||
bincode = "1"
|
||||
blst = "0.3.16"
|
||||
blstrs = "0.7.1"
|
||||
|
|
|
|||
20
syscall/bn254-syscall/Cargo.toml
Normal file
20
syscall/bn254-syscall/Cargo.toml
Normal 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"] }
|
||||
143
syscall/bn254-syscall/src/addition.rs
Normal file
143
syscall/bn254-syscall/src/addition.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
225
syscall/bn254-syscall/src/lib.rs
Normal file
225
syscall/bn254-syscall/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
159
syscall/bn254-syscall/src/multiplication.rs
Normal file
159
syscall/bn254-syscall/src/multiplication.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
86
syscall/bn254-syscall/src/pairing.rs
Normal file
86
syscall/bn254-syscall/src/pairing.rs
Normal 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)
|
||||
}
|
||||
Loading…
Reference in a new issue