diff --git a/Cargo.lock b/Cargo.lock index f307b53bb..2f475c492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6793,6 +6793,7 @@ dependencies = [ "hex-literal", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "k256 0.13.4 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", + "openvm-curve-utils", "openvm-ecc-guest", "openvm-keccak256", "openvm-kzg", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index ca0c0719a..14a75e5af 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -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 diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs new file mode 100644 index 000000000..a12a7e360 --- /dev/null +++ b/crates/accelerators/src/ffi/bls12_381.rs @@ -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, + } +} diff --git a/crates/accelerators/src/ffi/bn254.rs b/crates/accelerators/src/ffi/bn254.rs new file mode 100644 index 000000000..a866c845c --- /dev/null +++ b/crates/accelerators/src/ffi/bn254.rs @@ -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, + } +} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index fe78701e8..87dc17f4d 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -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::*; diff --git a/crates/accelerators/src/ops/bls12_381/codec.rs b/crates/accelerators/src/ops/bls12_381/codec.rs new file mode 100644 index 000000000..8d8771875 --- /dev/null +++ b/crates/accelerators/src/ops/bls12_381/codec.rs @@ -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::from_be_bytes(input).ok_or(Error::FieldElementInvalid) +} + +#[inline] +fn read_bls_fp2(c0: &[u8], c1: &[u8]) -> Result { + 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 { + 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 { + 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 { + 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 { + 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]; + } +} diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs new file mode 100644 index 000000000..569d304e2 --- /dev/null +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -0,0 +1,132 @@ +//! BLS12-381 group operations (EIP-2537). + +mod codec; + +use alloc::vec::Vec; + +use codec::{ + encode_bls_g1_point, encode_bls_g2_point, read_bls_g1_point, + read_bls_g1_point_no_subgroup_check, read_bls_g2_point, read_bls_g2_point_no_subgroup_check, + read_bls_scalar, +}; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, +}; +use openvm_pairing::{bls12_381::Bls12_381, PairingCheck}; + +use crate::{ + ops::Error, + types::{ + ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, + ZkvmBls12381PairingPair, + }, +}; + +/// BLS12-381 G1 point addition (precompile 0x0b). Inputs are `x || y`. +/// +/// Per EIP-2537 G1ADD, inputs are validated on-curve only, not for subgroup +/// membership. +pub fn bls12_381_g1_add( + p1: &ZkvmBls12381G1Point, + p2: &ZkvmBls12381G1Point, + output: &mut ZkvmBls12381G1Point, +) -> Result<(), Error> { + let p1 = read_bls_g1_point_no_subgroup_check(p1)?; + let p2 = read_bls_g1_point_no_subgroup_check(p2)?; + encode_bls_g1_point(&(p1 + p2), output); + Ok(()) +} + +/// BLS12-381 G1 multi-scalar multiplication (precompile 0x0c). +/// +/// Points must be in the prime-order subgroup; scalars need not be canonical. +/// An empty input yields the identity (all-zero) encoding. +pub fn bls12_381_g1_msm( + pairs: &[ZkvmBls12381G1MsmPair], + output: &mut ZkvmBls12381G1Point, +) -> Result<(), Error> { + if pairs.is_empty() { + output.data = [0u8; 96]; + return Ok(()); + } + + let mut points = Vec::with_capacity(pairs.len()); + let mut scalars = Vec::with_capacity(pairs.len()); + for pair in pairs { + points.push(read_bls_g1_point(&pair.point)?); + scalars.push(read_bls_scalar(&pair.scalar)); + } + encode_bls_g1_point(&Bls12_381::msm(&scalars, &points), output); + Ok(()) +} + +/// BLS12-381 G2 point addition (precompile 0x0d). +/// +/// Per EIP-2537 G2ADD, inputs are validated on-curve only, not for subgroup +/// membership. +pub fn bls12_381_g2_add( + p1: &ZkvmBls12381G2Point, + p2: &ZkvmBls12381G2Point, + output: &mut ZkvmBls12381G2Point, +) -> Result<(), Error> { + let p1 = read_bls_g2_point_no_subgroup_check(p1)?; + let p2 = read_bls_g2_point_no_subgroup_check(p2)?; + encode_bls_g2_point(&(p1 + p2), output); + Ok(()) +} + +/// BLS12-381 G2 multi-scalar multiplication (precompile 0x0e). +/// +/// Points must be in the prime-order subgroup; scalars need not be canonical. +/// An empty input yields the identity (all-zero) encoding. +pub fn bls12_381_g2_msm( + pairs: &[ZkvmBls12381G2MsmPair], + output: &mut ZkvmBls12381G2Point, +) -> Result<(), Error> { + if pairs.is_empty() { + output.data = [0u8; 192]; + return Ok(()); + } + + let mut points = Vec::with_capacity(pairs.len()); + let mut scalars = Vec::with_capacity(pairs.len()); + for pair in pairs { + points.push(read_bls_g2_point(&pair.point)?); + scalars.push(read_bls_scalar(&pair.scalar)); + } + encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points), output); + Ok(()) +} + +/// BLS12-381 pairing check (precompile 0x0f). +/// +/// Points must be in the prime-order subgroup. +pub fn bls12_381_pairing_check( + pairs: &[ZkvmBls12381PairingPair], + verified: &mut bool, +) -> Result<(), Error> { + *verified = false; + + if pairs.is_empty() { + *verified = true; + return Ok(()); + } + + let mut g1_points = Vec::with_capacity(pairs.len()); + let mut g2_points = Vec::with_capacity(pairs.len()); + + for pair in pairs { + let g1 = read_bls_g1_point(&pair.g1)?; + let g2 = read_bls_g2_point(&pair.g2)?; + + let (g1_x, g1_y) = g1.into_coords(); + let (g2_x, g2_y) = g2.into_coords(); + + g1_points.push(AffinePoint::new(g1_x, g1_y)); + g2_points.push(AffinePoint::new(g2_x, g2_y)); + } + + *verified = Bls12_381::pairing_check(&g1_points, &g2_points).is_ok(); + Ok(()) +} diff --git a/crates/accelerators/src/ops/bn254/codec.rs b/crates/accelerators/src/ops/bn254/codec.rs new file mode 100644 index 000000000..b940549ff --- /dev/null +++ b/crates/accelerators/src/ops/bn254/codec.rs @@ -0,0 +1,69 @@ +//! Byte codecs for BN254: EIP-196/197 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}; +use openvm_pairing::bn254 as bn; + +use crate::{ + ops::Error, + types::{ZkvmBn254G1Point, ZkvmBn254G2Point, ZkvmBn254Scalar}, +}; + +const BN_FQ_LEN: usize = 32; + +#[inline] +fn read_bn_fq(input: &[u8]) -> Result { + bn::Fp::from_be_bytes(&input[..BN_FQ_LEN]).ok_or(Error::FieldElementInvalid) +} + +#[inline] +fn read_bn_fq2(input: &[u8]) -> Result { + // EIP-197 encodes the imaginary part first. + let imag = read_bn_fq(&input[..BN_FQ_LEN])?; + let real = read_bn_fq(&input[BN_FQ_LEN..BN_FQ_LEN * 2])?; + Ok(bn::Fp2::new(real, imag)) +} + +#[inline] +pub(super) fn read_bn_g1_point(input: &ZkvmBn254G1Point) -> Result { + let px = read_bn_fq(&input.data[0..BN_FQ_LEN])?; + let py = read_bn_fq(&input.data[BN_FQ_LEN..])?; + // SAFETY: `read_bn_fq` produces canonical Fp elements; `from_xy` itself checks the curve + // equation and returns `None` if `(px, py)` is not on the curve. + let point = unsafe { bn::G1Affine::from_xy(px, py) }.ok_or(Error::PointNotOnCurve)?; + if point.is_in_correct_subgroup() { + Ok(point) + } else { + Err(Error::PointNotInSubgroup) + } +} + +#[inline] +pub(super) fn read_bn_g2_point(input: &ZkvmBn254G2Point) -> Result { + let x = read_bn_fq2(&input.data[..BN_FQ_LEN * 2])?; + let y = read_bn_fq2(&input.data[BN_FQ_LEN * 2..])?; + // SAFETY: `read_bn_fq2` produces canonical Fp2 elements; `from_xy` itself checks the curve + // equation and returns `None` if `(x, y)` is not on the twist. + let point = unsafe { bn::G2Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve)?; + if point.is_in_correct_subgroup() { + Ok(point) + } else { + Err(Error::PointNotInSubgroup) + } +} + +#[inline] +pub(super) fn read_bn_scalar(input: &ZkvmBn254Scalar) -> bn::Scalar { + bn::Scalar::from_be_bytes_unchecked(&input.data) +} + +#[inline] +pub(super) fn encode_bn_g1_point(point: bn::G1Affine, output: &mut ZkvmBn254G1Point) { + let x_bytes: &[u8] = point.x().as_le_bytes(); + let y_bytes: &[u8] = point.y().as_le_bytes(); + for i in 0..BN_FQ_LEN { + output.data[i] = x_bytes[BN_FQ_LEN - 1 - i]; + output.data[i + BN_FQ_LEN] = y_bytes[BN_FQ_LEN - 1 - i]; + } +} diff --git a/crates/accelerators/src/ops/bn254/mod.rs b/crates/accelerators/src/ops/bn254/mod.rs new file mode 100644 index 000000000..10041166a --- /dev/null +++ b/crates/accelerators/src/ops/bn254/mod.rs @@ -0,0 +1,71 @@ +//! BN254 (alt_bn128) group operations (EIP-196 / EIP-197). + +mod codec; + +use alloc::vec::Vec; + +use codec::{encode_bn_g1_point, read_bn_g1_point, read_bn_g2_point, read_bn_scalar}; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, +}; +use openvm_pairing::{bn254::Bn254, PairingCheck}; + +use crate::{ + ops::Error, + types::{ZkvmBn254G1Point, ZkvmBn254PairingPair, ZkvmBn254Scalar}, +}; + +/// BN254 G1 point addition (precompile 0x06). +pub fn bn254_g1_add( + p1: &ZkvmBn254G1Point, + p2: &ZkvmBn254G1Point, + output: &mut ZkvmBn254G1Point, +) -> Result<(), Error> { + let p1 = read_bn_g1_point(p1)?; + let p2 = read_bn_g1_point(p2)?; + encode_bn_g1_point(p1 + p2, output); + Ok(()) +} + +/// BN254 G1 scalar multiplication (precompile 0x07). +pub fn bn254_g1_mul( + point: &ZkvmBn254G1Point, + scalar: &ZkvmBn254Scalar, + output: &mut ZkvmBn254G1Point, +) -> Result<(), Error> { + let p = read_bn_g1_point(point)?; + let s = read_bn_scalar(scalar); + encode_bn_g1_point(Bn254::msm(&[s], &[p]), output); + Ok(()) +} + +/// BN254 pairing check (precompile 0x08). +pub fn bn254_pairing_check( + pairs: &[ZkvmBn254PairingPair], + verified: &mut bool, +) -> Result<(), Error> { + *verified = false; + + if pairs.is_empty() { + *verified = true; + return Ok(()); + } + + let mut g1_points = Vec::with_capacity(pairs.len()); + let mut g2_points = Vec::with_capacity(pairs.len()); + + for pair in pairs { + let g1 = read_bn_g1_point(&pair.g1)?; + let g2 = read_bn_g2_point(&pair.g2)?; + + let (g1_x, g1_y) = g1.into_coords(); + let (g2_x, g2_y) = g2.into_coords(); + + g1_points.push(AffinePoint::new(g1_x, g1_y)); + g2_points.push(AffinePoint::new(g2_x, g2_y)); + } + + *verified = Bn254::pairing_check(&g1_points, &g2_points).is_ok(); + Ok(()) +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 673c373fd..e65ab9083 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -5,12 +5,18 @@ //! `x_c1 || x_c0 || y_c1 || y_c0` order. mod blake2; +mod bls12_381; +mod bn254; mod ecdsa; mod hash; mod kzg; mod modexp; pub use blake2::blake2f; +pub use bls12_381::{ + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, bls12_381_pairing_check, +}; +pub use bn254::{bn254_g1_add, bn254_g1_mul, bn254_pairing_check}; pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify, secp256r1_verify}; pub use hash::{keccak256, ripemd160, sha256}; pub use kzg::kzg_point_eval; diff --git a/crates/accelerators/tests/conformance/bls12_381.rs b/crates/accelerators/tests/conformance/bls12_381.rs new file mode 100644 index 000000000..94a550857 --- /dev/null +++ b/crates/accelerators/tests/conformance/bls12_381.rs @@ -0,0 +1,227 @@ +//! BLS12-381 add/MSM/pairing conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{ + ffi::{ + zkvm_bls12_g1_add, zkvm_bls12_g1_msm, zkvm_bls12_g2_add, zkvm_bls12_g2_msm, + zkvm_bls12_pairing, + }, + ops::{ + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_pairing_check, + }, + types::{ + ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, + ZkvmBls12381PairingPair, ZkvmBls12381Scalar, ZkvmStatus, + }, +}; + +fn scalar(value: u8) -> ZkvmBls12381Scalar { + let mut scalar = ZkvmBls12381Scalar { data: [0; 32] }; + scalar.data[31] = value; + scalar +} + +/// BLS12-381 G1 generator (`x || y`). +const BLS_G1_GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { + data: hex!( + "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + "08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" + ), +}; + +/// Doubled BLS12-381 G1 generator, stripped from the EIP-2537 test-vector padding. +const BLS_G1_2GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { + data: hex!( + "0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e" + "166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28" + ), +}; + +/// BLS12-381 G2 generator in EIP-2537 order (`x_c0 || x_c1 || y_c0 || y_c1`). +const BLS_G2_GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { + data: hex!( + "024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8" + "13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e" + "0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801" + "0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be" + ), +}; + +/// Doubled BLS12-381 G2 generator, stripped from the EIP-2537 test-vector padding. +const BLS_G2_2GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { + data: hex!( + "1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053" + "0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577" + "0468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899" + "0f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3" + ), +}; + +/// BLS12-381 scalar field order minus one; multiplying by it negates a point. +const BLS_R_MINUS_1: ZkvmBls12381Scalar = ZkvmBls12381Scalar { + data: hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"), +}; + +fn neg_g1_generator() -> ZkvmBls12381G1Point { + let pairs = [ZkvmBls12381G1MsmPair { point: BLS_G1_GEN, scalar: BLS_R_MINUS_1 }]; + let mut output = ZkvmBls12381G1Point { data: [0; 96] }; + bls12_381_g1_msm(&pairs, &mut output).unwrap(); + output +} + +#[test] +fn bls12_g1_add_msm_vectors() { + let mut output = ZkvmBls12381G1Point { data: [0; 96] }; + bls12_381_g1_add(&BLS_G1_GEN, &BLS_G1_GEN, &mut output).unwrap(); + assert_eq!(output, BLS_G1_2GEN); + + let pairs = [ZkvmBls12381G1MsmPair { point: BLS_G1_GEN, scalar: scalar(2) }]; + output.data = [0; 96]; + bls12_381_g1_msm(&pairs, &mut output).unwrap(); + assert_eq!(output, BLS_G1_2GEN); + + output.data = [0xff; 96]; + bls12_381_g1_msm(&[], &mut output).unwrap(); + assert_eq!(output.data, [0u8; 96]); +} + +#[test] +fn bls12_g2_add_msm_vectors() { + let mut output = ZkvmBls12381G2Point { data: [0; 192] }; + bls12_381_g2_add(&BLS_G2_GEN, &BLS_G2_GEN, &mut output).unwrap(); + assert_eq!(output, BLS_G2_2GEN); + + let pairs = [ZkvmBls12381G2MsmPair { point: BLS_G2_GEN, scalar: scalar(2) }]; + output.data = [0; 192]; + bls12_381_g2_msm(&pairs, &mut output).unwrap(); + assert_eq!(output, BLS_G2_2GEN); + + output.data = [0xff; 192]; + bls12_381_g2_msm(&[], &mut output).unwrap(); + assert_eq!(output.data, [0u8; 192]); +} + +#[test] +fn bls12_pairing_vectors() { + let neg_g1 = neg_g1_generator(); + let pairs = [ + ZkvmBls12381PairingPair { g1: BLS_G1_GEN, g2: BLS_G2_GEN }, + ZkvmBls12381PairingPair { g1: neg_g1, g2: BLS_G2_GEN }, + ]; + let mut verified = false; + + bls12_381_pairing_check(&pairs, &mut verified).unwrap(); + assert!(verified); + + bls12_381_pairing_check(&pairs[..1], &mut verified).unwrap(); + assert!(!verified); + + bls12_381_pairing_check(&[], &mut verified).unwrap(); + assert!(verified); +} + +#[test] +fn zkvm_bls12_add_msm_smoke() { + let mut g1_output = ZkvmBls12381G1Point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_add(&BLS_G1_GEN, &BLS_G1_GEN, &mut g1_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g1_output, BLS_G1_2GEN); + + let g1_pairs = [ZkvmBls12381G1MsmPair { point: BLS_G1_GEN, scalar: scalar(2) }]; + g1_output.data = [0; 96]; + let status = unsafe { zkvm_bls12_g1_msm(g1_pairs.as_ptr(), g1_pairs.len(), &mut g1_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g1_output, BLS_G1_2GEN); + + let mut g2_output = ZkvmBls12381G2Point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_g2_add(&BLS_G2_GEN, &BLS_G2_GEN, &mut g2_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g2_output, BLS_G2_2GEN); + + let g2_pairs = [ZkvmBls12381G2MsmPair { point: BLS_G2_GEN, scalar: scalar(2) }]; + g2_output.data = [0; 192]; + let status = unsafe { zkvm_bls12_g2_msm(g2_pairs.as_ptr(), g2_pairs.len(), &mut g2_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g2_output, BLS_G2_2GEN); +} + +#[test] +fn zkvm_bls12_pairing_smoke() { + let neg_g1 = neg_g1_generator(); + let pairs = [ + ZkvmBls12381PairingPair { g1: BLS_G1_GEN, g2: BLS_G2_GEN }, + ZkvmBls12381PairingPair { g1: neg_g1, g2: BLS_G2_GEN }, + ]; + let mut verified = false; + + let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), 1, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(!verified); +} + +#[test] +fn bls12_rejects_invalid_points() { + let mut off_curve_g1 = BLS_G1_GEN; + off_curve_g1.data[95] ^= 1; + let mut g1_output = ZkvmBls12381G1Point { data: [0; 96] }; + assert!(bls12_381_g1_add(&off_curve_g1, &BLS_G1_GEN, &mut g1_output).is_err()); + + let mut off_curve_g2 = BLS_G2_GEN; + off_curve_g2.data[191] ^= 1; + let mut g2_output = ZkvmBls12381G2Point { data: [0; 192] }; + assert!(bls12_381_g2_add(&off_curve_g2, &BLS_G2_GEN, &mut g2_output).is_err()); + + let pairs = [ZkvmBls12381PairingPair { g1: off_curve_g1, g2: BLS_G2_GEN }]; + let mut verified = true; + assert!(bls12_381_pairing_check(&pairs, &mut verified).is_err()); + assert!(!verified); +} + +#[test] +fn zkvm_bls12_null_pointers() { + let mut g1_output = ZkvmBls12381G1Point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_add(core::ptr::null(), &BLS_G1_GEN, &mut g1_output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bls12_g1_add(&BLS_G1_GEN, &BLS_G1_GEN, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bls12_g1_msm(core::ptr::null(), 0, &mut g1_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g1_output.data, [0u8; 96]); + + let status = unsafe { zkvm_bls12_g1_msm(core::ptr::null(), 1, &mut g1_output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let mut g2_output = ZkvmBls12381G2Point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_g2_add(core::ptr::null(), &BLS_G2_GEN, &mut g2_output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bls12_g2_add(&BLS_G2_GEN, &BLS_G2_GEN, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bls12_g2_msm(core::ptr::null(), 0, &mut g2_output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g2_output.data, [0u8; 192]); + + let status = unsafe { zkvm_bls12_g2_msm(core::ptr::null(), 1, &mut g2_output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let pairs = [ZkvmBls12381PairingPair { g1: BLS_G1_GEN, g2: BLS_G2_GEN }]; + let mut verified = false; + + let status = unsafe { zkvm_bls12_pairing(core::ptr::null(), 0, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + let status = unsafe { zkvm_bls12_pairing(core::ptr::null(), 1, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} diff --git a/crates/accelerators/tests/conformance/bn254.rs b/crates/accelerators/tests/conformance/bn254.rs new file mode 100644 index 000000000..dcbce7b16 --- /dev/null +++ b/crates/accelerators/tests/conformance/bn254.rs @@ -0,0 +1,168 @@ +//! BN254 add/mul/pairing conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{ + ffi::{zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_pairing}, + ops::{bn254_g1_add, bn254_g1_mul, bn254_pairing_check, Error}, + types::{ + ZkvmBn254G1Point, ZkvmBn254G2Point, ZkvmBn254PairingPair, ZkvmBn254Scalar, ZkvmStatus, + }, +}; + +fn scalar(value: u8) -> ZkvmBn254Scalar { + let mut scalar = ZkvmBn254Scalar { data: [0; 32] }; + scalar.data[31] = value; + scalar +} + +/// BN254 generator (1, 2). +fn generator() -> ZkvmBn254G1Point { + let mut point = ZkvmBn254G1Point { data: [0; 64] }; + point.data[31] = 1; + point.data[63] = 2; + point +} + +/// Doubled BN254 generator, from the EIP-196 reference vectors. +const BN254_2GEN: ZkvmBn254G1Point = ZkvmBn254G1Point { + data: hex!( + "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3" + "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4" + ), +}; + +/// BN254 negated generator (1, p - 2). +const BN254_NEG_GEN: ZkvmBn254G1Point = ZkvmBn254G1Point { + data: hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45" + ), +}; + +/// BN254 G2 generator in EIP-197 order (`x_c1 || x_c0 || y_c1 || y_c0`). +const BN254_G2_GEN: ZkvmBn254G2Point = ZkvmBn254G2Point { + data: hex!( + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2" + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed" + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b" + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa" + ), +}; + +#[test] +fn bn254_add_mul_vectors() { + let point = generator(); + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + + bn254_g1_add(&point, &point, &mut output).unwrap(); + assert_eq!(output, BN254_2GEN); + + output.data = [0; 64]; + bn254_g1_mul(&point, &scalar(2), &mut output).unwrap(); + assert_eq!(output, BN254_2GEN); +} + +#[test] +fn bn254_pairing_vectors() { + let pairs = [ + ZkvmBn254PairingPair { g1: generator(), g2: BN254_G2_GEN }, + ZkvmBn254PairingPair { g1: BN254_NEG_GEN, g2: BN254_G2_GEN }, + ]; + let mut verified = false; + + bn254_pairing_check(&pairs, &mut verified).unwrap(); + assert!(verified); + + bn254_pairing_check(&pairs[..1], &mut verified).unwrap(); + assert!(!verified); + + bn254_pairing_check(&[], &mut verified).unwrap(); + assert!(verified); +} + +#[test] +fn zkvm_bn254_add_mul_smoke() { + let point = generator(); + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + + let status = unsafe { zkvm_bn254_g1_add(&point, &point, &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(output, BN254_2GEN); + + output.data = [0; 64]; + let status = unsafe { zkvm_bn254_g1_mul(&point, &scalar(2), &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(output, BN254_2GEN); +} + +#[test] +fn zkvm_bn254_pairing_smoke() { + let pairs = [ + ZkvmBn254PairingPair { g1: generator(), g2: BN254_G2_GEN }, + ZkvmBn254PairingPair { g1: BN254_NEG_GEN, g2: BN254_G2_GEN }, + ]; + let mut verified = false; + + let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), 1, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(!verified); +} + +#[test] +fn bn254_rejects_invalid_point() { + let mut not_on_curve = generator(); + not_on_curve.data[63] = 3; + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + + assert_eq!(bn254_g1_add(¬_on_curve, &generator(), &mut output), Err(Error::PointNotOnCurve)); + + let status = unsafe { zkvm_bn254_g1_mul(¬_on_curve, &scalar(2), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let pairs = [ZkvmBn254PairingPair { g1: not_on_curve, g2: BN254_G2_GEN }]; + let mut verified = true; + assert_eq!(bn254_pairing_check(&pairs, &mut verified), Err(Error::PointNotOnCurve)); + assert!(!verified); +} + +#[test] +fn zkvm_bn254_null_pointers() { + let point = generator(); + let scalar = scalar(2); + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + + let status = unsafe { zkvm_bn254_g1_add(core::ptr::null(), &point, &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_g1_add(&point, core::ptr::null(), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_g1_add(&point, &point, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_g1_mul(core::ptr::null(), &scalar, &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_g1_mul(&point, core::ptr::null(), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_g1_mul(&point, &scalar, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); + + let pairs = [ZkvmBn254PairingPair { g1: point, g2: BN254_G2_GEN }]; + let mut verified = false; + + let status = unsafe { zkvm_bn254_pairing(core::ptr::null(), 0, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + let status = unsafe { zkvm_bn254_pairing(core::ptr::null(), 1, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs index 9115352c0..0b6034a30 100644 --- a/crates/accelerators/tests/conformance/main.rs +++ b/crates/accelerators/tests/conformance/main.rs @@ -4,6 +4,8 @@ //! Modules mirror the `src/ops` layout: one file per domain. mod blake2; +mod bls12_381; +mod bn254; mod ecdsa; mod hash; mod kzg;