Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/accelerators/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ workspace = true

[dependencies]
# openvm
openvm-curve-utils = { workspace = true, features = ["bn254", "bls12_381"] }
openvm-ecc-guest.workspace = true
openvm-kzg = { workspace = true, features = ["use-intrinsics"] }
openvm-p256.workspace = true
openvm-pairing = { workspace = true, features = ["bn254"] }
openvm-pairing = { workspace = true, features = ["bn254", "bls12_381"] }
openvm-keccak256.workspace = true
openvm-sha2.workspace = true

Expand Down
149 changes: 149 additions & 0 deletions crates/accelerators/src/ffi/bls12_381.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! C ABI for the BLS12-381 add/MSM accelerators (EIP-2537).

use crate::{
ops,
types::{
ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point,
ZkvmBls12381PairingPair, ZkvmStatus,
},
};

/// BLS12-381 G1 point addition (precompile 0x0b, EIP-2537).
///
/// Inputs must be on the curve but, per EIP-2537 G1ADD, need not be in the
/// prime-order subgroup.
///
/// # Safety
///
/// - `p1` and `p2`, if non-NULL, must be valid for reads of 96 bytes.
/// - `result`, if non-NULL, must be valid for writes of 96 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bls12_g1_add(
p1: *const ZkvmBls12381G1Point,
p2: *const ZkvmBls12381G1Point,
result: *mut ZkvmBls12381G1Point,
) -> ZkvmStatus {
if p1.is_null() || p2.is_null() || result.is_null() {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let (p1, p2, result) = unsafe { (&*p1, &*p2, &mut *result) };
match ops::bls12_381_g1_add(p1, p2, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BLS12-381 G1 multi-scalar multiplication (precompile 0x0c, EIP-2537).
///
/// Inputs must be in the prime-order subgroup. Scalars need not be canonical.
/// `num_pairs == 0` yields the identity (all-zero) point.
///
/// # Safety
///
/// - `pairs`, if non-NULL, must be valid for reads of `num_pairs` elements.
/// - `result`, if non-NULL, must be valid for writes of 96 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bls12_g1_msm(
pairs: *const ZkvmBls12381G1MsmPair,
num_pairs: usize,
result: *mut ZkvmBls12381G1Point,
) -> ZkvmStatus {
if result.is_null() || (pairs.is_null() && num_pairs != 0) {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above for non-empty input; validity is guaranteed by the caller.
let pairs =
if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } };
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let result = unsafe { &mut *result };
match ops::bls12_381_g1_msm(pairs, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BLS12-381 G2 point addition (precompile 0x0d, EIP-2537).
///
/// Inputs must be on the curve but, per EIP-2537 G2ADD, need not be in the
/// prime-order subgroup.
///
/// # Safety
///
/// - `p1` and `p2`, if non-NULL, must be valid for reads of 192 bytes.
/// - `result`, if non-NULL, must be valid for writes of 192 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bls12_g2_add(
p1: *const ZkvmBls12381G2Point,
p2: *const ZkvmBls12381G2Point,
result: *mut ZkvmBls12381G2Point,
) -> ZkvmStatus {
if p1.is_null() || p2.is_null() || result.is_null() {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let (p1, p2, result) = unsafe { (&*p1, &*p2, &mut *result) };
match ops::bls12_381_g2_add(p1, p2, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BLS12-381 G2 multi-scalar multiplication (precompile 0x0e, EIP-2537).
///
/// Inputs must be in the prime-order subgroup. Scalars need not be canonical.
/// `num_pairs == 0` yields the identity (all-zero) point.
///
/// # Safety
///
/// - `pairs`, if non-NULL, must be valid for reads of `num_pairs` elements.
/// - `result`, if non-NULL, must be valid for writes of 192 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bls12_g2_msm(
pairs: *const ZkvmBls12381G2MsmPair,
num_pairs: usize,
result: *mut ZkvmBls12381G2Point,
) -> ZkvmStatus {
if result.is_null() || (pairs.is_null() && num_pairs != 0) {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above for non-empty input; validity is guaranteed by the caller.
let pairs =
if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } };
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let result = unsafe { &mut *result };
match ops::bls12_381_g2_msm(pairs, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BLS12-381 pairing check (precompile 0x0f, EIP-2537).
///
/// Sets `verified` to whether the product of pairings equals one. Inputs must
/// be in the prime-order subgroup; malformed points return
/// [`ZkvmStatus::Fail`]. `num_pairs == 0` verifies trivially.
///
/// # Safety
///
/// - `pairs`, if non-NULL, must be valid for reads of `num_pairs` elements.
/// - `verified`, if non-NULL, must be valid for writes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bls12_pairing(
pairs: *const ZkvmBls12381PairingPair,
num_pairs: usize,
verified: *mut bool,
) -> ZkvmStatus {
if verified.is_null() || (pairs.is_null() && num_pairs != 0) {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above for non-empty input; validity is guaranteed by the caller.
let pairs =
if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } };
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let verified = unsafe { &mut *verified };
match ops::bls12_381_pairing_check(pairs, verified) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}
90 changes: 90 additions & 0 deletions crates/accelerators/src/ffi/bn254.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! C ABI for the BN254 (alt_bn128) accelerators.

use crate::{
ops,
types::{ZkvmBn254G1Point, ZkvmBn254PairingPair, ZkvmBn254Scalar, ZkvmStatus},
};

/// BN254 G1 point addition (precompile 0x06, EIP-196).
///
/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or an input point is
/// malformed.
///
/// # Safety
///
/// - `p1` and `p2`, if non-NULL, must be valid for reads of 64 bytes.
/// - `result`, if non-NULL, must be valid for writes of 64 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bn254_g1_add(
p1: *const ZkvmBn254G1Point,
p2: *const ZkvmBn254G1Point,
result: *mut ZkvmBn254G1Point,
) -> ZkvmStatus {
if p1.is_null() || p2.is_null() || result.is_null() {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let (p1, p2, result) = unsafe { (&*p1, &*p2, &mut *result) };
match ops::bn254_g1_add(p1, p2, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BN254 G1 scalar multiplication (precompile 0x07, EIP-196).
///
/// The scalar need not be canonical.
///
/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the input point is
/// malformed.
///
/// # Safety
///
/// - `point`, if non-NULL, must be valid for reads of 64 bytes.
/// - `scalar`, if non-NULL, must be valid for reads of 32 bytes.
/// - `result`, if non-NULL, must be valid for writes of 64 bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bn254_g1_mul(
point: *const ZkvmBn254G1Point,
scalar: *const ZkvmBn254Scalar,
result: *mut ZkvmBn254G1Point,
) -> ZkvmStatus {
if point.is_null() || scalar.is_null() || result.is_null() {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let (point, scalar, result) = unsafe { (&*point, &*scalar, &mut *result) };
match ops::bn254_g1_mul(point, scalar, result) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}

/// BN254 pairing check (precompile 0x08, EIP-197).
///
/// Sets `verified` to whether the product of pairings equals one. Malformed
/// points return [`ZkvmStatus::Fail`]. `num_pairs == 0` verifies trivially.
///
/// # Safety
///
/// - `pairs`, if non-NULL, must be valid for reads of `num_pairs` elements.
/// - `verified`, if non-NULL, must be valid for writes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn zkvm_bn254_pairing(
pairs: *const ZkvmBn254PairingPair,
num_pairs: usize,
verified: *mut bool,
) -> ZkvmStatus {
if verified.is_null() || (pairs.is_null() && num_pairs != 0) {
return ZkvmStatus::Fail;
}
// SAFETY: non-NULL checked above for non-empty input; validity is guaranteed by the caller.
let pairs =
if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } };
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let verified = unsafe { &mut *verified };
match ops::bn254_pairing_check(pairs, verified) {
Ok(()) => ZkvmStatus::Ok,
Err(_) => ZkvmStatus::Fail,
}
}
4 changes: 4 additions & 0 deletions crates/accelerators/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
//! [`crate::types::ZkvmStatus`]. No other logic lives here.

mod blake2;
mod bls12_381;
mod bn254;
mod ecdsa;
mod hash;
mod kzg;
mod modexp;

pub use blake2::*;
pub use bls12_381::*;
pub use bn254::*;
pub use ecdsa::*;
pub use hash::*;
pub use kzg::*;
Expand Down
109 changes: 109 additions & 0 deletions crates/accelerators/src/ops/bls12_381/codec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! Byte codecs for BLS12-381: EIP-2537 point encodings, including the
//! on-curve and subgroup validation performed while decoding.

use openvm_curve_utils::SubgroupCheck;
use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint, Group};
use openvm_pairing::bls12_381 as bls;

use crate::{
ops::Error,
types::{ZkvmBls12381G1Point, ZkvmBls12381G2Point, ZkvmBls12381Scalar},
};

const BLS_FP_LEN: usize = 48;

#[inline]
fn read_bls_fp(input: &[u8]) -> Result<bls::Fp, Error> {
bls::Fp::from_be_bytes(input).ok_or(Error::FieldElementInvalid)
}

#[inline]
fn read_bls_fp2(c0: &[u8], c1: &[u8]) -> Result<bls::Fp2, Error> {
let real = read_bls_fp(c0)?;
let imag = read_bls_fp(c1)?;
Ok(bls::Fp2::new(real, imag))
}

#[inline]
pub(super) fn read_bls_g1_point_no_subgroup_check(
point: &ZkvmBls12381G1Point,
) -> Result<bls::G1Affine, Error> {
let px = read_bls_fp(&point.data[..BLS_FP_LEN])?;
let py = read_bls_fp(&point.data[BLS_FP_LEN..])?;
// SAFETY: `read_bls_fp` produces canonical Fp elements; `from_xy` itself checks the curve
// equation and returns `None` if `(px, py)` is not on the curve.
unsafe { bls::G1Affine::from_xy(px, py) }.ok_or(Error::PointNotOnCurve)
}

#[inline]
pub(super) fn read_bls_g1_point(point: &ZkvmBls12381G1Point) -> Result<bls::G1Affine, Error> {
let point = read_bls_g1_point_no_subgroup_check(point)?;
if point.is_in_correct_subgroup() {
Ok(point)
} else {
Err(Error::PointNotInSubgroup)
}
}

#[inline]
pub(super) fn read_bls_g2_point_no_subgroup_check(
point: &ZkvmBls12381G2Point,
) -> Result<bls::G2Affine, Error> {
let x = read_bls_fp2(&point.data[..BLS_FP_LEN], &point.data[BLS_FP_LEN..2 * BLS_FP_LEN])?;
let y =
read_bls_fp2(&point.data[2 * BLS_FP_LEN..3 * BLS_FP_LEN], &point.data[3 * BLS_FP_LEN..])?;
// SAFETY: `read_bls_fp2` produces canonical Fp2 elements; `from_xy` itself checks the curve
// equation and returns `None` if `(x, y)` is not on the twist.
unsafe { bls::G2Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve)
}

#[inline]
pub(super) fn read_bls_g2_point(point: &ZkvmBls12381G2Point) -> Result<bls::G2Affine, Error> {
let point = read_bls_g2_point_no_subgroup_check(point)?;
if point.is_in_correct_subgroup() {
Ok(point)
} else {
Err(Error::PointNotInSubgroup)
}
}

#[inline]
pub(super) fn read_bls_scalar(input: &ZkvmBls12381Scalar) -> bls::Scalar {
bls::Scalar::from_be_bytes_unchecked(&input.data)
}

#[inline]
pub(super) fn encode_bls_g1_point(point: &bls::G1Affine, output: &mut ZkvmBls12381G1Point) {
if point.is_identity() {
output.data.fill(0);
return;
}

let x_bytes: &[u8] = point.x().as_le_bytes();
let y_bytes: &[u8] = point.y().as_le_bytes();
for i in 0..BLS_FP_LEN {
output.data[i] = x_bytes[BLS_FP_LEN - 1 - i];
output.data[i + BLS_FP_LEN] = y_bytes[BLS_FP_LEN - 1 - i];
}
}

#[inline]
pub(super) fn encode_bls_g2_point(point: &bls::G2Affine, output: &mut ZkvmBls12381G2Point) {
if point.is_identity() {
output.data.fill(0);
return;
}

let x = point.x();
let y = point.y();
let x_c0 = x.c0.as_le_bytes();
let x_c1 = x.c1.as_le_bytes();
let y_c0 = y.c0.as_le_bytes();
let y_c1 = y.c1.as_le_bytes();
for i in 0..BLS_FP_LEN {
output.data[i] = x_c0[BLS_FP_LEN - 1 - i];
output.data[i + BLS_FP_LEN] = x_c1[BLS_FP_LEN - 1 - i];
output.data[i + (2 * BLS_FP_LEN)] = y_c0[BLS_FP_LEN - 1 - i];
output.data[i + (3 * BLS_FP_LEN)] = y_c1[BLS_FP_LEN - 1 - i];
}
}
Loading
Loading