From bfde64f6433e7b59c7288c4464856a55ba0715be Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 17:08:45 -0400 Subject: [PATCH 01/44] feat: openvm accelerators crate --- Cargo.lock | 4 ++++ Cargo.toml | 1 + crates/accelerators/Cargo.toml | 14 ++++++++++++++ crates/accelerators/src/lib.rs | 5 +++++ crates/accelerators/src/ops/mod.rs | 19 +++++++++++++++++++ 5 files changed, 43 insertions(+) create mode 100644 crates/accelerators/Cargo.toml create mode 100644 crates/accelerators/src/lib.rs create mode 100644 crates/accelerators/src/ops/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d07466a53..f6cabdd38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6785,6 +6785,10 @@ dependencies = [ "serde", ] +[[package]] +name = "openvm-accelerators" +version = "0.4.0" + [[package]] name = "openvm-algebra-circuit" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index 19035c747..761b39e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/curve-utils", "crates/kzg", "crates/kzg/tests/programs/verify_kzg", + "crates/accelerators", ] exclude = [] resolver = "3" diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml new file mode 100644 index 000000000..1c2161b92 --- /dev/null +++ b/crates/accelerators/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "openvm-accelerators" +description = "OpenVM implementation of the zkVM Cryptographic Accelerators C Interface" +version.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = [] +std = [] diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs new file mode 100644 index 000000000..25926c35b --- /dev/null +++ b/crates/accelerators/src/lib.rs @@ -0,0 +1,5 @@ +//! OpenVM implementation of the zkVM Cryptographic Accelerators C Interface. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod ops; diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs new file mode 100644 index 000000000..102b7d077 --- /dev/null +++ b/crates/accelerators/src/ops/mod.rs @@ -0,0 +1,19 @@ +//! OpenVM-accelerated implementations of the zkVM accelerator operations. +//! +//! All functions operate on fixed-size big-endian byte encodings; BLS12-381 +//! G2 is `x_c0 || x_c1 || y_c0 || y_c1`, BN254 G2 uses the EIP-197 +//! `x_c1 || x_c0 || y_c1 || y_c0` order). + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// A field element is out of range or otherwise not a field member. + FieldElementInvalid, + /// A point encoding does not satisfy the curve equation. + PointNotOnCurve, + /// A point is on the curve but not in the prime-order subgroup. + PointNotInSubgroup, + /// A signature could not be parsed or key recovery failed. + InvalidSignature, + /// KZG commitment/proof/field-element inputs are malformed. + KzgInvalidInput, +} From d0e81c7c5c3278e6b7698033020547f13439126b Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 17:37:50 -0400 Subject: [PATCH 02/44] feat: standard interface types --- crates/accelerators/src/lib.rs | 1 + crates/accelerators/src/types.rs | 172 +++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 crates/accelerators/src/types.rs diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs index 25926c35b..49808c622 100644 --- a/crates/accelerators/src/lib.rs +++ b/crates/accelerators/src/lib.rs @@ -3,3 +3,4 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod ops; +pub mod types; diff --git a/crates/accelerators/src/types.rs b/crates/accelerators/src/types.rs new file mode 100644 index 000000000..56c2694ef --- /dev/null +++ b/crates/accelerators/src/types.rs @@ -0,0 +1,172 @@ +//! Types mirroring the complete interface standard header. + +/// Status code returned by every accelerator function (`zkvm_status`). +/// +/// Pinned to `i32` so the layout does not depend on target conventions. +#[repr(i32)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ZkvmStatus { + /// Success (`ZKVM_EOK`). + Ok = 0, + /// Failure (`ZKVM_EFAIL`). + Fail = -1, +} + +/// 16-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes16 { + pub data: [u8; 16], +} + +/// 32-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes32 { + pub data: [u8; 32], +} + +/// 48-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes48 { + pub data: [u8; 48], +} + +/// 64-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes64 { + pub data: [u8; 64], +} + +/// 96-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes96 { + pub data: [u8; 96], +} + +/// 128-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes128 { + pub data: [u8; 128], +} + +/// 192-byte buffer. +#[repr(C, align(8))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBytes192 { + pub data: [u8; 192], +} + +/* Hash types */ +pub type ZkvmKeccak256Hash = ZkvmBytes32; +pub type ZkvmSha256Hash = ZkvmBytes32; +/// 20-byte hash padded to 32 bytes, first 12 bytes zero. +pub type ZkvmRipemd160Hash = ZkvmBytes32; + +/* secp256k1 types */ +pub type ZkvmSecp256k1Hash = ZkvmBytes32; +/// `r || s`, 32 bytes each, big-endian. +pub type ZkvmSecp256k1Signature = ZkvmBytes64; +/// uncompressed `x || y`, 32 bytes each, big-endian. +pub type ZkvmSecp256k1Pubkey = ZkvmBytes64; + +/* secp256r1 (P-256) types */ +pub type ZkvmSecp256r1Hash = ZkvmBytes32; +/// `r || s`, 32 bytes each, big-endian. +pub type ZkvmSecp256r1Signature = ZkvmBytes64; +/// uncompressed `x || y`, 32 bytes each, big-endian. +pub type ZkvmSecp256r1Pubkey = ZkvmBytes64; + +/* BN254 types */ +/// `x || y`, 32 bytes each, big-endian. +pub type ZkvmBn254G1Point = ZkvmBytes64; +/// `x_c1 || x_c0 || y_c1 || y_c0` (EIP-197 order). +pub type ZkvmBn254G2Point = ZkvmBytes128; +pub type ZkvmBn254Scalar = ZkvmBytes32; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBn254PairingPair { + pub g1: ZkvmBn254G1Point, + pub g2: ZkvmBn254G2Point, +} + +/* BLS12-381 types */ +/// `x || y`, 48 bytes each, big-endian. +pub type ZkvmBls12381G1Point = ZkvmBytes96; +/// `x_c0 || x_c1 || y_c0 || y_c1` (EIP-2537 order). +pub type ZkvmBls12381G2Point = ZkvmBytes192; +pub type ZkvmBls12381Scalar = ZkvmBytes32; +pub type ZkvmBls12381Fp = ZkvmBytes48; +/// `c0 || c1`, 48 bytes each, big-endian. +pub type ZkvmBls12381Fp2 = ZkvmBytes96; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBls12381G1MsmPair { + pub point: ZkvmBls12381G1Point, + pub scalar: ZkvmBls12381Scalar, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBls12381G2MsmPair { + pub point: ZkvmBls12381G2Point, + pub scalar: ZkvmBls12381Scalar, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ZkvmBls12381PairingPair { + pub g1: ZkvmBls12381G1Point, + pub g2: ZkvmBls12381G2Point, +} + +/* BLAKE2f types */ +/// 8 × u64 little-endian. +pub type ZkvmBlake2fState = ZkvmBytes64; +/// 16 × u64 little-endian. +pub type ZkvmBlake2fMessage = ZkvmBytes128; +/// 2 × u64 little-endian. +pub type ZkvmBlake2fOffset = ZkvmBytes16; + +/* KZG types */ +pub type ZkvmKzgCommitment = ZkvmBytes48; +pub type ZkvmKzgProof = ZkvmBytes48; +pub type ZkvmKzgFieldElement = ZkvmBytes32; + +// Assert 8-byte alignment and sizes. +const _: () = { + use core::mem::{align_of, size_of}; + + assert!(size_of::() == 4); + assert!(align_of::() == 4); + + assert!(size_of::() == 16); + assert!(size_of::() == 32); + assert!(size_of::() == 48); + assert!(size_of::() == 64); + assert!(size_of::() == 96); + assert!(size_of::() == 128); + assert!(size_of::() == 192); + assert!(size_of::() == 192); + assert!(size_of::() == 128); + assert!(size_of::() == 224); + assert!(size_of::() == 288); + + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); + assert!(align_of::() == 8); +}; From ac73b9a32e8bd09b504caa638097accab268019e Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 17:40:34 -0400 Subject: [PATCH 03/44] feat(ops): keccak256 --- Cargo.lock | 4 ++++ crates/accelerators/Cargo.toml | 11 ++++++++++ crates/accelerators/src/ops/hash.rs | 9 ++++++++ crates/accelerators/src/ops/mod.rs | 6 +++++- crates/accelerators/tests/conformance/hash.rs | 21 +++++++++++++++++++ crates/accelerators/tests/conformance/main.rs | 6 ++++++ 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 crates/accelerators/src/ops/hash.rs create mode 100644 crates/accelerators/tests/conformance/hash.rs create mode 100644 crates/accelerators/tests/conformance/main.rs diff --git a/Cargo.lock b/Cargo.lock index f6cabdd38..dc028e27f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6788,6 +6788,10 @@ dependencies = [ [[package]] name = "openvm-accelerators" version = "0.4.0" +dependencies = [ + "hex-literal", + "openvm-keccak256", +] [[package]] name = "openvm-algebra-circuit" diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 1c2161b92..02fc36661 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -9,6 +9,17 @@ repository.workspace = true [lints] workspace = true +[dependencies] +# openvm +openvm-keccak256.workspace = true + +# Host implementations when not building for the zkVM guest. +[target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] +openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } + +[dev-dependencies] +hex-literal.workspace = true + [features] default = [] std = [] diff --git a/crates/accelerators/src/ops/hash.rs b/crates/accelerators/src/ops/hash.rs new file mode 100644 index 000000000..383680008 --- /dev/null +++ b/crates/accelerators/src/ops/hash.rs @@ -0,0 +1,9 @@ +//! Hash operations. + +use crate::types::ZkvmKeccak256Hash; + +/// Compute the Keccak-256 hash of `data` into `output`. +#[inline] +pub fn keccak256(data: &[u8], output: &mut ZkvmKeccak256Hash) { + openvm_keccak256::set_keccak256(data, &mut output.data); +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 102b7d077..284cf97ec 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -2,7 +2,11 @@ //! //! All functions operate on fixed-size big-endian byte encodings; BLS12-381 //! G2 is `x_c0 || x_c1 || y_c0 || y_c1`, BN254 G2 uses the EIP-197 -//! `x_c1 || x_c0 || y_c1 || y_c0` order). +//! `x_c1 || x_c0 || y_c1 || y_c0` order. + +mod hash; + +pub use hash::keccak256; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs new file mode 100644 index 000000000..a5d47585d --- /dev/null +++ b/crates/accelerators/tests/conformance/hash.rs @@ -0,0 +1,21 @@ +//! Hash conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{ops::keccak256, types::ZkvmKeccak256Hash}; + +#[test] +fn keccak256_vectors() { + let mut output = ZkvmKeccak256Hash { data: [0; 32] }; + + keccak256(b"", &mut output); + assert_eq!( + output.data, + hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + ); + + keccak256(b"abc", &mut output); + assert_eq!( + output.data, + hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + ); +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs new file mode 100644 index 000000000..12e61b30d --- /dev/null +++ b/crates/accelerators/tests/conformance/main.rs @@ -0,0 +1,6 @@ +//! Conformance tests for the accelerator operations, using official test +//! vectors and reference implementations. +//! +//! Modules mirror the `src/ops` layout: one file per domain. + +mod hash; From 396f19f523e8f605fa434e81f92c8df3a987e737 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 17:58:32 -0400 Subject: [PATCH 04/44] feat(ffi): keccak256 interface --- crates/accelerators/Cargo.toml | 5 ++- crates/accelerators/src/ffi/hash.rs | 33 ++++++++++++++++ crates/accelerators/src/ffi/mod.rs | 9 +++++ crates/accelerators/src/lib.rs | 2 + crates/accelerators/tests/conformance/hash.rs | 38 ++++++++++++++++++- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 crates/accelerators/src/ffi/hash.rs create mode 100644 crates/accelerators/src/ffi/mod.rs diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 02fc36661..d7e345fd9 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -21,5 +21,8 @@ openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } hex-literal.workspace = true [features] -default = [] +default = ["ffi"] +# The extern "C" `zkvm_*` symbols. Rust consumers that only need `ops` can +# disable this. +ffi = [] std = [] diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs new file mode 100644 index 000000000..421326bd6 --- /dev/null +++ b/crates/accelerators/src/ffi/hash.rs @@ -0,0 +1,33 @@ +//! C ABI for the hash accelerators. + +use crate::{ + ops, + types::{ZkvmKeccak256Hash, ZkvmStatus}, +}; + +/// Compute the Keccak-256 hash of `data[..len]` into `output`. +/// +/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL +/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty +/// input. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of 32 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_keccak256( + data: *const u8, + len: usize, + output: *mut ZkvmKeccak256Hash, +) -> ZkvmStatus { + if output.is_null() || (data.is_null() && len != 0) { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let output = unsafe { &mut *output }; + ops::keccak256(data, output); + ZkvmStatus::Ok +} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs new file mode 100644 index 000000000..34441d5b6 --- /dev/null +++ b/crates/accelerators/src/ffi/mod.rs @@ -0,0 +1,9 @@ +//! The `extern "C"` layer: `zkvm_*` symbols matching `zkvm_accelerators.h`. +//! +//! Every function is a thin wrapper over [`crate::ops`]: it checks pointers, +//! converts them to references, calls the operation, and maps the result to +//! [`crate::types::ZkvmStatus`]. No other logic lives here. + +mod hash; + +pub use hash::*; diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs index 49808c622..10a85ec01 100644 --- a/crates/accelerators/src/lib.rs +++ b/crates/accelerators/src/lib.rs @@ -2,5 +2,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "ffi")] +pub mod ffi; pub mod ops; pub mod types; diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs index a5d47585d..68fa8437f 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/conformance/hash.rs @@ -1,7 +1,11 @@ //! Hash conformance vectors. use hex_literal::hex; -use openvm_accelerators::{ops::keccak256, types::ZkvmKeccak256Hash}; +use openvm_accelerators::{ + ffi::zkvm_keccak256, + ops::keccak256, + types::{ZkvmKeccak256Hash, ZkvmStatus}, +}; #[test] fn keccak256_vectors() { @@ -19,3 +23,35 @@ fn keccak256_vectors() { hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") ); } + +#[test] +fn zkvm_keccak256_smoke() { + let data = *b"abc"; + let mut output = ZkvmKeccak256Hash { data: [0; 32] }; + let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + ); +} + +#[test] +fn zkvm_keccak256_null_pointers() { + let data = *b"abc"; + let mut output = ZkvmKeccak256Hash { data: [0; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_keccak256(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + ); + + let status = unsafe { zkvm_keccak256(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From 97b03efa809d5cc093d513e7710e1eae82bcbc01 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:03:55 -0400 Subject: [PATCH 05/44] feat(ops): sha256 --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 2 ++ crates/accelerators/src/ops/hash.rs | 14 ++++++++++++- crates/accelerators/src/ops/mod.rs | 2 +- crates/accelerators/tests/conformance/hash.rs | 21 +++++++++++++++++-- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc028e27f..e108c1021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6791,6 +6791,7 @@ version = "0.4.0" dependencies = [ "hex-literal", "openvm-keccak256", + "openvm-sha2", ] [[package]] diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index d7e345fd9..77b2f6e63 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -12,10 +12,12 @@ workspace = true [dependencies] # openvm openvm-keccak256.workspace = true +openvm-sha2.workspace = true # Host implementations when not building for the zkVM guest. [target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } +openvm-sha2 = { workspace = true, features = ["import_sha2"] } [dev-dependencies] hex-literal.workspace = true diff --git a/crates/accelerators/src/ops/hash.rs b/crates/accelerators/src/ops/hash.rs index 383680008..620e4112e 100644 --- a/crates/accelerators/src/ops/hash.rs +++ b/crates/accelerators/src/ops/hash.rs @@ -1,9 +1,21 @@ //! Hash operations. -use crate::types::ZkvmKeccak256Hash; +// `Digest` provides the sha2 method resolution on the host; under +// `openvm_intrinsics` the methods are inherent and the re-export does not +// exist. +#[cfg(not(openvm_intrinsics))] +use openvm_sha2::Digest as _; + +use crate::types::{ZkvmKeccak256Hash, ZkvmSha256Hash}; /// Compute the Keccak-256 hash of `data` into `output`. #[inline] pub fn keccak256(data: &[u8], output: &mut ZkvmKeccak256Hash) { openvm_keccak256::set_keccak256(data, &mut output.data); } + +/// Compute the SHA-256 hash of `data` into `output`. +#[inline] +pub fn sha256(data: &[u8], output: &mut ZkvmSha256Hash) { + output.data = openvm_sha2::Sha256::digest(data).into(); +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 284cf97ec..4d56babf0 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -6,7 +6,7 @@ mod hash; -pub use hash::keccak256; +pub use hash::{keccak256, sha256}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs index 68fa8437f..72a4cbac2 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/conformance/hash.rs @@ -3,8 +3,8 @@ use hex_literal::hex; use openvm_accelerators::{ ffi::zkvm_keccak256, - ops::keccak256, - types::{ZkvmKeccak256Hash, ZkvmStatus}, + ops::{keccak256, sha256}, + types::{ZkvmKeccak256Hash, ZkvmSha256Hash, ZkvmStatus}, }; #[test] @@ -55,3 +55,20 @@ fn zkvm_keccak256_null_pointers() { let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; assert_eq!(status, ZkvmStatus::Fail); } + +#[test] +fn sha256_vectors() { + let mut output = ZkvmSha256Hash { data: [0; 32] }; + + sha256(b"", &mut output); + assert_eq!( + output.data, + hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + ); + + sha256(b"abc", &mut output); + assert_eq!( + output.data, + hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") + ); +} From d74cad215a61e849fc4c1f5e62c96a7f5236100b Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:05:23 -0400 Subject: [PATCH 06/44] feat(ffi): sha256 interface --- crates/accelerators/src/ffi/hash.rs | 29 +++++++++++++++- crates/accelerators/tests/conformance/hash.rs | 34 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs index 421326bd6..072f58065 100644 --- a/crates/accelerators/src/ffi/hash.rs +++ b/crates/accelerators/src/ffi/hash.rs @@ -2,7 +2,7 @@ use crate::{ ops, - types::{ZkvmKeccak256Hash, ZkvmStatus}, + types::{ZkvmKeccak256Hash, ZkvmSha256Hash, ZkvmStatus}, }; /// Compute the Keccak-256 hash of `data[..len]` into `output`. @@ -31,3 +31,30 @@ pub unsafe extern "C" fn zkvm_keccak256( ops::keccak256(data, output); ZkvmStatus::Ok } + +/// Compute the SHA-256 hash of `data[..len]` into `output`. +/// +/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL +/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty +/// input. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of 32 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_sha256( + data: *const u8, + len: usize, + output: *mut ZkvmSha256Hash, +) -> ZkvmStatus { + if output.is_null() || (data.is_null() && len != 0) { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let output = unsafe { &mut *output }; + ops::sha256(data, output); + ZkvmStatus::Ok +} diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs index 72a4cbac2..ae0fdae5d 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/conformance/hash.rs @@ -2,7 +2,7 @@ use hex_literal::hex; use openvm_accelerators::{ - ffi::zkvm_keccak256, + ffi::{zkvm_keccak256, zkvm_sha256}, ops::{keccak256, sha256}, types::{ZkvmKeccak256Hash, ZkvmSha256Hash, ZkvmStatus}, }; @@ -72,3 +72,35 @@ fn sha256_vectors() { hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") ); } + +#[test] +fn zkvm_sha256_smoke() { + let data = *b"abc"; + let mut output = ZkvmSha256Hash { data: [0; 32] }; + let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") + ); +} + +#[test] +fn zkvm_sha256_null_pointers() { + let data = *b"abc"; + let mut output = ZkvmSha256Hash { data: [0; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_sha256(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + ); + + let status = unsafe { zkvm_sha256(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From 3955f08bf36a03d9552f8fdf4fc197d106785b4f Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:11:08 -0400 Subject: [PATCH 07/44] feat(ops): ripemd160 --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 3 +++ crates/accelerators/src/ops/hash.rs | 23 +++++++++++++------ crates/accelerators/src/ops/mod.rs | 2 +- crates/accelerators/tests/conformance/hash.rs | 22 ++++++++++++++++-- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e108c1021..1833a04ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6792,6 +6792,7 @@ dependencies = [ "hex-literal", "openvm-keccak256", "openvm-sha2", + "ripemd", ] [[package]] diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 77b2f6e63..c353aa641 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -14,6 +14,9 @@ workspace = true openvm-keccak256.workspace = true openvm-sha2.workspace = true +# crypto +ripemd = { version = "0.1.3", default-features = false } + # Host implementations when not building for the zkVM guest. [target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } diff --git a/crates/accelerators/src/ops/hash.rs b/crates/accelerators/src/ops/hash.rs index 620e4112e..3bae39f29 100644 --- a/crates/accelerators/src/ops/hash.rs +++ b/crates/accelerators/src/ops/hash.rs @@ -1,12 +1,6 @@ //! Hash operations. -// `Digest` provides the sha2 method resolution on the host; under -// `openvm_intrinsics` the methods are inherent and the re-export does not -// exist. -#[cfg(not(openvm_intrinsics))] -use openvm_sha2::Digest as _; - -use crate::types::{ZkvmKeccak256Hash, ZkvmSha256Hash}; +use crate::types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash}; /// Compute the Keccak-256 hash of `data` into `output`. #[inline] @@ -17,5 +11,20 @@ pub fn keccak256(data: &[u8], output: &mut ZkvmKeccak256Hash) { /// Compute the SHA-256 hash of `data` into `output`. #[inline] pub fn sha256(data: &[u8], output: &mut ZkvmSha256Hash) { + #[cfg(not(openvm_intrinsics))] + use openvm_sha2::Digest; output.data = openvm_sha2::Sha256::digest(data).into(); } + +/// Compute the RIPEMD-160 hash of `data` into `output`. +/// +/// The 20-byte digest is written to `output.data[12..]`; the first 12 bytes +/// are zeroed, matching the EVM word layout. +#[inline] +pub fn ripemd160(data: &[u8], output: &mut ZkvmRipemd160Hash) { + use ripemd::Digest; + let mut hasher = ripemd::Ripemd160::new(); + hasher.update(data); + output.data[..12].fill(0); + hasher.finalize_into((&mut output.data[12..]).into()); +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 4d56babf0..72a5da521 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -6,7 +6,7 @@ mod hash; -pub use hash::{keccak256, sha256}; +pub use hash::{keccak256, ripemd160, sha256}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs index ae0fdae5d..4b0a09c53 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/conformance/hash.rs @@ -3,8 +3,8 @@ use hex_literal::hex; use openvm_accelerators::{ ffi::{zkvm_keccak256, zkvm_sha256}, - ops::{keccak256, sha256}, - types::{ZkvmKeccak256Hash, ZkvmSha256Hash, ZkvmStatus}, + ops::{keccak256, ripemd160, sha256}, + types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus}, }; #[test] @@ -104,3 +104,21 @@ fn zkvm_sha256_null_pointers() { let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; assert_eq!(status, ZkvmStatus::Fail); } + +#[test] +fn ripemd160_vectors() { + // Start from a dirty buffer to check the 12-byte zero padding is written. + let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; + + ripemd160(b"", &mut output); + assert_eq!( + output.data, + hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") + ); + + ripemd160(b"abc", &mut output); + assert_eq!( + output.data, + hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") + ); +} From d7439566b8dd237747326e50556dab4791a80ede Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:12:52 -0400 Subject: [PATCH 08/44] feat(ffi): ripemd160 interface --- crates/accelerators/src/ffi/hash.rs | 32 ++++++++++++++++- crates/accelerators/tests/conformance/hash.rs | 34 ++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs index 072f58065..0deb16129 100644 --- a/crates/accelerators/src/ffi/hash.rs +++ b/crates/accelerators/src/ffi/hash.rs @@ -2,7 +2,7 @@ use crate::{ ops, - types::{ZkvmKeccak256Hash, ZkvmSha256Hash, ZkvmStatus}, + types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus}, }; /// Compute the Keccak-256 hash of `data[..len]` into `output`. @@ -58,3 +58,33 @@ pub unsafe extern "C" fn zkvm_sha256( ops::sha256(data, output); ZkvmStatus::Ok } + +/// Compute the RIPEMD-160 hash of `data[..len]` into `output`. +/// +/// The 20-byte digest is written to `output.data[12..]`; the first 12 bytes +/// are zeroed. +/// +/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL +/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty +/// input. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of 32 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_ripemd160( + data: *const u8, + len: usize, + output: *mut ZkvmRipemd160Hash, +) -> ZkvmStatus { + if output.is_null() || (data.is_null() && len != 0) { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let output = unsafe { &mut *output }; + ops::ripemd160(data, output); + ZkvmStatus::Ok +} diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/conformance/hash.rs index 4b0a09c53..339bc3f79 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/conformance/hash.rs @@ -2,7 +2,7 @@ use hex_literal::hex; use openvm_accelerators::{ - ffi::{zkvm_keccak256, zkvm_sha256}, + ffi::{zkvm_keccak256, zkvm_ripemd160, zkvm_sha256}, ops::{keccak256, ripemd160, sha256}, types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus}, }; @@ -122,3 +122,35 @@ fn ripemd160_vectors() { hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") ); } + +#[test] +fn zkvm_ripemd160_smoke() { + let data = *b"abc"; + let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; + let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") + ); +} + +#[test] +fn zkvm_ripemd160_null_pointers() { + let data = *b"abc"; + let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_ripemd160(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + output.data, + hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") + ); + + let status = unsafe { zkvm_ripemd160(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From ecdf4899640fd175dc1f1e1fed3944e61e9ec950 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:15:39 -0400 Subject: [PATCH 09/44] ci: accelerators tests and guest build --- .github/workflows/tests-accelerators.yml | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/tests-accelerators.yml diff --git a/.github/workflows/tests-accelerators.yml b/.github/workflows/tests-accelerators.yml new file mode 100644 index 000000000..e4907dafa --- /dev/null +++ b/.github/workflows/tests-accelerators.yml @@ -0,0 +1,67 @@ +name: OpenVM Accelerators Tests + +on: + pull_request: + paths: + - "crates/accelerators/**" + - "Cargo.toml" + - "Cargo.lock" + - ".github/workflows/tests-accelerators.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: "true" + +jobs: + test: + runs-on: + - runs-on=${{ github.run_id }} + - runner=64cpu-linux-arm64 + - extras=s3-cache + + steps: + - uses: runs-on/action@v2 + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - uses: taiki-e/install-action@nextest + + - name: Run tests + run: cargo nextest run -p openvm-accelerators + + guest-build: + runs-on: + - runs-on=${{ github.run_id }} + - runner=64cpu-linux-arm64 + - extras=s3-cache + + steps: + - uses: runs-on/action@v2 + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.91.1" + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Cache openvm toolchain + uses: actions/cache@v4 + with: + path: ~/.openvm/toolchains + key: openvm-toolchain-openvm-1.94.0-${{ runner.os }}-${{ runner.arch }} + + - name: Install OpenVM CLI + run: | + cargo install --git https://github.com/openvm-org/openvm.git --branch develop-v2.1.0 --locked --force cargo-openvm + cargo openvm toolchain install + + - name: Build for the guest target + run: | + cd crates/accelerators + cargo openvm build --no-transpile From 4f3e13ab501e7244079712ff6b203bc6ff5ff22c Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:34:02 -0400 Subject: [PATCH 10/44] feat(ops): blake2 --- crates/accelerators/src/ops/blake2/mod.rs | 77 +++++++++++++++++ .../accelerators/src/ops/blake2/portable.rs | 76 +++++++++++++++++ crates/accelerators/src/ops/mod.rs | 4 + .../accelerators/tests/conformance/blake2.rs | 83 +++++++++++++++++++ crates/accelerators/tests/conformance/main.rs | 1 + 5 files changed, 241 insertions(+) create mode 100644 crates/accelerators/src/ops/blake2/mod.rs create mode 100644 crates/accelerators/src/ops/blake2/portable.rs create mode 100644 crates/accelerators/tests/conformance/blake2.rs diff --git a/crates/accelerators/src/ops/blake2/mod.rs b/crates/accelerators/src/ops/blake2/mod.rs new file mode 100644 index 000000000..06d0c1866 --- /dev/null +++ b/crates/accelerators/src/ops/blake2/mod.rs @@ -0,0 +1,77 @@ +//! BLAKE2b compression function F (EIP-152). +//! +//! Vendored from revm-precompile, which adapted the compression function from +//! [`blake2b_simd`](https://github.com/oconnor663/blake2_simd) (MIT license) +//! for EIP-152 variable round counts. + +mod portable; + +use crate::{ + ops::Error, + types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, +}; + +type Word = u64; + +const IV: [Word; 8] = [ + 0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179, +]; + +// SIGMA has spec period 10 (RFC 7693 §2.7). BLAKE2b runs 12 rounds by reusing +// SIGMA[0]/SIGMA[1] for rounds 10/11; for EIP-152's variable round count we +// must index with `r % 10`, not `r % 12`. +const SIGMA: [[u8; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +/// Apply the BLAKE2 compression function F to the state vector `h` in place. +/// +/// `h`, `m` and `t` hold little-endian words; `f` is the final-block +/// indicator and must be `0` or `1`. +pub fn blake2f( + rounds: u32, + h: &mut ZkvmBlake2fState, + m: &ZkvmBlake2fMessage, + t: &ZkvmBlake2fOffset, + f: u8, +) -> Result<(), Error> { + if f > 1 { + return Err(Error::InvalidFinalFlag); + } + + let mut state = [0u64; 8]; + for (word, chunk) in state.iter_mut().zip(h.data.chunks_exact(8)) { + *word = u64::from_le_bytes(chunk.try_into().unwrap()); + } + let mut message = [0u64; 16]; + for (word, chunk) in message.iter_mut().zip(m.data.chunks_exact(8)) { + *word = u64::from_le_bytes(chunk.try_into().unwrap()); + } + let offset = [ + u64::from_le_bytes(t.data[..8].try_into().unwrap()), + u64::from_le_bytes(t.data[8..].try_into().unwrap()), + ]; + + portable::compress(rounds, &mut state, &message, &offset, f == 1); + + for (chunk, word) in h.data.chunks_exact_mut(8).zip(state.iter()) { + chunk.copy_from_slice(&word.to_le_bytes()); + } + Ok(()) +} diff --git a/crates/accelerators/src/ops/blake2/portable.rs b/crates/accelerators/src/ops/blake2/portable.rs new file mode 100644 index 000000000..2e3a52b7f --- /dev/null +++ b/crates/accelerators/src/ops/blake2/portable.rs @@ -0,0 +1,76 @@ +// Adapted from https://github.com/oconnor663/blake2_simd +// Copyright (c) 2018 Jack O'Connor +// Licensed under the MIT license + +use super::{Word, IV, SIGMA}; + +// G is the mixing function, called eight times per round in the compression +// function. V is the 16-word state vector of the compression function, usually +// described as a 4x4 matrix. A, B, C, and D are the mixing indices, set by the +// caller first to the four columns of V, and then to its four diagonals. X and +// Y are words of input, chosen by the caller according to the message +// schedule, SIGMA. +#[inline(always)] +const fn g(v: &mut [Word; 16], a: usize, b: usize, c: usize, d: usize, x: Word, y: Word) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +#[inline(always)] +const fn round(r: usize, m: &[Word; 16], v: &mut [Word; 16]) { + // Select the message schedule based on the round. + let s = SIGMA[r % 10]; + + // Mix the columns. + g(v, 0, 4, 8, 12, m[s[0] as usize], m[s[1] as usize]); + g(v, 1, 5, 9, 13, m[s[2] as usize], m[s[3] as usize]); + g(v, 2, 6, 10, 14, m[s[4] as usize], m[s[5] as usize]); + g(v, 3, 7, 11, 15, m[s[6] as usize], m[s[7] as usize]); + + // Mix the rows. + g(v, 0, 5, 10, 15, m[s[8] as usize], m[s[9] as usize]); + g(v, 1, 6, 11, 12, m[s[10] as usize], m[s[11] as usize]); + g(v, 2, 7, 8, 13, m[s[12] as usize], m[s[13] as usize]); + g(v, 3, 4, 9, 14, m[s[14] as usize], m[s[15] as usize]); +} + +pub(super) fn compress(rounds: u32, words: &mut [Word; 8], m: &[Word; 16], t: &[Word; 2], f: bool) { + // Initialize the compression state. + let mut v = [ + words[0], + words[1], + words[2], + words[3], + words[4], + words[5], + words[6], + words[7], + IV[0], + IV[1], + IV[2], + IV[3], + IV[4] ^ t[0], + IV[5] ^ t[1], + IV[6] ^ if f { !0 } else { 0 }, + IV[7], + ]; + + for i in 0..rounds as usize { + round(i, m, &mut v); + } + + words[0] ^= v[0] ^ v[8]; + words[1] ^= v[1] ^ v[9]; + words[2] ^= v[2] ^ v[10]; + words[3] ^= v[3] ^ v[11]; + words[4] ^= v[4] ^ v[12]; + words[5] ^= v[5] ^ v[13]; + words[6] ^= v[6] ^ v[14]; + words[7] ^= v[7] ^ v[15]; +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 72a5da521..c9c9fedae 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -4,8 +4,10 @@ //! G2 is `x_c0 || x_c1 || y_c0 || y_c1`, BN254 G2 uses the EIP-197 //! `x_c1 || x_c0 || y_c1 || y_c0` order. +mod blake2; mod hash; +pub use blake2::blake2f; pub use hash::{keccak256, ripemd160, sha256}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -16,6 +18,8 @@ pub enum Error { PointNotOnCurve, /// A point is on the curve but not in the prime-order subgroup. PointNotInSubgroup, + /// The BLAKE2f final-block flag is neither 0 nor 1. + InvalidFinalFlag, /// A signature could not be parsed or key recovery failed. InvalidSignature, /// KZG commitment/proof/field-element inputs are malformed. diff --git a/crates/accelerators/tests/conformance/blake2.rs b/crates/accelerators/tests/conformance/blake2.rs new file mode 100644 index 000000000..55054f2ab --- /dev/null +++ b/crates/accelerators/tests/conformance/blake2.rs @@ -0,0 +1,83 @@ +//! BLAKE2f conformance: the official EIP-152 test vectors 4-7. + +use hex_literal::hex; +use openvm_accelerators::{ + ops::{blake2f, Error}, + types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, +}; + +/// EIP-152 vectors 4-7 share the same h, m and t inputs. +const H: [u8; 64] = hex!( + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5" + "d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b" +); +const T: [u8; 16] = hex!("03000000000000000000000000000000"); + +fn m() -> ZkvmBlake2fMessage { + let mut m = ZkvmBlake2fMessage { data: [0; 128] }; + m.data[..3].copy_from_slice(b"abc"); + m +} + +fn check(rounds: u32, f: u8, expected: [u8; 64]) { + let mut h = ZkvmBlake2fState { data: H }; + blake2f(rounds, &mut h, &m(), &ZkvmBlake2fOffset { data: T }, f).unwrap(); + assert_eq!(h.data, expected, "rounds={rounds}, f={f}"); +} + +#[test] +fn blake2f_eip152_vector_4_zero_rounds() { + check( + 0, + 1, + hex!( + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5" + "d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b" + ), + ); +} + +#[test] +fn blake2f_eip152_vector_5_twelve_rounds() { + check( + 12, + 1, + hex!( + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" + "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923" + ), + ); +} + +#[test] +fn blake2f_eip152_vector_6_no_final_flag() { + check( + 12, + 0, + hex!( + "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d28752" + "98743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735" + ), + ); +} + +#[test] +fn blake2f_eip152_vector_7_one_round() { + check( + 1, + 1, + hex!( + "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fb" + "a551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421" + ), + ); +} + +#[test] +fn blake2f_invalid_final_flag() { + let mut h = ZkvmBlake2fState { data: H }; + let result = blake2f(12, &mut h, &m(), &ZkvmBlake2fOffset { data: T }, 2); + assert_eq!(result, Err(Error::InvalidFinalFlag)); + // The state must be untouched on failure. + assert_eq!(h.data, H); +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs index 12e61b30d..acfab0996 100644 --- a/crates/accelerators/tests/conformance/main.rs +++ b/crates/accelerators/tests/conformance/main.rs @@ -3,4 +3,5 @@ //! //! Modules mirror the `src/ops` layout: one file per domain. +mod blake2; mod hash; From 7ab50015e7c33734a5a7a01252a4431dd921a475 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 18:37:25 -0400 Subject: [PATCH 11/44] feat(ffi): blake2 interface --- crates/accelerators/src/ffi/blake2.rs | 35 ++++++++++++++++ crates/accelerators/src/ffi/mod.rs | 2 + .../accelerators/tests/conformance/blake2.rs | 42 ++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 crates/accelerators/src/ffi/blake2.rs diff --git a/crates/accelerators/src/ffi/blake2.rs b/crates/accelerators/src/ffi/blake2.rs new file mode 100644 index 000000000..fb43c0ff4 --- /dev/null +++ b/crates/accelerators/src/ffi/blake2.rs @@ -0,0 +1,35 @@ +//! C ABI for the BLAKE2 compression function. + +use crate::{ + ops, + types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus}, +}; + +/// Apply the BLAKE2 compression function F (EIP-152) to `h` in place. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL, or if `f` is neither +/// 0 nor 1. +/// +/// # Safety +/// +/// - `h`, if non-NULL, must be valid for reads and writes of 64 bytes. +/// - `m`, if non-NULL, must be valid for reads of 128 bytes. +/// - `t`, if non-NULL, must be valid for reads of 16 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_blake2f( + rounds: u32, + h: *mut ZkvmBlake2fState, + m: *const ZkvmBlake2fMessage, + t: *const ZkvmBlake2fOffset, + f: u8, +) -> ZkvmStatus { + if h.is_null() || m.is_null() || t.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (h, m, t) = unsafe { (&mut *h, &*m, &*t) }; + match ops::blake2f(rounds, h, m, t, f) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index 34441d5b6..8e9ba6c90 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -4,6 +4,8 @@ //! converts them to references, calls the operation, and maps the result to //! [`crate::types::ZkvmStatus`]. No other logic lives here. +mod blake2; mod hash; +pub use blake2::*; pub use hash::*; diff --git a/crates/accelerators/tests/conformance/blake2.rs b/crates/accelerators/tests/conformance/blake2.rs index 55054f2ab..05f147c22 100644 --- a/crates/accelerators/tests/conformance/blake2.rs +++ b/crates/accelerators/tests/conformance/blake2.rs @@ -2,8 +2,9 @@ use hex_literal::hex; use openvm_accelerators::{ + ffi::zkvm_blake2f, ops::{blake2f, Error}, - types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, + types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus}, }; /// EIP-152 vectors 4-7 share the same h, m and t inputs. @@ -81,3 +82,42 @@ fn blake2f_invalid_final_flag() { // The state must be untouched on failure. assert_eq!(h.data, H); } + +#[test] +fn zkvm_blake2f_smoke() { + let mut h = ZkvmBlake2fState { data: H }; + let m = m(); + let t = ZkvmBlake2fOffset { data: T }; + + let status = unsafe { zkvm_blake2f(12, &mut h, &m, &t, 1) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!( + h.data, + hex!( + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" + "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923" + ) + ); + + // An invalid final flag maps to the failure status. + let status = unsafe { zkvm_blake2f(12, &mut h, &m, &t, 2) }; + assert_eq!(status, ZkvmStatus::Fail); +} + +#[test] +fn zkvm_blake2f_null_pointers() { + let mut h = ZkvmBlake2fState { data: H }; + let m = m(); + let t = ZkvmBlake2fOffset { data: T }; + + let status = unsafe { zkvm_blake2f(12, core::ptr::null_mut(), &m, &t, 1) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_blake2f(12, &mut h, core::ptr::null(), &t, 1) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_blake2f(12, &mut h, &m, core::ptr::null(), 1) }; + assert_eq!(status, ZkvmStatus::Fail); + // The state must be untouched when a pointer is NULL. + assert_eq!(h.data, H); +} From b17d7a8b4c9f60140adf5463a9229272f0a8815d Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:24:44 -0400 Subject: [PATCH 12/44] feat(ops): secp256k1 ecrecover and verify --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 1 + crates/accelerators/src/ops/ecdsa.rs | 61 ++++++++++++++++++++++++++++ crates/accelerators/src/ops/mod.rs | 2 + 4 files changed, 65 insertions(+) create mode 100644 crates/accelerators/src/ops/ecdsa.rs diff --git a/Cargo.lock b/Cargo.lock index 1833a04ce..7cd4a9eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6790,6 +6790,7 @@ name = "openvm-accelerators" version = "0.4.0" dependencies = [ "hex-literal", + "k256 0.13.4 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "openvm-keccak256", "openvm-sha2", "ripemd", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index c353aa641..fc6987841 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] # openvm +openvm-k256.workspace = true openvm-keccak256.workspace = true openvm-sha2.workspace = true diff --git a/crates/accelerators/src/ops/ecdsa.rs b/crates/accelerators/src/ops/ecdsa.rs new file mode 100644 index 000000000..f7c5c7ad0 --- /dev/null +++ b/crates/accelerators/src/ops/ecdsa.rs @@ -0,0 +1,61 @@ +//! ECDSA operations. + +use openvm_k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; + +use crate::{ + ops::Error, + types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature}, +}; + +/// Recover the uncompressed secp256k1 public key from an ECDSA signature +/// over `msg` into `output`. +/// +/// Both low-s and high-s signatures are accepted. +pub fn secp256k1_ecrecover( + msg: &ZkvmSecp256k1Hash, + sig: &ZkvmSecp256k1Signature, + mut recid: u8, + output: &mut ZkvmSecp256k1Pubkey, +) -> Result<(), Error> { + let mut signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; + // k256 requires a low-s signature for recovery; normalizing flips the + // recovery id parity but recovers the same key. + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + recid ^= 1; + } + let recovery_id = RecoveryId::from_byte(recid).ok_or(Error::InvalidSignature)?; + + let key = + VerifyingKey::recover_from_prehash_noverify(&msg.data, &signature.to_bytes(), recovery_id) + .map_err(|_| Error::InvalidSignature)?; + + let point = key.to_encoded_point(false); + output.data.copy_from_slice(&point.as_bytes()[1..65]); + Ok(()) +} + +/// Verify an ECDSA signature over secp256k1 against an uncompressed public +/// key, writing the result to `verified`. +/// +/// Both low-s and high-s signatures are accepted. +pub fn secp256k1_verify( + msg: &ZkvmSecp256k1Hash, + sig: &ZkvmSecp256k1Signature, + pubkey: &ZkvmSecp256k1Pubkey, + verified: &mut bool, +) -> Result<(), Error> { + *verified = false; + + let mut sec1 = [0u8; 65]; + sec1[0] = 0x04; + sec1[1..].copy_from_slice(&pubkey.data); + let key = VerifyingKey::from_sec1_bytes(&sec1).map_err(|_| Error::PointNotOnCurve)?; + let mut signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; + // k256 rejects high-s signatures in verification. Normalize to accept both forms. + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + } + *verified = key.verify_prehash(&msg.data, &signature).is_ok(); + Ok(()) +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index c9c9fedae..bbed3cd18 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -5,9 +5,11 @@ //! `x_c1 || x_c0 || y_c1 || y_c0` order. mod blake2; +mod ecdsa; mod hash; pub use blake2::blake2f; +pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify}; pub use hash::{keccak256, ripemd160, sha256}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] From b573278d6a6e4323a146430a4c9c8ad8c7b401db Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:28:50 -0400 Subject: [PATCH 13/44] feat(ffi): secp256k1 ecrecover and verify interfaces --- crates/accelerators/src/ffi/ecdsa.rs | 66 ++++++++++++++++++++++++++++ crates/accelerators/src/ffi/mod.rs | 2 + 2 files changed, 68 insertions(+) create mode 100644 crates/accelerators/src/ffi/ecdsa.rs diff --git a/crates/accelerators/src/ffi/ecdsa.rs b/crates/accelerators/src/ffi/ecdsa.rs new file mode 100644 index 000000000..bf9563c4f --- /dev/null +++ b/crates/accelerators/src/ffi/ecdsa.rs @@ -0,0 +1,66 @@ +//! C ABI for the ECDSA accelerators. + +use crate::{ + ops, + types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmStatus}, +}; + +/// Recover the uncompressed secp256k1 public key from an ECDSA signature +/// over `msg` into `output`. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL, the signature cannot +/// be parsed, or the recovery id is invalid. +/// +/// # Safety +/// +/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. +/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. +/// - `output`, if non-NULL, must be valid for writes of 64 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( + msg: *const ZkvmSecp256k1Hash, + sig: *const ZkvmSecp256k1Signature, + recid: u8, + output: *mut ZkvmSecp256k1Pubkey, +) -> ZkvmStatus { + if msg.is_null() || sig.is_null() || output.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (msg, sig, output) = unsafe { (&*msg, &*sig, &mut *output) }; + match ops::secp256k1_ecrecover(msg, sig, recid, output) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} + +/// Verify an ECDSA signature over secp256k1 against an uncompressed public +/// key, writing the result to `verified`. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are +/// malformed; `verified` is `false` when a well-formed signature does not +/// verify. +/// +/// # Safety +/// +/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. +/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. +/// - `pubkey`, if non-NULL, must be valid for reads of 64 bytes. +/// - `verified`, if non-NULL, must be valid for writes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256k1_verify( + msg: *const ZkvmSecp256k1Hash, + sig: *const ZkvmSecp256k1Signature, + pubkey: *const ZkvmSecp256k1Pubkey, + verified: *mut bool, +) -> ZkvmStatus { + if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (msg, sig, pubkey, verified) = unsafe { (&*msg, &*sig, &*pubkey, &mut *verified) }; + match ops::secp256k1_verify(msg, sig, pubkey, verified) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index 8e9ba6c90..3f82ef4ea 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -5,7 +5,9 @@ //! [`crate::types::ZkvmStatus`]. No other logic lives here. mod blake2; +mod ecdsa; mod hash; pub use blake2::*; +pub use ecdsa::*; pub use hash::*; From 2b128590ce956e502a536184033ca77d1a657999 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:36:56 -0400 Subject: [PATCH 14/44] feat(ops): secp256r1 verify --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 1 + crates/accelerators/src/ops/ecdsa.rs | 28 +++++++++++++++++++++++++++- crates/accelerators/src/ops/mod.rs | 2 +- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cd4a9eb4..b7b7ecc15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6793,6 +6793,7 @@ dependencies = [ "k256 0.13.4 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "openvm-keccak256", "openvm-sha2", + "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "ripemd", ] diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index fc6987841..5b7c7afe2 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # openvm openvm-k256.workspace = true +openvm-p256.workspace = true openvm-keccak256.workspace = true openvm-sha2.workspace = true diff --git a/crates/accelerators/src/ops/ecdsa.rs b/crates/accelerators/src/ops/ecdsa.rs index f7c5c7ad0..40e454bf8 100644 --- a/crates/accelerators/src/ops/ecdsa.rs +++ b/crates/accelerators/src/ops/ecdsa.rs @@ -4,7 +4,10 @@ use openvm_k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signatu use crate::{ ops::Error, - types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature}, + types::{ + ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, + ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, + }, }; /// Recover the uncompressed secp256k1 public key from an ECDSA signature @@ -59,3 +62,26 @@ pub fn secp256k1_verify( *verified = key.verify_prehash(&msg.data, &signature).is_ok(); Ok(()) } + +/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed +/// public key, writing the result to `verified`. +pub fn secp256r1_verify( + msg: &ZkvmSecp256r1Hash, + sig: &ZkvmSecp256r1Signature, + pubkey: &ZkvmSecp256r1Pubkey, + verified: &mut bool, +) -> Result<(), Error> { + use openvm_p256::{ + ecdsa::{Signature, VerifyingKey}, + EncodedPoint, + }; + + *verified = false; + + let encoded_point = EncodedPoint::from_untagged_bytes(&pubkey.data.into()); + let key = + VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| Error::PointNotOnCurve)?; + let signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; + *verified = key.verify_prehash(&msg.data, &signature).is_ok(); + Ok(()) +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index bbed3cd18..f269b7319 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -9,7 +9,7 @@ mod ecdsa; mod hash; pub use blake2::blake2f; -pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify}; +pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify, secp256r1_verify}; pub use hash::{keccak256, ripemd160, sha256}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] From 1d16948fdf91b80a50e4edc3fbb5c396bf5c368a Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:37:18 -0400 Subject: [PATCH 15/44] feat(tests): tests for secp256r1 verify --- .../accelerators/tests/conformance/ecdsa.rs | 60 +++++++++++++++++++ crates/accelerators/tests/conformance/main.rs | 1 + 2 files changed, 61 insertions(+) create mode 100644 crates/accelerators/tests/conformance/ecdsa.rs diff --git a/crates/accelerators/tests/conformance/ecdsa.rs b/crates/accelerators/tests/conformance/ecdsa.rs new file mode 100644 index 000000000..705921151 --- /dev/null +++ b/crates/accelerators/tests/conformance/ecdsa.rs @@ -0,0 +1,60 @@ +//! ECDSA conformance vectors. +//! +//! Tested with vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors. + +use hex_literal::hex; +use openvm_accelerators::{ + ops::{secp256r1_verify, Error}, + types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature}, +}; + +/// Splits a 160-byte P256VERIFY input (msg || sig || pk) into its parts. +fn parts(input: &[u8; 160]) -> (ZkvmSecp256r1Hash, ZkvmSecp256r1Signature, ZkvmSecp256r1Pubkey) { + ( + ZkvmSecp256r1Hash { data: input[..32].try_into().unwrap() }, + ZkvmSecp256r1Signature { data: input[32..96].try_into().unwrap() }, + ZkvmSecp256r1Pubkey { data: input[96..].try_into().unwrap() }, + ) +} + +const VALID: [u8; 160] = hex!( + "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d" + "a73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac" + "36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60" + "4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3" + "7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e" +); + +#[test] +fn secp256r1_verify_vectors() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = false; + + secp256r1_verify(&msg, &sig, &pubkey, &mut verified).unwrap(); + assert!(verified); + + // Wrong message must not verify; `verified` must be overwritten. + let mut wrong_msg = msg; + wrong_msg.data[0] = 0x3c; + secp256r1_verify(&wrong_msg, &sig, &pubkey, &mut verified).unwrap(); + assert!(!verified); +} + +#[test] +fn secp256r1_verify_malformed_inputs() { + let (msg, sig, _) = parts(&VALID); + let mut verified = true; + + // A signature with out-of-range values cannot be parsed. + let bad_sig = ZkvmSecp256r1Signature { data: [0xff; 64] }; + let result = secp256r1_verify(&msg, &bad_sig, &parts(&VALID).2, &mut verified); + assert_eq!(result, Err(Error::InvalidSignature)); + assert!(!verified); + + // A public key that is not on the curve cannot be parsed. + verified = true; + let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; + let result = secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified); + assert_eq!(result, Err(Error::PointNotOnCurve)); + assert!(!verified); +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs index acfab0996..d8bb24d1e 100644 --- a/crates/accelerators/tests/conformance/main.rs +++ b/crates/accelerators/tests/conformance/main.rs @@ -4,4 +4,5 @@ //! Modules mirror the `src/ops` layout: one file per domain. mod blake2; +mod ecdsa; mod hash; From 5f4874b5378268f0cceecff2f98b6e7d358e1db7 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:39:54 -0400 Subject: [PATCH 16/44] feat(ffi): secp256r1 verify interface --- crates/accelerators/src/ffi/ecdsa.rs | 36 +++++++++++++++++- .../accelerators/tests/conformance/ecdsa.rs | 37 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/crates/accelerators/src/ffi/ecdsa.rs b/crates/accelerators/src/ffi/ecdsa.rs index bf9563c4f..266771893 100644 --- a/crates/accelerators/src/ffi/ecdsa.rs +++ b/crates/accelerators/src/ffi/ecdsa.rs @@ -2,7 +2,10 @@ use crate::{ ops, - types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmStatus}, + types::{ + ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, + ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus, + }, }; /// Recover the uncompressed secp256k1 public key from an ECDSA signature @@ -64,3 +67,34 @@ pub unsafe extern "C" fn zkvm_secp256k1_verify( Err(_) => ZkvmStatus::Fail, } } + +/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed +/// public key, writing the result to `verified`. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are +/// malformed; `verified` is `false` when a well-formed signature does not +/// verify. +/// +/// # Safety +/// +/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. +/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. +/// - `pubkey`, if non-NULL, must be valid for reads of 64 bytes. +/// - `verified`, if non-NULL, must be valid for writes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256r1_verify( + msg: *const ZkvmSecp256r1Hash, + sig: *const ZkvmSecp256r1Signature, + pubkey: *const ZkvmSecp256r1Pubkey, + verified: *mut bool, +) -> ZkvmStatus { + if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (msg, sig, pubkey, verified) = unsafe { (&*msg, &*sig, &*pubkey, &mut *verified) }; + match ops::secp256r1_verify(msg, sig, pubkey, verified) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} diff --git a/crates/accelerators/tests/conformance/ecdsa.rs b/crates/accelerators/tests/conformance/ecdsa.rs index 705921151..972fdd5be 100644 --- a/crates/accelerators/tests/conformance/ecdsa.rs +++ b/crates/accelerators/tests/conformance/ecdsa.rs @@ -4,8 +4,9 @@ use hex_literal::hex; use openvm_accelerators::{ + ffi::zkvm_secp256r1_verify, ops::{secp256r1_verify, Error}, - types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature}, + types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus}, }; /// Splits a 160-byte P256VERIFY input (msg || sig || pk) into its parts. @@ -58,3 +59,37 @@ fn secp256r1_verify_malformed_inputs() { assert_eq!(result, Err(Error::PointNotOnCurve)); assert!(!verified); } + +#[test] +fn zkvm_secp256r1_verify_smoke() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = false; + + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + // Malformed inputs map to the failure status. + let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + assert!(!verified); +} + +#[test] +fn zkvm_secp256r1_verify_null_pointers() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = false; + + let status = unsafe { zkvm_secp256r1_verify(core::ptr::null(), &sig, &pubkey, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_secp256r1_verify(&msg, core::ptr::null(), &pubkey, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, core::ptr::null(), &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From c2d5b788dc608585041d4546c892341986cd4c7b Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:51:06 -0400 Subject: [PATCH 17/44] feat(ops): host fallback for secp256k1 --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 7 ++++++- crates/accelerators/src/ops/ecdsa.rs | 9 ++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7b7ecc15..f6fd77a28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6790,6 +6790,7 @@ name = "openvm-accelerators" version = "0.4.0" 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-keccak256", "openvm-sha2", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 5b7c7afe2..19648018d 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -11,7 +11,6 @@ workspace = true [dependencies] # openvm -openvm-k256.workspace = true openvm-p256.workspace = true openvm-keccak256.workspace = true openvm-sha2.workspace = true @@ -19,8 +18,14 @@ openvm-sha2.workspace = true # crypto ripemd = { version = "0.1.3", default-features = false } +# The OpenVM-accelerated k256 fork; its ECDSA recovery relies on zkVM hints +# and is unimplemented outside the guest. +[target.'cfg(any(target_os = "none", target_os = "openvm"))'.dependencies] +openvm-k256.workspace = true + # Host implementations when not building for the zkVM guest. [target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] +k256 = { version = "0.13", default-features = false, features = ["ecdsa"] } openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } openvm-sha2 = { workspace = true, features = ["import_sha2"] } diff --git a/crates/accelerators/src/ops/ecdsa.rs b/crates/accelerators/src/ops/ecdsa.rs index 40e454bf8..ae5735bc1 100644 --- a/crates/accelerators/src/ops/ecdsa.rs +++ b/crates/accelerators/src/ops/ecdsa.rs @@ -1,6 +1,9 @@ //! ECDSA operations. -use openvm_k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; +#[cfg(any(target_os = "none", target_os = "openvm"))] +use openvm_k256 as k256; + +use k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; use crate::{ ops::Error, @@ -29,9 +32,13 @@ pub fn secp256k1_ecrecover( } let recovery_id = RecoveryId::from_byte(recid).ok_or(Error::InvalidSignature)?; + #[cfg(any(target_os = "none", target_os = "openvm"))] let key = VerifyingKey::recover_from_prehash_noverify(&msg.data, &signature.to_bytes(), recovery_id) .map_err(|_| Error::InvalidSignature)?; + #[cfg(not(any(target_os = "none", target_os = "openvm")))] + let key = VerifyingKey::recover_from_prehash(&msg.data, &signature, recovery_id) + .map_err(|_| Error::InvalidSignature)?; let point = key.to_encoded_point(false); output.data.copy_from_slice(&point.as_bytes()[1..65]); From 41994b3e0ff90aaa384d185877526245f22a9b55 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 10:51:49 -0400 Subject: [PATCH 18/44] feat(tests): tests for secp256k1 --- .../accelerators/tests/conformance/ecdsa.rs | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/crates/accelerators/tests/conformance/ecdsa.rs b/crates/accelerators/tests/conformance/ecdsa.rs index 972fdd5be..3230f324e 100644 --- a/crates/accelerators/tests/conformance/ecdsa.rs +++ b/crates/accelerators/tests/conformance/ecdsa.rs @@ -5,8 +5,11 @@ use hex_literal::hex; use openvm_accelerators::{ ffi::zkvm_secp256r1_verify, - ops::{secp256r1_verify, Error}, - types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus}, + ops::{keccak256, secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, Error}, + types::{ + ZkvmKeccak256Hash, ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, + ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus, + }, }; /// Splits a 160-byte P256VERIFY input (msg || sig || pk) into its parts. @@ -93,3 +96,63 @@ fn zkvm_secp256r1_verify_null_pointers() { let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, core::ptr::null_mut()) }; assert_eq!(status, ZkvmStatus::Fail); } + +const K1_MSG: ZkvmSecp256k1Hash = ZkvmSecp256k1Hash { + data: hex!("456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3"), +}; +const K1_SIG: ZkvmSecp256k1Signature = ZkvmSecp256k1Signature { + data: hex!( + "9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608" + "4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada" + ), +}; +const K1_ADDRESS: [u8; 20] = hex!("7156526fbd7a3c72969b54f64e42c10fbb768c8a"); + +#[test] +fn secp256k1_ecrecover_vector() { + let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; + secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, &mut pubkey).unwrap(); + + // The Ethereum address is keccak(pubkey)[12..], derived here exactly as + // a caller of the interface would. + let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; + keccak256(&pubkey.data, &mut hash); + assert_eq!(hash.data[12..], K1_ADDRESS); +} + +#[test] +fn secp256k1_ecrecover_invalid_inputs() { + let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; + + // Recovery ids above 3 are invalid. + let result = secp256k1_ecrecover(&K1_MSG, &K1_SIG, 4, &mut pubkey); + assert_eq!(result, Err(Error::InvalidSignature)); + + // The zero signature cannot be parsed. + let zero_sig = ZkvmSecp256k1Signature { data: [0; 64] }; + let result = secp256k1_ecrecover(&K1_MSG, &zero_sig, 0, &mut pubkey); + assert_eq!(result, Err(Error::InvalidSignature)); +} + +#[test] +fn secp256k1_verify_roundtrip() { + let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; + secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, &mut pubkey).unwrap(); + + let mut verified = false; + secp256k1_verify(&K1_MSG, &K1_SIG, &pubkey, &mut verified).unwrap(); + assert!(verified); + + // Wrong message must not verify; `verified` must be overwritten. + let mut wrong_msg = K1_MSG; + wrong_msg.data[0] ^= 1; + secp256k1_verify(&wrong_msg, &K1_SIG, &pubkey, &mut verified).unwrap(); + assert!(!verified); + + // A public key that is not on the curve cannot be parsed. + verified = true; + let bad_pubkey = ZkvmSecp256k1Pubkey { data: [0xff; 64] }; + let result = secp256k1_verify(&K1_MSG, &K1_SIG, &bad_pubkey, &mut verified); + assert_eq!(result, Err(Error::PointNotOnCurve)); + assert!(!verified); +} From aaa2d9d164a377be7071b7bbf0fe9fd35016baa0 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Thu, 30 Jul 2026 10:34:13 -0400 Subject: [PATCH 19/44] feat(ops): split ecdsa by curve --- crates/accelerators/src/ops/ecdsa/mod.rs | 10 ++++++ .../src/ops/{ecdsa.rs => ecdsa/secp256k1.rs} | 33 +++---------------- .../accelerators/src/ops/ecdsa/secp256r1.rs | 29 ++++++++++++++++ 3 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 crates/accelerators/src/ops/ecdsa/mod.rs rename crates/accelerators/src/ops/{ecdsa.rs => ecdsa/secp256k1.rs} (71%) create mode 100644 crates/accelerators/src/ops/ecdsa/secp256r1.rs diff --git a/crates/accelerators/src/ops/ecdsa/mod.rs b/crates/accelerators/src/ops/ecdsa/mod.rs new file mode 100644 index 000000000..e25e45ee3 --- /dev/null +++ b/crates/accelerators/src/ops/ecdsa/mod.rs @@ -0,0 +1,10 @@ +//! ECDSA operations. +//! +//! Split by curve: the two crates providing them use the same type names, and +//! secp256k1 additionally needs a guest/host split that secp256r1 does not. + +mod secp256k1; +mod secp256r1; + +pub use secp256k1::{secp256k1_ecrecover, secp256k1_verify}; +pub use secp256r1::secp256r1_verify; diff --git a/crates/accelerators/src/ops/ecdsa.rs b/crates/accelerators/src/ops/ecdsa/secp256k1.rs similarity index 71% rename from crates/accelerators/src/ops/ecdsa.rs rename to crates/accelerators/src/ops/ecdsa/secp256k1.rs index ae5735bc1..a69dfdbea 100644 --- a/crates/accelerators/src/ops/ecdsa.rs +++ b/crates/accelerators/src/ops/ecdsa/secp256k1.rs @@ -1,5 +1,8 @@ -//! ECDSA operations. +//! ECDSA over the secp256k1 curve. +// In the guest, secp256k1 operations use the OpenVM-accelerated k256; on +// the host they use upstream RustCrypto k256 (the ECDSA recovery +// relies on zkVM hints and is unimplemented outside the guest). #[cfg(any(target_os = "none", target_os = "openvm"))] use openvm_k256 as k256; @@ -7,10 +10,7 @@ use k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, Ver use crate::{ ops::Error, - types::{ - ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, - ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, - }, + types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature}, }; /// Recover the uncompressed secp256k1 public key from an ECDSA signature @@ -69,26 +69,3 @@ pub fn secp256k1_verify( *verified = key.verify_prehash(&msg.data, &signature).is_ok(); Ok(()) } - -/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed -/// public key, writing the result to `verified`. -pub fn secp256r1_verify( - msg: &ZkvmSecp256r1Hash, - sig: &ZkvmSecp256r1Signature, - pubkey: &ZkvmSecp256r1Pubkey, - verified: &mut bool, -) -> Result<(), Error> { - use openvm_p256::{ - ecdsa::{Signature, VerifyingKey}, - EncodedPoint, - }; - - *verified = false; - - let encoded_point = EncodedPoint::from_untagged_bytes(&pubkey.data.into()); - let key = - VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| Error::PointNotOnCurve)?; - let signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; - *verified = key.verify_prehash(&msg.data, &signature).is_ok(); - Ok(()) -} diff --git a/crates/accelerators/src/ops/ecdsa/secp256r1.rs b/crates/accelerators/src/ops/ecdsa/secp256r1.rs new file mode 100644 index 000000000..fd21c0a16 --- /dev/null +++ b/crates/accelerators/src/ops/ecdsa/secp256r1.rs @@ -0,0 +1,29 @@ +//! ECDSA over the secp256r1 (P-256) curve. + +use openvm_p256::{ + ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}, + EncodedPoint, +}; + +use crate::{ + ops::Error, + types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature}, +}; + +/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed +/// public key, writing the result to `verified`. +pub fn secp256r1_verify( + msg: &ZkvmSecp256r1Hash, + sig: &ZkvmSecp256r1Signature, + pubkey: &ZkvmSecp256r1Pubkey, + verified: &mut bool, +) -> Result<(), Error> { + *verified = false; + + let encoded_point = EncodedPoint::from_untagged_bytes(&pubkey.data.into()); + let key = + VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| Error::PointNotOnCurve)?; + let signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; + *verified = key.verify_prehash(&msg.data, &signature).is_ok(); + Ok(()) +} From b810d8b56394125ae26055a3d2d2ed9a0a36d745 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 11:12:21 -0400 Subject: [PATCH 20/44] feat(ops): modexp --- Cargo.lock | 3 + crates/accelerators/Cargo.toml | 3 + crates/accelerators/src/lib.rs | 2 + crates/accelerators/src/ops/mod.rs | 2 + crates/accelerators/src/ops/modexp.rs | 143 ++++++++++++++++++ crates/accelerators/tests/conformance/main.rs | 1 + .../accelerators/tests/conformance/modexp.rs | 41 +++++ 7 files changed, 195 insertions(+) create mode 100644 crates/accelerators/src/ops/modexp.rs create mode 100644 crates/accelerators/tests/conformance/modexp.rs diff --git a/Cargo.lock b/Cargo.lock index f6fd77a28..8d9ca6a3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6789,10 +6789,13 @@ dependencies = [ name = "openvm-accelerators" version = "0.4.0" dependencies = [ + "aurora-engine-modexp", "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-ecc-guest", "openvm-keccak256", + "openvm-pairing", "openvm-sha2", "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "ripemd", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 19648018d..e89886d92 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -11,11 +11,14 @@ workspace = true [dependencies] # openvm +openvm-ecc-guest.workspace = true openvm-p256.workspace = true +openvm-pairing = { workspace = true, features = ["bn254"] } openvm-keccak256.workspace = true openvm-sha2.workspace = true # crypto +aurora-engine-modexp = { version = "1.2.0", default-features = false } ripemd = { version = "0.1.3", default-features = false } # The OpenVM-accelerated k256 fork; its ECDSA recovery relies on zkVM hints diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs index 10a85ec01..e851bff8e 100644 --- a/crates/accelerators/src/lib.rs +++ b/crates/accelerators/src/lib.rs @@ -2,6 +2,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + #[cfg(feature = "ffi")] pub mod ffi; pub mod ops; diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index f269b7319..4813df2b2 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -7,10 +7,12 @@ mod blake2; mod ecdsa; mod hash; +mod modexp; pub use blake2::blake2f; pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify, secp256r1_verify}; pub use hash::{keccak256, ripemd160, sha256}; +pub use modexp::modexp; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { diff --git a/crates/accelerators/src/ops/modexp.rs b/crates/accelerators/src/ops/modexp.rs new file mode 100644 index 000000000..9b15018c5 --- /dev/null +++ b/crates/accelerators/src/ops/modexp.rs @@ -0,0 +1,143 @@ +//! Modular exponentiation with a BN254-Fr fast path. + +use alloc::{vec, vec::Vec}; + +use openvm_pairing::bn254 as bn; + +/// The number of bytes needed to represent an element of BN254's scalar +/// field Fr. +const BN_SCALAR_LEN: usize = 32; + +/// Compute `base^exp % modulus` into `output`, left-padded with zeros. +/// +/// # Panics +/// +/// Panics if `output.len() != modulus.len()`. +pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8], output: &mut [u8]) { + assert_eq!(output.len(), modulus.len(), "output must be exactly modulus-sized"); + + let result = if is_bn254_fr(modulus) { + accelerated_modexp_bn254_fr(base, exp) + } else { + aurora_engine_modexp::modexp(base, exp, modulus) + }; + + // The result is numerically reduced, but its byte representation may be + // shorter or longer (leading zeros); right-align it. + if result.len() >= output.len() { + output.copy_from_slice(&result[result.len() - output.len()..]); + } else { + let pad = output.len() - result.len(); + output[..pad].fill(0); + output[pad..].copy_from_slice(&result); + } +} + +/// Returns true if the modulus (big-endian, possibly with leading zeros) equals BN254 Fr. +fn is_bn254_fr(modulus: &[u8]) -> bool { + use openvm_ecc_guest::algebra::IntMod; + + // Strip leading zeros + let stripped = match modulus.iter().position(|&b| b != 0) { + Some(i) => &modulus[i..], + None => return false, // all zeros + }; + // bn::Scalar::MODULUS is little-endian; compare against reversed input + stripped.len() == BN_SCALAR_LEN && stripped.iter().rev().eq(bn::Scalar::MODULUS.as_ref().iter()) +} + +/// Accelerated modexp for BN254 Fr using field arithmetic intrinsics. +fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { + use openvm_ecc_guest::algebra::{ExpBytes, IntMod, Reduce}; + + // OpenVM's field reduction requires inputs to be aligned to the field byte size. + let padded_len = base.len().next_multiple_of(BN_SCALAR_LEN).max(BN_SCALAR_LEN); + let mut padded = vec![0u8; padded_len]; + padded[padded_len - base.len()..].copy_from_slice(base); + let base_fr = bn::Scalar::reduce_be_bytes(&padded); + + base_fr.exp_bytes(true, exp).to_be_bytes().as_ref().to_vec() +} + +#[cfg(test)] +mod tests { + use super::*; + + /// BN254 Fr modulus in big-endian bytes + fn bn254_fr_modulus_be() -> Vec { + use openvm_ecc_guest::algebra::IntMod; + bn::Scalar::MODULUS.as_ref().iter().rev().copied().collect() + } + + /// Helper: run the accelerated path and compare against the aurora + /// reference. The accelerated path always returns BN_SCALAR_LEN bytes, + /// so the reference output is left-padded to match. + fn check(base: &[u8], exp: &[u8]) { + let modulus = bn254_fr_modulus_be(); + let expected = aurora_engine_modexp::modexp(base, exp, &modulus); + let actual = accelerated_modexp_bn254_fr(base, exp); + let mut expected_padded = vec![0u8; BN_SCALAR_LEN]; + let offset = BN_SCALAR_LEN - expected.len(); + expected_padded[offset..].copy_from_slice(&expected); + assert_eq!(actual, expected_padded, "base={base:?}, exp={exp:?}"); + } + + #[test] + fn test_is_bn254_fr() { + // Exact modulus + assert!(is_bn254_fr(&bn254_fr_modulus_be())); + + // With leading zeros + let mut padded = vec![0u8; 10]; + padded.extend_from_slice(&bn254_fr_modulus_be()); + assert!(is_bn254_fr(&padded)); + + // All zeros → false + assert!(!is_bn254_fr(&[0u8; 32])); + + // Wrong modulus (flip last bit) + let mut m = bn254_fr_modulus_be(); + *m.last_mut().unwrap() ^= 1; + assert!(!is_bn254_fr(&m)); + } + + #[test] + fn test_accelerated_modexp_bn254_fr() { + // --- short base (<=32 bytes), value < modulus --- + check(&[3], &[5]); // 3^5 mod Fr + check(&[0], &[5]); // 0^5 = 0 + check(&[3], &[0]); // 3^0 = 1 + check(&[0], &[0]); // 0^0 = 1 by convention + check(&[], &[]); // empty inputs + check(&[0, 0, 0, 3], &[5]); // leading zeros in base + + // --- short base, value >= modulus (triggers the reduce fallback) --- + let m = bn254_fr_modulus_be(); + check(&m, &[1]); // Fr mod Fr = 0, so 0^1 = 0 + let mut m_plus_1 = m.clone(); + *m_plus_1.last_mut().unwrap() += 1; + check(&m_plus_1, &[2]); // (Fr+1)^2 mod Fr = 1 + check(&[0xff; 32], &[1]); // max 256-bit value, >= modulus + + // --- large base (> 32 bytes, reduce_be_bytes path) --- + check(&[0xab; 64], &[3]); // aligned (multiple of 32) + check(&[0x42; 100], &[2]); // unaligned (tests the padding) + check(&[0xab; 64], &[0xff; 32]); // large base + large exponent + + // --- larger exponents --- + check(&[2], &[0xff; 32]); // 2^(2^256-1) mod Fr + check(&[2], &[0, 0, 0, 5]); // leading zeros in exponent + check(&[3], &[0xab; 64]); // exponent > 32 bytes + + // --- same value through both base-parsing code paths --- + let base_32 = [0xab; 32]; + let mut base_33 = vec![0u8]; + base_33.extend_from_slice(&base_32); + let exp = &[7]; + assert_eq!( + accelerated_modexp_bn254_fr(&base_32, exp), + accelerated_modexp_bn254_fr(&base_33, exp), + "33-byte base with leading zero must match 32-byte base" + ); + } +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs index d8bb24d1e..af50b7471 100644 --- a/crates/accelerators/tests/conformance/main.rs +++ b/crates/accelerators/tests/conformance/main.rs @@ -6,3 +6,4 @@ mod blake2; mod ecdsa; mod hash; +mod modexp; diff --git a/crates/accelerators/tests/conformance/modexp.rs b/crates/accelerators/tests/conformance/modexp.rs new file mode 100644 index 000000000..f66e1c2fb --- /dev/null +++ b/crates/accelerators/tests/conformance/modexp.rs @@ -0,0 +1,41 @@ +//! Modexp conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::ops::modexp; + +/// BN254 Fr (the scalar field) modulus, big-endian. Not to be confused with +/// the base field prime, which shares the leading bytes. +const BN254_FR: [u8; 32] = hex!("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); + +#[test] +fn modexp_small() { + // 3^5 mod 7 = 5 + let mut output = [0xffu8; 1]; + modexp(&[3], &[5], &[7], &mut output); + assert_eq!(output, [5]); + + // Output is left-padded to the modulus length. + let mut output = [0xffu8; 2]; + modexp(&[3], &[5], &[0, 7], &mut output); + assert_eq!(output, [0, 5]); + + // A zero-length modulus writes nothing. + modexp(&[3], &[5], &[], &mut []); +} + +#[test] +fn modexp_matches_reference() { + // The BN254-Fr accelerated path, compared right-aligned against the + // aurora reference. + let mut output = [0u8; 32]; + modexp(&[0xab; 32], &[0x07], &BN254_FR, &mut output); + let reference = aurora_engine_modexp::modexp(&[0xab; 32], &[0x07], &BN254_FR); + assert_eq!(output[32 - reference.len()..], reference[..]); + + // The generic path with a non-special modulus. + let modulus = [0xef; 24]; + let mut output = [0u8; 24]; + modexp(&[0x12; 40], &[0x34; 3], &modulus, &mut output); + let reference = aurora_engine_modexp::modexp(&[0x12; 40], &[0x34; 3], &modulus); + assert_eq!(output[24 - reference.len()..], reference[..]); +} From c1f2cd34ed983126a2118b4aa5191a0425fc194b Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 11:17:01 -0400 Subject: [PATCH 21/44] feat(ffi): modexp interface --- crates/accelerators/src/ffi/mod.rs | 2 + crates/accelerators/src/ffi/modexp.rs | 48 +++++++++++++ .../accelerators/tests/conformance/modexp.rs | 68 ++++++++++++++++++- 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 crates/accelerators/src/ffi/modexp.rs diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index 3f82ef4ea..e351d6812 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -7,7 +7,9 @@ mod blake2; mod ecdsa; mod hash; +mod modexp; pub use blake2::*; pub use ecdsa::*; pub use hash::*; +pub use modexp::*; diff --git a/crates/accelerators/src/ffi/modexp.rs b/crates/accelerators/src/ffi/modexp.rs new file mode 100644 index 000000000..3733d987f --- /dev/null +++ b/crates/accelerators/src/ffi/modexp.rs @@ -0,0 +1,48 @@ +//! C ABI for modular exponentiation. + +use crate::{ops, types::ZkvmStatus}; + +/// Compute `base[..base_len] ^ exp[..exp_len] % modulus[..mod_len]` into +/// `output`, which receives exactly `mod_len` bytes, left-padded with zeros. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL while its length is +/// non-zero; a NULL pointer with a zero length is the empty input. +/// +/// # Safety +/// +/// - `base`, `exp` and `modulus`, if non-NULL, must be valid for reads of their respective lengths. +/// - `output`, if non-NULL, must be valid for writes of `mod_len` bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_modexp( + base: *const u8, + base_len: usize, + exp: *const u8, + exp_len: usize, + modulus: *const u8, + mod_len: usize, + output: *mut u8, +) -> ZkvmStatus { + if (base.is_null() && base_len != 0) || + (exp.is_null() && exp_len != 0) || + (modulus.is_null() && mod_len != 0) || + (output.is_null() && mod_len != 0) + { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let base = + if base_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(base, base_len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let exp = if exp_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(exp, exp_len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let modulus = + if mod_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(modulus, mod_len) } }; + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let output = if mod_len == 0 { + &mut [][..] + } else { + unsafe { core::slice::from_raw_parts_mut(output, mod_len) } + }; + ops::modexp(base, exp, modulus, output); + ZkvmStatus::Ok +} diff --git a/crates/accelerators/tests/conformance/modexp.rs b/crates/accelerators/tests/conformance/modexp.rs index f66e1c2fb..760015463 100644 --- a/crates/accelerators/tests/conformance/modexp.rs +++ b/crates/accelerators/tests/conformance/modexp.rs @@ -1,7 +1,7 @@ //! Modexp conformance vectors. use hex_literal::hex; -use openvm_accelerators::ops::modexp; +use openvm_accelerators::{ffi::zkvm_modexp, ops::modexp, types::ZkvmStatus}; /// BN254 Fr (the scalar field) modulus, big-endian. Not to be confused with /// the base field prime, which shares the leading bytes. @@ -39,3 +39,69 @@ fn modexp_matches_reference() { let reference = aurora_engine_modexp::modexp(&[0x12; 40], &[0x34; 3], &modulus); assert_eq!(output[24 - reference.len()..], reference[..]); } + +#[test] +fn zkvm_modexp_smoke() { + // 3^5 mod 7 = 5 + let base = [3u8]; + let exp = [5u8]; + let modulus = [7u8]; + let mut output = [0xffu8; 1]; + let status = unsafe { + zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, output.as_mut_ptr()) + }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(output, [5]); +} + +#[test] +fn zkvm_modexp_null_pointers() { + let base = [3u8]; + let exp = [5u8]; + let modulus = [7u8]; + let mut output = [0xffu8; 1]; + + // NULL base and exp with zero lengths are empty inputs: 0^0 mod 7 = 1. + let status = unsafe { + zkvm_modexp( + core::ptr::null(), + 0, + core::ptr::null(), + 0, + modulus.as_ptr(), + 1, + output.as_mut_ptr(), + ) + }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(output, [1]); + + // A NULL pointer with a non-zero length fails. + let status = unsafe { + zkvm_modexp(core::ptr::null(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, output.as_mut_ptr()) + }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { + zkvm_modexp( + base.as_ptr(), + 1, + core::ptr::null(), + 1, + modulus.as_ptr(), + 1, + output.as_mut_ptr(), + ) + }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { + zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, core::ptr::null(), 1, output.as_mut_ptr()) + }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { + zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, core::ptr::null_mut()) + }; + assert_eq!(status, ZkvmStatus::Fail); +} From cb062290b9f6e075bd49b7f769d4431b66eea860 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Thu, 30 Jul 2026 10:37:23 -0400 Subject: [PATCH 22/44] refactor(ops): imports out of functions --- crates/accelerators/src/ops/modexp.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/accelerators/src/ops/modexp.rs b/crates/accelerators/src/ops/modexp.rs index 9b15018c5..5652d7362 100644 --- a/crates/accelerators/src/ops/modexp.rs +++ b/crates/accelerators/src/ops/modexp.rs @@ -2,6 +2,7 @@ use alloc::{vec, vec::Vec}; +use openvm_ecc_guest::algebra::{ExpBytes, IntMod, Reduce}; use openvm_pairing::bn254 as bn; /// The number of bytes needed to represent an element of BN254's scalar @@ -35,8 +36,6 @@ pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8], output: &mut [u8]) { /// Returns true if the modulus (big-endian, possibly with leading zeros) equals BN254 Fr. fn is_bn254_fr(modulus: &[u8]) -> bool { - use openvm_ecc_guest::algebra::IntMod; - // Strip leading zeros let stripped = match modulus.iter().position(|&b| b != 0) { Some(i) => &modulus[i..], @@ -48,8 +47,6 @@ fn is_bn254_fr(modulus: &[u8]) -> bool { /// Accelerated modexp for BN254 Fr using field arithmetic intrinsics. fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { - use openvm_ecc_guest::algebra::{ExpBytes, IntMod, Reduce}; - // OpenVM's field reduction requires inputs to be aligned to the field byte size. let padded_len = base.len().next_multiple_of(BN_SCALAR_LEN).max(BN_SCALAR_LEN); let mut padded = vec![0u8; padded_len]; @@ -65,7 +62,6 @@ mod tests { /// BN254 Fr modulus in big-endian bytes fn bn254_fr_modulus_be() -> Vec { - use openvm_ecc_guest::algebra::IntMod; bn::Scalar::MODULUS.as_ref().iter().rev().copied().collect() } From ecb73c7f191849fe9c0f975edd2c9e6300e38ede Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 12:12:12 -0400 Subject: [PATCH 23/44] feat(ops): kzg point evaluation --- Cargo.lock | 2 + Cargo.toml | 1 + crates/accelerators/Cargo.toml | 12 ++++ crates/accelerators/src/ops/kzg.rs | 36 ++++++++++++ crates/accelerators/src/ops/mod.rs | 2 + crates/accelerators/tests/conformance/kzg.rs | 58 +++++++++++++++++++ crates/accelerators/tests/conformance/main.rs | 1 + 7 files changed, 112 insertions(+) create mode 100644 crates/accelerators/src/ops/kzg.rs create mode 100644 crates/accelerators/tests/conformance/kzg.rs diff --git a/Cargo.lock b/Cargo.lock index 8d9ca6a3f..f307b53bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6795,7 +6795,9 @@ dependencies = [ "k256 0.13.4 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "openvm-ecc-guest", "openvm-keccak256", + "openvm-kzg", "openvm-pairing", + "openvm-pairing-guest", "openvm-sha2", "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", "ripemd", diff --git a/Cargo.toml b/Cargo.toml index 761b39e61..b9aa23503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ openvm-circuit = { git = "https://github.com/openvm-org/openvm.git", branch = "d openvm-verify-stark-host = { git = "https://github.com/openvm-org/openvm.git", branch = "develop-v2.1.0", default-features = false } openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", branch = "develop-v2.1.0", default-features = false } openvm-keccak256 = { git = "https://github.com/openvm-org/openvm.git", branch = "develop-v2.1.0", default-features = false } +openvm-pairing-guest = { git = "https://github.com/openvm-org/openvm.git", branch = "develop-v2.1.0", default-features = false } openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", branch = "develop-v2.1.0", default-features = false, features = [ "bn254", "bls12_381", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index e89886d92..ca0c0719a 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # openvm openvm-ecc-guest.workspace = true +openvm-kzg = { workspace = true, features = ["use-intrinsics"] } openvm-p256.workspace = true openvm-pairing = { workspace = true, features = ["bn254"] } openvm-keccak256.workspace = true @@ -30,11 +31,22 @@ openvm-k256.workspace = true [target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] k256 = { version = "0.13", default-features = false, features = ["ecdsa"] } openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } +# Not imported directly: enables the host pairing backend used by the KZG +# verification. openvm-pairing's `halo2curves` feature does not forward to +# openvm-pairing-guest, so the inner feature is enabled explicitly. +openvm-pairing = { workspace = true, features = ["halo2curves"] } +openvm-pairing-guest = { workspace = true, features = ["halo2curves"] } openvm-sha2 = { workspace = true, features = ["import_sha2"] } [dev-dependencies] hex-literal.workspace = true +[package.metadata.cargo-shear] +ignored = ["openvm-pairing-guest"] + +[package.metadata.cargo-machete] +ignored = ["openvm-pairing-guest"] + [features] default = ["ffi"] # The extern "C" `zkvm_*` symbols. Rust consumers that only need `ops` can diff --git a/crates/accelerators/src/ops/kzg.rs b/crates/accelerators/src/ops/kzg.rs new file mode 100644 index 000000000..3cca34cc4 --- /dev/null +++ b/crates/accelerators/src/ops/kzg.rs @@ -0,0 +1,36 @@ +//! KZG point-evaluation proof verification (EIP-4844). + +use openvm_kzg::{Bytes32, Bytes48, KzgProof}; + +use crate::{ + ops::Error, + types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof}, +}; + +/// Verify a KZG proof that the blob committed to by `commitment` evaluates +/// to `y` at point `z`, writing the result to `verified`. +/// +/// Errors mean the check could not run — a commitment or proof that is not +/// a valid compressed G1 point, or an out-of-range field element; `verified` +/// is `false` only when a well-formed proof does not verify. +pub fn kzg_point_eval( + commitment: &ZkvmKzgCommitment, + z: &ZkvmKzgFieldElement, + y: &ZkvmKzgFieldElement, + proof: &ZkvmKzgProof, + verified: &mut bool, +) -> Result<(), Error> { + *verified = false; + + let env = openvm_kzg::EnvKzgSettings::default(); + let kzg_settings = env.get(); + + let commitment = Bytes48::from_slice(&commitment.data).map_err(|_| Error::KzgInvalidInput)?; + let z = Bytes32::from_slice(&z.data).map_err(|_| Error::KzgInvalidInput)?; + let y = Bytes32::from_slice(&y.data).map_err(|_| Error::KzgInvalidInput)?; + let proof = Bytes48::from_slice(&proof.data).map_err(|_| Error::KzgInvalidInput)?; + + *verified = KzgProof::verify_kzg_proof(&commitment, &z, &y, &proof, kzg_settings) + .map_err(|_| Error::KzgInvalidInput)?; + Ok(()) +} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 4813df2b2..673c373fd 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -7,11 +7,13 @@ mod blake2; mod ecdsa; mod hash; +mod kzg; mod modexp; pub use blake2::blake2f; pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify, secp256r1_verify}; pub use hash::{keccak256, ripemd160, sha256}; +pub use kzg::kzg_point_eval; pub use modexp::modexp; #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/accelerators/tests/conformance/kzg.rs b/crates/accelerators/tests/conformance/kzg.rs new file mode 100644 index 000000000..06b17118a --- /dev/null +++ b/crates/accelerators/tests/conformance/kzg.rs @@ -0,0 +1,58 @@ +//! KZG point-evaluation conformance using the point-at-infinity commitment, +//! which commits to the zero polynomial (p(z) = 0 for every z). + +use openvm_accelerators::{ + ops::{kzg_point_eval, Error}, + types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof}, +}; + +/// The compressed point at infinity: 0xc0 followed by zeros. +fn infinity() -> ZkvmKzgCommitment { + let mut point = ZkvmKzgCommitment { data: [0; 48] }; + point.data[0] = 0xc0; + point +} + +fn scalar(value: u8) -> ZkvmKzgFieldElement { + let mut s = ZkvmKzgFieldElement { data: [0; 32] }; + s.data[31] = value; + s +} + +#[test] +fn kzg_point_eval_infinity_commitment() { + let commitment = infinity(); + let proof: ZkvmKzgProof = infinity(); + let z = scalar(2); + let mut verified = false; + + // The zero polynomial evaluates to 0 at every z; the infinity proof + // attests it. + kzg_point_eval(&commitment, &z, &scalar(0), &proof, &mut verified).unwrap(); + assert!(verified); + + // Claiming y = 1 for the zero polynomial must not verify. + kzg_point_eval(&commitment, &z, &scalar(1), &proof, &mut verified).unwrap(); + assert!(!verified); +} + +#[test] +fn kzg_point_eval_malformed_inputs() { + let z = scalar(2); + let y = scalar(0); + let mut verified = true; + + // Not a valid compressed-point prefix. + let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; + garbage.data[0] = 0x01; + let result = kzg_point_eval(&garbage, &z, &y, &infinity(), &mut verified); + assert_eq!(result, Err(Error::KzgInvalidInput)); + assert!(!verified); + + // An out-of-range evaluation point (>= the BLS scalar field order). + let big_z = ZkvmKzgFieldElement { data: [0xff; 32] }; + verified = true; + let result = kzg_point_eval(&infinity(), &big_z, &y, &infinity(), &mut verified); + assert_eq!(result, Err(Error::KzgInvalidInput)); + assert!(!verified); +} diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs index af50b7471..9115352c0 100644 --- a/crates/accelerators/tests/conformance/main.rs +++ b/crates/accelerators/tests/conformance/main.rs @@ -6,4 +6,5 @@ mod blake2; mod ecdsa; mod hash; +mod kzg; mod modexp; From df5f4c45f6982e2d03267cd943c4871af7a55088 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 12:14:55 -0400 Subject: [PATCH 24/44] feat(ffi): kzg interface --- crates/accelerators/src/ffi/kzg.rs | 38 +++++++++++++++ crates/accelerators/src/ffi/mod.rs | 2 + crates/accelerators/tests/conformance/kzg.rs | 50 +++++++++++++++++++- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 crates/accelerators/src/ffi/kzg.rs diff --git a/crates/accelerators/src/ffi/kzg.rs b/crates/accelerators/src/ffi/kzg.rs new file mode 100644 index 000000000..8fb2b60ec --- /dev/null +++ b/crates/accelerators/src/ffi/kzg.rs @@ -0,0 +1,38 @@ +//! C ABI for KZG point evaluation. + +use crate::{ + ops, + types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmStatus}, +}; + +/// Verify a KZG proof that the blob committed to by `commitment` evaluates +/// to `y` at point `z`, writing the result to `verified`. +/// +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are +/// malformed; `verified` is `false` when a well-formed proof does not +/// verify. +/// +/// # Safety +/// +/// - `commitment` and `proof`, if non-NULL, must be valid for reads of 48 bytes. +/// - `z` and `y`, if non-NULL, must be valid for reads of 32 bytes. +/// - `verified`, if non-NULL, must be valid for writes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_kzg_point_eval( + commitment: *const ZkvmKzgCommitment, + z: *const ZkvmKzgFieldElement, + y: *const ZkvmKzgFieldElement, + proof: *const ZkvmKzgProof, + verified: *mut bool, +) -> ZkvmStatus { + if commitment.is_null() || z.is_null() || y.is_null() || proof.is_null() || verified.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (commitment, z, y, proof, verified) = + unsafe { (&*commitment, &*z, &*y, &*proof, &mut *verified) }; + match ops::kzg_point_eval(commitment, z, y, proof, verified) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index e351d6812..fe78701e8 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -7,9 +7,11 @@ mod blake2; mod ecdsa; mod hash; +mod kzg; mod modexp; pub use blake2::*; pub use ecdsa::*; pub use hash::*; +pub use kzg::*; pub use modexp::*; diff --git a/crates/accelerators/tests/conformance/kzg.rs b/crates/accelerators/tests/conformance/kzg.rs index 06b17118a..74dba8e1f 100644 --- a/crates/accelerators/tests/conformance/kzg.rs +++ b/crates/accelerators/tests/conformance/kzg.rs @@ -2,8 +2,9 @@ //! which commits to the zero polynomial (p(z) = 0 for every z). use openvm_accelerators::{ + ffi::zkvm_kzg_point_eval, ops::{kzg_point_eval, Error}, - types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof}, + types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmStatus}, }; /// The compressed point at infinity: 0xc0 followed by zeros. @@ -56,3 +57,50 @@ fn kzg_point_eval_malformed_inputs() { assert_eq!(result, Err(Error::KzgInvalidInput)); assert!(!verified); } + +#[test] +fn zkvm_kzg_point_eval_smoke() { + let commitment = infinity(); + let proof: ZkvmKzgProof = infinity(); + let z = scalar(2); + let y = scalar(0); + let mut verified = false; + + let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(verified); + + // Malformed inputs map to the failure status. + let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; + garbage.data[0] = 0x01; + let status = unsafe { zkvm_kzg_point_eval(&garbage, &z, &y, &proof, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + assert!(!verified); +} + +#[test] +fn zkvm_kzg_point_eval_null_pointers() { + let commitment = infinity(); + let proof: ZkvmKzgProof = infinity(); + let z = scalar(2); + let y = scalar(0); + let mut verified = false; + + let status = unsafe { zkvm_kzg_point_eval(core::ptr::null(), &z, &y, &proof, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = + unsafe { zkvm_kzg_point_eval(&commitment, core::ptr::null(), &y, &proof, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = + unsafe { zkvm_kzg_point_eval(&commitment, &z, core::ptr::null(), &proof, &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = + unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, core::ptr::null(), &mut verified) }; + assert_eq!(status, ZkvmStatus::Fail); + + let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From c3d60dccdce8df1367929e4a2e8f02540f7200c7 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 14:44:27 -0400 Subject: [PATCH 25/44] feat(ops): BN254 add and mul --- Cargo.lock | 1 + crates/accelerators/Cargo.toml | 3 +- crates/accelerators/src/ops/bn254/codec.rs | 47 ++++++++++++++++++++++ crates/accelerators/src/ops/bn254/mod.rs | 36 +++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 crates/accelerators/src/ops/bn254/codec.rs create mode 100644 crates/accelerators/src/ops/bn254/mod.rs 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/ops/bn254/codec.rs b/crates/accelerators/src/ops/bn254/codec.rs new file mode 100644 index 000000000..2a248f792 --- /dev/null +++ b/crates/accelerators/src/ops/bn254/codec.rs @@ -0,0 +1,47 @@ +//! 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, 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] +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_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..72c595f5d --- /dev/null +++ b/crates/accelerators/src/ops/bn254/mod.rs @@ -0,0 +1,36 @@ +//! BN254 (alt_bn128) group operations (EIP-196 / EIP-197). + +mod codec; + +use codec::{encode_bn_g1_point, read_bn_g1_point, read_bn_scalar}; +use openvm_ecc_guest::weierstrass::IntrinsicCurve; +use openvm_pairing::bn254::Bn254; + +use crate::{ + ops::Error, + types::{ZkvmBn254G1Point, 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(()) +} From c57a69b029f6a2421a42f38f740c21f1e3784897 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 14:54:23 -0400 Subject: [PATCH 26/44] feat(ops): BN254 pairing check --- crates/accelerators/src/ops/bn254/codec.rs | 24 +++++++++++- crates/accelerators/src/ops/bn254/mod.rs | 43 ++++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/crates/accelerators/src/ops/bn254/codec.rs b/crates/accelerators/src/ops/bn254/codec.rs index 2a248f792..b940549ff 100644 --- a/crates/accelerators/src/ops/bn254/codec.rs +++ b/crates/accelerators/src/ops/bn254/codec.rs @@ -7,7 +7,7 @@ use openvm_pairing::bn254 as bn; use crate::{ ops::Error, - types::{ZkvmBn254G1Point, ZkvmBn254Scalar}, + types::{ZkvmBn254G1Point, ZkvmBn254G2Point, ZkvmBn254Scalar}, }; const BN_FQ_LEN: usize = 32; @@ -17,6 +17,14 @@ 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])?; @@ -31,6 +39,20 @@ pub(super) fn read_bn_g1_point(input: &ZkvmBn254G1Point) -> Result 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) diff --git a/crates/accelerators/src/ops/bn254/mod.rs b/crates/accelerators/src/ops/bn254/mod.rs index 72c595f5d..10041166a 100644 --- a/crates/accelerators/src/ops/bn254/mod.rs +++ b/crates/accelerators/src/ops/bn254/mod.rs @@ -2,13 +2,18 @@ mod codec; -use codec::{encode_bn_g1_point, read_bn_g1_point, read_bn_scalar}; -use openvm_ecc_guest::weierstrass::IntrinsicCurve; -use openvm_pairing::bn254::Bn254; +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, ZkvmBn254Scalar}, + types::{ZkvmBn254G1Point, ZkvmBn254PairingPair, ZkvmBn254Scalar}, }; /// BN254 G1 point addition (precompile 0x06). @@ -34,3 +39,33 @@ pub fn bn254_g1_mul( 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(()) +} From fbd44f626c243b35280c166e5ed7ea202533fbb6 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 14:56:55 -0400 Subject: [PATCH 27/44] feat(ffi): BN254 add/mul/pairing check interfaces --- crates/accelerators/src/ffi/bn254.rs | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 crates/accelerators/src/ffi/bn254.rs 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, + } +} From 05ab82502e13080148976692dc66f80be074181c Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 14:58:26 -0400 Subject: [PATCH 28/44] feat(tests): BN254 tests --- .../accelerators/tests/conformance/bn254.rs | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 crates/accelerators/tests/conformance/bn254.rs 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); +} From d32ac4d9ec3e3bdaab285fcc8b3d8197fee4f4ab Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 15:03:42 -0400 Subject: [PATCH 29/44] feat(ops): BLS12-381 add/msm --- .../accelerators/src/ops/bls12_381/codec.rs | 109 ++++++++++++++++++ crates/accelerators/src/ops/bls12_381/mod.rs | 98 ++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 crates/accelerators/src/ops/bls12_381/codec.rs create mode 100644 crates/accelerators/src/ops/bls12_381/mod.rs 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..0d9fda155 --- /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}, +}; + +use super::BLS_FP_LEN; + +#[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..620b1b938 --- /dev/null +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -0,0 +1,98 @@ +//! 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; +use openvm_pairing::bls12_381::Bls12_381; + +use crate::{ + ops::Error, + types::{ + ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, + }, +}; + +pub(super) const BLS_FP_LEN: usize = 48; + +/// 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(()) +} From c02e2f635d1f8afa4f0470181956bf6fab606f7b Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 15:10:37 -0400 Subject: [PATCH 30/44] feat(ops): BLS12-381 pairing check --- crates/accelerators/src/ops/bls12_381/mod.rs | 40 +++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 620b1b938..2c62ff7d4 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -9,13 +9,17 @@ use codec::{ 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; -use openvm_pairing::bls12_381::Bls12_381; +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, }, }; @@ -96,3 +100,35 @@ pub fn bls12_381_g2_msm( 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(()) +} From 5b1bf521655c790e302c98bccf83406199161089 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 15:11:28 -0400 Subject: [PATCH 31/44] feat(ffi): BLS12-381 add/msm/pairing check interfaces --- crates/accelerators/src/ffi/bls12_381.rs | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 crates/accelerators/src/ffi/bls12_381.rs 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, + } +} From b3251ee9fc2f9a3371820ec4ffdc631673130090 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 15:12:25 -0400 Subject: [PATCH 32/44] feat(tests): BLS12-381 tests --- .../tests/conformance/bls12_381.rs | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 crates/accelerators/tests/conformance/bls12_381.rs 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); +} From 27d322675dbc6d46bc6fedcadeee28f0e7d58fdf Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 15:13:13 -0400 Subject: [PATCH 33/44] fix: expose functions --- crates/accelerators/src/ffi/mod.rs | 4 ++++ crates/accelerators/src/ops/mod.rs | 6 ++++++ crates/accelerators/tests/conformance/main.rs | 2 ++ 3 files changed, 12 insertions(+) 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/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/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; From 37cc4daca84f8c5256f4bad761f4e992f63c28ce Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 16:54:35 -0400 Subject: [PATCH 34/44] refactor: move bls fp length constant into codec --- crates/accelerators/src/ops/bls12_381/codec.rs | 2 +- crates/accelerators/src/ops/bls12_381/mod.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/accelerators/src/ops/bls12_381/codec.rs b/crates/accelerators/src/ops/bls12_381/codec.rs index 0d9fda155..8d8771875 100644 --- a/crates/accelerators/src/ops/bls12_381/codec.rs +++ b/crates/accelerators/src/ops/bls12_381/codec.rs @@ -10,7 +10,7 @@ use crate::{ types::{ZkvmBls12381G1Point, ZkvmBls12381G2Point, ZkvmBls12381Scalar}, }; -use super::BLS_FP_LEN; +const BLS_FP_LEN: usize = 48; #[inline] fn read_bls_fp(input: &[u8]) -> Result { diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 2c62ff7d4..569d304e2 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -23,8 +23,6 @@ use crate::{ }, }; -pub(super) const BLS_FP_LEN: usize = 48; - /// BLS12-381 G1 point addition (precompile 0x0b). Inputs are `x || y`. /// /// Per EIP-2537 G1ADD, inputs are validated on-curve only, not for subgroup From 1e3c851b40e13e1991f7b2ff1dec812a323a2417 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 18:35:00 -0400 Subject: [PATCH 35/44] feat(ops): map-to-curve implementation --- Cargo.lock | 3 + crates/accelerators/Cargo.toml | 3 + .../accelerators/src/ops/bls12_381/codec.rs | 3 +- crates/accelerators/src/ops/bls12_381/map.rs | 83 +++++++++++++++++++ crates/accelerators/src/ops/bls12_381/mod.rs | 6 ++ crates/accelerators/src/ops/mod.rs | 3 +- 6 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 crates/accelerators/src/ops/bls12_381/map.rs diff --git a/Cargo.lock b/Cargo.lock index 2f475c492..5e5419547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6789,6 +6789,9 @@ dependencies = [ name = "openvm-accelerators" version = "0.4.0" dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-serialize 0.5.0", "aurora-engine-modexp", "hex-literal", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 14a75e5af..e919e9fd2 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -20,6 +20,9 @@ openvm-keccak256.workspace = true openvm-sha2.workspace = true # crypto +ark-bls12-381 = { version = "0.5", default-features = false, features = ["curve"] } +ark-ec = { version = "0.5", default-features = false } +ark-serialize = { version = "0.5", default-features = false } aurora-engine-modexp = { version = "1.2.0", default-features = false } ripemd = { version = "0.1.3", default-features = false } diff --git a/crates/accelerators/src/ops/bls12_381/codec.rs b/crates/accelerators/src/ops/bls12_381/codec.rs index 8d8771875..f6c5d7ebc 100644 --- a/crates/accelerators/src/ops/bls12_381/codec.rs +++ b/crates/accelerators/src/ops/bls12_381/codec.rs @@ -5,13 +5,12 @@ use openvm_curve_utils::SubgroupCheck; use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint, Group}; use openvm_pairing::bls12_381 as bls; +use super::BLS_FP_LEN; 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) diff --git a/crates/accelerators/src/ops/bls12_381/map.rs b/crates/accelerators/src/ops/bls12_381/map.rs new file mode 100644 index 000000000..c08625647 --- /dev/null +++ b/crates/accelerators/src/ops/bls12_381/map.rs @@ -0,0 +1,83 @@ +//! BLS12-381 map-to-curve (EIP-2537). +//! +//! Implemented using arkworks. + +use ark_bls12_381::{Fq, Fq2, G1Affine, G2Affine}; +use ark_ec::{ + hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve}, + AffineRepr, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use super::BLS_FP_LEN; +use crate::{ + ops::Error, + types::{ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1Point, ZkvmBls12381G2Point}, +}; + +/// BLS12-381 map field element to G1 (precompile 0x10). +pub fn bls12_381_map_fp_to_g1( + fp: &ZkvmBls12381Fp, + output: &mut ZkvmBls12381G1Point, +) -> Result<(), Error> { + let fp = read_fq(&fp.data)?; + let point = WBMap::map_to_curve(fp).map_err(|_| Error::FieldElementInvalid)?.clear_cofactor(); + + encode_g1_point(&point, output); + Ok(()) +} + +/// BLS12-381 map field element to G2 (precompile 0x11). Input is `c0 || c1`. +pub fn bls12_381_map_fp2_to_g2( + fp2: &ZkvmBls12381Fp2, + output: &mut ZkvmBls12381G2Point, +) -> Result<(), Error> { + let c0 = read_fq(&fp2.data[..BLS_FP_LEN])?; + let c1 = read_fq(&fp2.data[BLS_FP_LEN..])?; + let point = WBMap::map_to_curve(Fq2::new(c0, c1)) + .map_err(|_| Error::FieldElementInvalid)? + .clear_cofactor(); + + encode_g2_point(&point, output); + Ok(()) +} + +/// Reads a big-endian field element, rejecting non-canonical encodings. +fn read_fq(input_be: &[u8]) -> Result { + let mut input_le = [0u8; BLS_FP_LEN]; + input_le.copy_from_slice(input_be); + input_le.reverse(); + + Fq::deserialize_uncompressed(&input_le[..]).map_err(|_| Error::FieldElementInvalid) +} + +/// Writes a field element as big-endian bytes. +fn encode_fq(fq: &Fq, output: &mut [u8]) { + fq.serialize_uncompressed(&mut output[..]).expect("Failed to serialize field element"); + output.reverse(); +} + +/// Writes a G1 point as `x || y`; the point at infinity encodes as zeros. +fn encode_g1_point(point: &G1Affine, output: &mut ZkvmBls12381G1Point) { + let Some((x, y)) = point.xy() else { + output.data.fill(0); + return; + }; + + encode_fq(&x, &mut output.data[..BLS_FP_LEN]); + encode_fq(&y, &mut output.data[BLS_FP_LEN..]); +} + +/// Writes a G2 point as `x_c0 || x_c1 || y_c0 || y_c1`; the point at infinity +/// encodes as zeros. +fn encode_g2_point(point: &G2Affine, output: &mut ZkvmBls12381G2Point) { + let Some((x, y)) = point.xy() else { + output.data.fill(0); + return; + }; + + encode_fq(&x.c0, &mut output.data[..BLS_FP_LEN]); + encode_fq(&x.c1, &mut output.data[BLS_FP_LEN..2 * BLS_FP_LEN]); + encode_fq(&y.c0, &mut output.data[2 * BLS_FP_LEN..3 * BLS_FP_LEN]); + encode_fq(&y.c1, &mut output.data[3 * BLS_FP_LEN..]); +} diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 569d304e2..17e19695b 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -1,6 +1,9 @@ //! BLS12-381 group operations (EIP-2537). mod codec; +mod map; + +pub use map::{bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1}; use alloc::vec::Vec; @@ -23,6 +26,9 @@ use crate::{ }, }; +/// The number of bytes needed to represent an element of the base field Fp. +const BLS_FP_LEN: usize = 48; + /// BLS12-381 G1 point addition (precompile 0x0b). Inputs are `x || y`. /// /// Per EIP-2537 G1ADD, inputs are validated on-curve only, not for subgroup diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index e65ab9083..258a55ac0 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -14,7 +14,8 @@ 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, + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, 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}; From 35a9d81d6b43bcd8857211cdd2340b2c7dd4f589 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 18:52:18 -0400 Subject: [PATCH 36/44] feat(ffi): map-to-curve C Interface --- crates/accelerators/src/ffi/bls12_381.rs | 56 ++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs index a12a7e360..9dcc6c73b 100644 --- a/crates/accelerators/src/ffi/bls12_381.rs +++ b/crates/accelerators/src/ffi/bls12_381.rs @@ -1,10 +1,10 @@ -//! C ABI for the BLS12-381 add/MSM accelerators (EIP-2537). +//! C ABI for the BLS12-381 add/MSM/map accelerators (EIP-2537). use crate::{ ops, types::{ - ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, - ZkvmBls12381PairingPair, ZkvmStatus, + ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, + ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmStatus, }, }; @@ -147,3 +147,53 @@ pub unsafe extern "C" fn zkvm_bls12_pairing( Err(_) => ZkvmStatus::Fail, } } + +/// BLS12-381 map field element to G1 (precompile 0x10, EIP-2537). +/// +/// Returns [`ZkvmStatus::Fail`] if either pointer is NULL or the field element +/// is not canonical. +/// +/// # Safety +/// +/// - `field_element`, if non-NULL, must be valid for reads of 48 bytes. +/// - `result`, if non-NULL, must be valid for writes of 96 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_map_fp_to_g1( + field_element: *const ZkvmBls12381Fp, + result: *mut ZkvmBls12381G1Point, +) -> ZkvmStatus { + if field_element.is_null() || result.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (field_element, result) = unsafe { (&*field_element, &mut *result) }; + match ops::bls12_381_map_fp_to_g1(field_element, result) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} + +/// BLS12-381 map field element to G2 (precompile 0x11, EIP-2537). +/// +/// Returns [`ZkvmStatus::Fail`] if either pointer is NULL or either half of +/// the field element is not canonical. +/// +/// # Safety +/// +/// - `field_element`, if non-NULL, must be valid for reads of 96 bytes. +/// - `result`, if non-NULL, must be valid for writes of 192 bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( + field_element: *const ZkvmBls12381Fp2, + result: *mut ZkvmBls12381G2Point, +) -> ZkvmStatus { + if field_element.is_null() || result.is_null() { + return ZkvmStatus::Fail; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let (field_element, result) = unsafe { (&*field_element, &mut *result) }; + match ops::bls12_381_map_fp2_to_g2(field_element, result) { + Ok(()) => ZkvmStatus::Ok, + Err(_) => ZkvmStatus::Fail, + } +} From f4ca98a8557c3183339c619a5f311a804625fabd Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 29 Jul 2026 18:53:02 -0400 Subject: [PATCH 37/44] feat(tests): map-to-curve tests --- .../tests/conformance/bls12_381.rs | 176 +++++++++++++++++- 1 file changed, 171 insertions(+), 5 deletions(-) diff --git a/crates/accelerators/tests/conformance/bls12_381.rs b/crates/accelerators/tests/conformance/bls12_381.rs index 94a550857..d1d827d73 100644 --- a/crates/accelerators/tests/conformance/bls12_381.rs +++ b/crates/accelerators/tests/conformance/bls12_381.rs @@ -1,18 +1,19 @@ -//! BLS12-381 add/MSM/pairing conformance vectors. +//! BLS12-381 add/MSM/pairing/map 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, + zkvm_bls12_map_fp2_to_g2, zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, }, ops::{ bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_pairing_check, + bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, Error, }, types::{ - ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, - ZkvmBls12381PairingPair, ZkvmBls12381Scalar, ZkvmStatus, + ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, + ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBls12381Scalar, + ZkvmStatus, }, }; @@ -225,3 +226,168 @@ fn zkvm_bls12_null_pointers() { let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), core::ptr::null_mut()) }; assert_eq!(status, ZkvmStatus::Fail); } + +/* ============================================================================ + * Map to curve + * ============================================================================ */ + +/// Official EIP-2537 vectors for MAP_FP_TO_G1, from https://github.com/ethereum/EIPs/blob/master/assets/eip-2537/map_fp_to_G1_bls.json +const MAP_FP_TO_G1_VECTORS: [([u8; 48], [u8; 96]); 2] = [ + // "bls_g1map_" + ( + hex!("156c8a6a2c184569d69a76be144b5cdc5141d2d2ca4fe341f011e25e3969c55ad9e9b9ce2eb833c81a908e5fa4ac5f03"), + hex!( + "184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba" + "04407b8d35af4dacc809927071fc0405218f1401a6d15af775810e4e460064bcc9468beeba82fdc751be70476c888bf3" + ), + ), + // "bls_g1map_616263" + ( + hex!("147e1ed29f06e4c5079b9d14fc89d2820d32419b990c1c7bb7dbea2a36a045124b31ffbde7c99329c05c559af1c6cc82"), + hex!( + "009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d" + "1532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c" + ), + ), +]; + +/// Official EIP-2537 vectors for MAP_FP2_TO_G2, from https://github.com/ethereum/EIPs/blob/master/assets/eip-2537/map_fp2_to_G2_bls.json +const MAP_FP2_TO_G2_VECTORS: [([u8; 96], [u8; 192]); 2] = [ + // "bls_g2map_" + ( + hex!( + "07355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b04" + "02829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c" + ), + hex!( + "00e7f4568a82b4b7dc1f14c6aaa055edf51502319c723c4dc2688c7fe5944c213f510328082396515734b6612c4e7bb7" + "126b855e9e69b1f691f816e48ac6977664d24d99f8724868a184186469ddfd4617367e94527d4b74fc86413483afb35b" + "0caead0fd7b6176c01436833c79d305c78be307da5f6af6c133c47311def6ff1e0babf57a0fb5539fce7ee12407b0a42" + "1498aadcf7ae2b345243e281ae076df6de84455d766ab6fcdaad71fab60abb2e8b980a440043cd305db09d283c895e3d" + ), + ), + // "bls_g2map_616263" + ( + hex!( + "138879a9559e24cecee8697b8b4ad32cced053138ab913b99872772dc753a2967ed50aabc907937aefb2439ba06cc50c" + "0a1ae7999ea9bab1dcc9ef8887a6cb6e8f1e22566015428d220b7eec90ffa70ad1f624018a9ad11e78d588bd3617f9f2" + ), + hex!( + "108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f" + "0296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d" + "033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee656" + "153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f" + ), + ), +]; + +/// The largest canonical field element. +const BLS_FP_MAX: [u8; 48] = + hex!("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa"); + +/// The base field modulus itself, which is not a canonical field element. +const BLS_FP_MODULUS: [u8; 48] = + hex!("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"); + +#[test] +fn bls12_map_fp_to_g1_vectors() { + for (input, expected) in MAP_FP_TO_G1_VECTORS { + let mut output = ZkvmBls12381G1Point { data: [0xff; 96] }; + bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: input }, &mut output).unwrap(); + assert_eq!(output.data, expected, "input={input:?}"); + } +} + +#[test] +fn bls12_map_fp2_to_g2_vectors() { + for (input, expected) in MAP_FP2_TO_G2_VECTORS { + let mut output = ZkvmBls12381G2Point { data: [0xff; 192] }; + bls12_381_map_fp2_to_g2(&ZkvmBls12381Fp2 { data: input }, &mut output).unwrap(); + assert_eq!(output.data, expected, "input={input:?}"); + } +} + +/// Mapped points must be valid members of the prime-order subgroup. +/// +/// MSM re-parses the point and validates subgroup membership, so this is an +/// independent check that the cofactor was cleared; multiplying by one must +/// return the point itself. +#[test] +fn bls12_map_lands_in_prime_order_subgroup() { + let mapped = ZkvmBls12381G1Point { data: MAP_FP_TO_G1_VECTORS[0].1 }; + let mut output = ZkvmBls12381G1Point { data: [0; 96] }; + bls12_381_g1_msm(&[ZkvmBls12381G1MsmPair { point: mapped, scalar: scalar(1) }], &mut output) + .expect("mapped G1 point must be in the prime-order subgroup"); + assert_eq!(output.data, mapped.data); + + let mapped = ZkvmBls12381G2Point { data: MAP_FP2_TO_G2_VECTORS[0].1 }; + let mut output = ZkvmBls12381G2Point { data: [0; 192] }; + bls12_381_g2_msm(&[ZkvmBls12381G2MsmPair { point: mapped, scalar: scalar(1) }], &mut output) + .expect("mapped G2 point must be in the prime-order subgroup"); + assert_eq!(output.data, mapped.data); +} + +#[test] +fn bls12_map_field_element_range() { + let mut g1 = ZkvmBls12381G1Point { data: [0; 96] }; + let mut g2 = ZkvmBls12381G2Point { data: [0; 192] }; + + // The largest canonical element is accepted, the modulus itself is not. + assert_eq!(bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: BLS_FP_MAX }, &mut g1), Ok(())); + assert_eq!( + bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: BLS_FP_MODULUS }, &mut g1), + Err(Error::FieldElementInvalid) + ); + assert_eq!( + bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: [0xff; 48] }, &mut g1), + Err(Error::FieldElementInvalid) + ); + + // Either half of an Fp2 input is checked. + let mut c0_bad = ZkvmBls12381Fp2 { data: [0; 96] }; + c0_bad.data[..48].copy_from_slice(&BLS_FP_MODULUS); + assert_eq!(bls12_381_map_fp2_to_g2(&c0_bad, &mut g2), Err(Error::FieldElementInvalid)); + + let mut c1_bad = ZkvmBls12381Fp2 { data: [0; 96] }; + c1_bad.data[48..].copy_from_slice(&BLS_FP_MODULUS); + assert_eq!(bls12_381_map_fp2_to_g2(&c1_bad, &mut g2), Err(Error::FieldElementInvalid)); +} + +#[test] +fn zkvm_bls12_map_smoke() { + let (fp, expected) = MAP_FP_TO_G1_VECTORS[0]; + let field_element = ZkvmBls12381Fp { data: fp }; + let mut g1 = ZkvmBls12381G1Point { data: [0xff; 96] }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, &mut g1) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g1.data, expected); + + let (fp2, expected) = MAP_FP2_TO_G2_VECTORS[0]; + let field_element = ZkvmBls12381Fp2 { data: fp2 }; + let mut g2 = ZkvmBls12381G2Point { data: [0xff; 192] }; + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, &mut g2) }; + assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(g2.data, expected); + + // A non-canonical field element maps to the failure status. + let not_canonical = ZkvmBls12381Fp { data: BLS_FP_MODULUS }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(¬_canonical, &mut g1) }; + assert_eq!(status, ZkvmStatus::Fail); +} + +#[test] +fn zkvm_bls12_map_null_pointers() { + let field_element = ZkvmBls12381Fp { data: MAP_FP_TO_G1_VECTORS[0].0 }; + let mut g1 = ZkvmBls12381G1Point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(core::ptr::null(), &mut g1) }; + assert_eq!(status, ZkvmStatus::Fail); + let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); + + let field_element = ZkvmBls12381Fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }; + let mut g2 = ZkvmBls12381G2Point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_map_fp2_to_g2(core::ptr::null(), &mut g2) }; + assert_eq!(status, ZkvmStatus::Fail); + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, core::ptr::null_mut()) }; + assert_eq!(status, ZkvmStatus::Fail); +} From d4d9fa85b767b37d9f6ca1dc582c895075fec4af Mon Sep 17 00:00:00 2001 From: Ayush Shukla Date: Tue, 11 Aug 2026 22:40:31 +0200 Subject: [PATCH 38/44] refactor: dogfood zkVM accelerator interface --- Cargo.lock | 11 +- Cargo.toml | 2 + bin/stateless-guest/Cargo.lock | 34 +- crates/accelerators/Cargo.toml | 6 + crates/accelerators/src/ffi/blake2.rs | 12 +- crates/accelerators/src/ffi/bls12_381.rs | 91 +- crates/accelerators/src/ffi/bn254.rs | 43 +- crates/accelerators/src/ffi/ecdsa.rs | 49 +- crates/accelerators/src/ffi/hash.rs | 22 +- crates/accelerators/src/ffi/kzg.rs | 20 +- crates/accelerators/src/ffi/modexp.rs | 13 +- crates/accelerators/src/ops/blake2/mod.rs | 44 +- .../accelerators/src/ops/blake2/portable.rs | 76 -- crates/accelerators/src/ops/bls12_381/map.rs | 8 +- crates/accelerators/src/ops/bls12_381/mod.rs | 102 +- crates/accelerators/src/ops/bn254/mod.rs | 36 +- crates/accelerators/src/ops/mod.rs | 27 +- crates/accelerators/src/ops/modexp.rs | 57 +- crates/accelerators/src/types.rs | 28 +- .../tests/conformance/bls12_381.rs | 10 +- .../accelerators/tests/conformance/ecdsa.rs | 25 +- crates/accelerators/tests/conformance/kzg.rs | 4 +- .../accelerators/tests/conformance/modexp.rs | 12 +- crates/revm-crypto/Cargo.toml | 18 +- crates/revm-crypto/src/lib.rs | 879 +++++++----------- 25 files changed, 719 insertions(+), 910 deletions(-) delete mode 100644 crates/accelerators/src/ops/blake2/portable.rs diff --git a/Cargo.lock b/Cargo.lock index 5e5419547..a89c4bd7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6804,6 +6804,7 @@ dependencies = [ "openvm-pairing-guest", "openvm-sha2", "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", + "revm-precompile 36.0.3", "ripemd", ] @@ -7751,15 +7752,7 @@ version = "0.4.0" dependencies = [ "alloy-consensus", "alloy-primitives", - "aurora-engine-modexp", - "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", - "openvm-pairing", - "openvm-sha2", - "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", + "openvm-accelerators", "revm 40.0.3", "revm-primitives 24.0.1", ] diff --git a/Cargo.toml b/Cargo.toml index b9aa23503..0e45bb4a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ zstd = { version = "0.13", default-features = false } # workspace openvm-curve-utils = { path = "./crates/curve-utils", default-features = false } +openvm-accelerators = { path = "./crates/accelerators", default-features = false } openvm-kzg = { path = "./crates/kzg", default-features = false } openvm-mpt = { path = "./crates/mpt" } openvm-revm-crypto = { path = "./crates/revm-crypto" } @@ -86,6 +87,7 @@ reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v2.3.0", d # revm revm = { version = "=40.0.3", features = ["serde"], default-features = false } +revm-precompile = { version = "=36.0.3", default-features = false } revm-primitives = { version = "=24.0.1", default-features = false } # alloy diff --git a/bin/stateless-guest/Cargo.lock b/bin/stateless-guest/Cargo.lock index 46c3bb510..8279dd5bf 100644 --- a/bin/stateless-guest/Cargo.lock +++ b/bin/stateless-guest/Cargo.lock @@ -2463,6 +2463,28 @@ dependencies = [ "serde", ] +[[package]] +name = "openvm-accelerators" +version = "0.4.0" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-serialize 0.5.0", + "aurora-engine-modexp", + "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", + "openvm-pairing", + "openvm-pairing-guest", + "openvm-sha2", + "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", + "revm-precompile", + "ripemd", +] + [[package]] name = "openvm-algebra-complex-macros" version = "2.0.0" @@ -2627,6 +2649,7 @@ version = "2.0.0" source = "git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0#ec5180e9ee15234aff8070febcbb2733a2adad74" dependencies = [ "group", + "halo2curves-axiom", "hex-literal", "itertools 0.14.0", "num-bigint", @@ -2649,6 +2672,7 @@ name = "openvm-pairing-guest" version = "2.0.0" source = "git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0#ec5180e9ee15234aff8070febcbb2733a2adad74" dependencies = [ + "halo2curves-axiom", "hex-literal", "itertools 0.14.0", "lazy_static", @@ -2681,15 +2705,7 @@ version = "0.4.0" dependencies = [ "alloy-consensus", "alloy-primitives", - "aurora-engine-modexp", - "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", - "openvm-pairing", - "openvm-sha2", - "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", + "openvm-accelerators", "revm", "revm-primitives", ] diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index e919e9fd2..5b3baff20 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -25,6 +25,7 @@ ark-ec = { version = "0.5", default-features = false } ark-serialize = { version = "0.5", default-features = false } aurora-engine-modexp = { version = "1.2.0", default-features = false } ripemd = { version = "0.1.3", default-features = false } +revm-precompile.workspace = true # The OpenVM-accelerated k256 fork; its ECDSA recovery relies on zkVM hints # and is unimplemented outside the guest. @@ -51,6 +52,11 @@ ignored = ["openvm-pairing-guest"] [package.metadata.cargo-machete] ignored = ["openvm-pairing-guest"] +[[test]] +name = "conformance" +path = "tests/conformance/main.rs" +required-features = ["ffi"] + [features] default = ["ffi"] # The extern "C" `zkvm_*` symbols. Rust consumers that only need `ops` can diff --git a/crates/accelerators/src/ffi/blake2.rs b/crates/accelerators/src/ffi/blake2.rs index fb43c0ff4..a08840a7f 100644 --- a/crates/accelerators/src/ffi/blake2.rs +++ b/crates/accelerators/src/ffi/blake2.rs @@ -26,10 +26,12 @@ pub unsafe extern "C" fn zkvm_blake2f( if h.is_null() || m.is_null() || t.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (h, m, t) = unsafe { (&mut *h, &*m, &*t) }; - match ops::blake2f(rounds, h, m, t, f) { - Ok(()) => ZkvmStatus::Ok, - Err(_) => ZkvmStatus::Fail, + // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. + let (mut state, message, offset) = unsafe { (h.read(), m.read(), t.read()) }; + if ops::blake2f(rounds, &mut state, &message, &offset, f).is_err() { + return ZkvmStatus::Fail; } + // SAFETY: `h` is non-NULL and valid for writes. + unsafe { h.write(state) }; + ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs index 9dcc6c73b..3e802b12a 100644 --- a/crates/accelerators/src/ffi/bls12_381.rs +++ b/crates/accelerators/src/ffi/bls12_381.rs @@ -26,10 +26,15 @@ pub unsafe extern "C" fn zkvm_bls12_g1_add( 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, + // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. + let (p1, p2) = unsafe { (p1.read(), p2.read()) }; + let mut value = ZkvmBls12381G1Point { data: [0; 96] }; + match ops::bls12_381_g1_add(&p1, &p2, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -55,10 +60,13 @@ pub unsafe extern "C" fn zkvm_bls12_g1_msm( // 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, + let mut value = ZkvmBls12381G1Point { data: [0; 96] }; + match ops::bls12_381_g1_msm(pairs, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -81,10 +89,15 @@ pub unsafe extern "C" fn zkvm_bls12_g2_add( 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, + // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. + let (p1, p2) = unsafe { (p1.read(), p2.read()) }; + let mut value = ZkvmBls12381G2Point { data: [0; 192] }; + match ops::bls12_381_g2_add(&p1, &p2, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -110,10 +123,13 @@ pub unsafe extern "C" fn zkvm_bls12_g2_msm( // 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, + let mut value = ZkvmBls12381G2Point { data: [0; 192] }; + match ops::bls12_381_g2_msm(pairs, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -140,11 +156,18 @@ pub unsafe extern "C" fn zkvm_bls12_pairing( // 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, + let mut value = false; + match ops::bls12_381_pairing_check(pairs, &mut value) { + Ok(()) => { + // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. + unsafe { verified.write(value) }; + ZkvmStatus::Ok + } + Err(_) => { + // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. + unsafe { verified.write(false) }; + ZkvmStatus::Fail + } } } @@ -165,10 +188,15 @@ pub unsafe extern "C" fn zkvm_bls12_map_fp_to_g1( if field_element.is_null() || result.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (field_element, result) = unsafe { (&*field_element, &mut *result) }; - match ops::bls12_381_map_fp_to_g1(field_element, result) { - Ok(()) => ZkvmStatus::Ok, + // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. + let field_element = unsafe { field_element.read() }; + let mut value = ZkvmBls12381G1Point { data: [0; 96] }; + match ops::bls12_381_map_fp_to_g1(&field_element, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -190,10 +218,15 @@ pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( if field_element.is_null() || result.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (field_element, result) = unsafe { (&*field_element, &mut *result) }; - match ops::bls12_381_map_fp2_to_g2(field_element, result) { - Ok(()) => ZkvmStatus::Ok, + // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. + let field_element = unsafe { field_element.read() }; + let mut value = ZkvmBls12381G2Point { data: [0; 192] }; + match ops::bls12_381_map_fp2_to_g2(&field_element, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } diff --git a/crates/accelerators/src/ffi/bn254.rs b/crates/accelerators/src/ffi/bn254.rs index a866c845c..7dae5301f 100644 --- a/crates/accelerators/src/ffi/bn254.rs +++ b/crates/accelerators/src/ffi/bn254.rs @@ -23,10 +23,15 @@ pub unsafe extern "C" fn zkvm_bn254_g1_add( 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, + // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. + let (p1, p2) = unsafe { (p1.read(), p2.read()) }; + let mut value = ZkvmBn254G1Point { data: [0; 64] }; + match ops::bn254_g1_add(&p1, &p2, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -52,10 +57,15 @@ pub unsafe extern "C" fn zkvm_bn254_g1_mul( 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, + // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. + let (point, scalar) = unsafe { (point.read(), scalar.read()) }; + let mut value = ZkvmBn254G1Point { data: [0; 64] }; + match ops::bn254_g1_mul(&point, &scalar, &mut value) { + Ok(()) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -81,10 +91,17 @@ pub unsafe extern "C" fn zkvm_bn254_pairing( // 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, + let mut value = false; + match ops::bn254_pairing_check(pairs, &mut value) { + Ok(()) => { + // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. + unsafe { verified.write(value) }; + ZkvmStatus::Ok + } + Err(_) => { + // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. + unsafe { verified.write(false) }; + ZkvmStatus::Fail + } } } diff --git a/crates/accelerators/src/ffi/ecdsa.rs b/crates/accelerators/src/ffi/ecdsa.rs index 266771893..a3396c33b 100644 --- a/crates/accelerators/src/ffi/ecdsa.rs +++ b/crates/accelerators/src/ffi/ecdsa.rs @@ -29,10 +29,15 @@ pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( if msg.is_null() || sig.is_null() || output.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (msg, sig, output) = unsafe { (&*msg, &*sig, &mut *output) }; - match ops::secp256k1_ecrecover(msg, sig, recid, output) { - Ok(()) => ZkvmStatus::Ok, + // SAFETY: the non-NULL inputs are valid for reads. Copying before writing supports overlap. + let (msg, sig) = unsafe { (msg.read(), sig.read()) }; + let mut value = ZkvmSecp256k1Pubkey { data: [0; 64] }; + match ops::secp256k1_ecrecover(&msg, &sig, recid, &mut value) { + Ok(()) => { + // SAFETY: `output` is non-NULL and valid for writes. + unsafe { output.write(value) }; + ZkvmStatus::Ok + } Err(_) => ZkvmStatus::Fail, } } @@ -40,9 +45,8 @@ pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( /// Verify an ECDSA signature over secp256k1 against an uncompressed public /// key, writing the result to `verified`. /// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are -/// malformed; `verified` is `false` when a well-formed signature does not -/// verify. +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid +/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. /// /// # Safety /// @@ -60,20 +64,20 @@ pub unsafe extern "C" fn zkvm_secp256k1_verify( if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (msg, sig, pubkey, verified) = unsafe { (&*msg, &*sig, &*pubkey, &mut *verified) }; - match ops::secp256k1_verify(msg, sig, pubkey, verified) { - Ok(()) => ZkvmStatus::Ok, - Err(_) => ZkvmStatus::Fail, - } + // SAFETY: the non-NULL inputs are valid for reads. + let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; + let mut value = false; + let _ = ops::secp256k1_verify(&msg, &sig, &pubkey, &mut value); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZkvmStatus::Ok } /// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed /// public key, writing the result to `verified`. /// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are -/// malformed; `verified` is `false` when a well-formed signature does not -/// verify. +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid +/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. /// /// # Safety /// @@ -91,10 +95,11 @@ pub unsafe extern "C" fn zkvm_secp256r1_verify( if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (msg, sig, pubkey, verified) = unsafe { (&*msg, &*sig, &*pubkey, &mut *verified) }; - match ops::secp256r1_verify(msg, sig, pubkey, verified) { - Ok(()) => ZkvmStatus::Ok, - Err(_) => ZkvmStatus::Fail, - } + // SAFETY: the non-NULL inputs are valid for reads. + let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; + let mut value = false; + let _ = ops::secp256r1_verify(&msg, &sig, &pubkey, &mut value); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs index 0deb16129..16f950bb7 100644 --- a/crates/accelerators/src/ffi/hash.rs +++ b/crates/accelerators/src/ffi/hash.rs @@ -26,9 +26,11 @@ pub unsafe extern "C" fn zkvm_keccak256( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let output = unsafe { &mut *output }; - ops::keccak256(data, output); + let mut value = ZkvmKeccak256Hash { data: [0; 32] }; + ops::keccak256(data, &mut value); + // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so + // overlapping input/output storage is supported. + unsafe { output.write(value) }; ZkvmStatus::Ok } @@ -53,9 +55,10 @@ pub unsafe extern "C" fn zkvm_sha256( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let output = unsafe { &mut *output }; - ops::sha256(data, output); + let mut value = ZkvmSha256Hash { data: [0; 32] }; + ops::sha256(data, &mut value); + // SAFETY: see `zkvm_keccak256`. + unsafe { output.write(value) }; ZkvmStatus::Ok } @@ -83,8 +86,9 @@ pub unsafe extern "C" fn zkvm_ripemd160( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let output = unsafe { &mut *output }; - ops::ripemd160(data, output); + let mut value = ZkvmRipemd160Hash { data: [0; 32] }; + ops::ripemd160(data, &mut value); + // SAFETY: see `zkvm_keccak256`. + unsafe { output.write(value) }; ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ffi/kzg.rs b/crates/accelerators/src/ffi/kzg.rs index 8fb2b60ec..4b23d1bed 100644 --- a/crates/accelerators/src/ffi/kzg.rs +++ b/crates/accelerators/src/ffi/kzg.rs @@ -8,9 +8,8 @@ use crate::{ /// Verify a KZG proof that the blob committed to by `commitment` evaluates /// to `y` at point `z`, writing the result to `verified`. /// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL or the inputs are -/// malformed; `verified` is `false` when a well-formed proof does not -/// verify. +/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid +/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. /// /// # Safety /// @@ -28,11 +27,12 @@ pub unsafe extern "C" fn zkvm_kzg_point_eval( if commitment.is_null() || z.is_null() || y.is_null() || proof.is_null() || verified.is_null() { return ZkvmStatus::Fail; } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let (commitment, z, y, proof, verified) = - unsafe { (&*commitment, &*z, &*y, &*proof, &mut *verified) }; - match ops::kzg_point_eval(commitment, z, y, proof, verified) { - Ok(()) => ZkvmStatus::Ok, - Err(_) => ZkvmStatus::Fail, - } + // SAFETY: the non-NULL inputs are valid for reads. + let (commitment, z, y, proof) = + unsafe { (commitment.read(), z.read(), y.read(), proof.read()) }; + let mut value = false; + let _ = ops::kzg_point_eval(&commitment, &z, &y, &proof, &mut value); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ffi/modexp.rs b/crates/accelerators/src/ffi/modexp.rs index 3733d987f..db86c7a13 100644 --- a/crates/accelerators/src/ffi/modexp.rs +++ b/crates/accelerators/src/ffi/modexp.rs @@ -37,12 +37,11 @@ pub unsafe extern "C" fn zkvm_modexp( // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let modulus = if mod_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(modulus, mod_len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let output = if mod_len == 0 { - &mut [][..] - } else { - unsafe { core::slice::from_raw_parts_mut(output, mod_len) } - }; - ops::modexp(base, exp, modulus, output); + let value = ops::modexp_result(base, exp, modulus); + if mod_len != 0 { + // SAFETY: `output` is non-NULL and valid for `mod_len` writes. `value` cannot overlap it, + // and all caller-provided input reads are complete. + unsafe { core::ptr::copy_nonoverlapping(value.as_ptr(), output, mod_len) }; + } ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ops/blake2/mod.rs b/crates/accelerators/src/ops/blake2/mod.rs index 06d0c1866..4714055a7 100644 --- a/crates/accelerators/src/ops/blake2/mod.rs +++ b/crates/accelerators/src/ops/blake2/mod.rs @@ -1,45 +1,13 @@ //! BLAKE2b compression function F (EIP-152). //! -//! Vendored from revm-precompile, which adapted the compression function from -//! [`blake2b_simd`](https://github.com/oconnor663/blake2_simd) (MIT license) -//! for EIP-152 variable round counts. - -mod portable; +//! Uses REVM's EIP-152 implementation so the standard interface and REVM's +//! precompile provider share one compression function. use crate::{ ops::Error, types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, }; -type Word = u64; - -const IV: [Word; 8] = [ - 0x6A09E667F3BCC908, - 0xBB67AE8584CAA73B, - 0x3C6EF372FE94F82B, - 0xA54FF53A5F1D36F1, - 0x510E527FADE682D1, - 0x9B05688C2B3E6C1F, - 0x1F83D9ABFB41BD6B, - 0x5BE0CD19137E2179, -]; - -// SIGMA has spec period 10 (RFC 7693 §2.7). BLAKE2b runs 12 rounds by reusing -// SIGMA[0]/SIGMA[1] for rounds 10/11; for EIP-152's variable round count we -// must index with `r % 10`, not `r % 12`. -const SIGMA: [[u8; 16]; 10] = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], - [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], - [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], - [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], - [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], - [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], - [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], - [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], - [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], -]; - /// Apply the BLAKE2 compression function F to the state vector `h` in place. /// /// `h`, `m` and `t` hold little-endian words; `f` is the final-block @@ -68,10 +36,16 @@ pub fn blake2f( u64::from_le_bytes(t.data[8..].try_into().unwrap()), ]; - portable::compress(rounds, &mut state, &message, &offset, f == 1); + blake2f_words(rounds, &mut state, &message, &offset, f == 1); for (chunk, word) in h.data.chunks_exact_mut(8).zip(state.iter()) { chunk.copy_from_slice(&word.to_le_bytes()); } Ok(()) } + +/// Apply BLAKE2 compression to word-oriented state without byte conversion. +#[inline] +pub fn blake2f_words(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { + revm_precompile::blake2::compress(rounds, h, m, t, f); +} diff --git a/crates/accelerators/src/ops/blake2/portable.rs b/crates/accelerators/src/ops/blake2/portable.rs deleted file mode 100644 index 2e3a52b7f..000000000 --- a/crates/accelerators/src/ops/blake2/portable.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Adapted from https://github.com/oconnor663/blake2_simd -// Copyright (c) 2018 Jack O'Connor -// Licensed under the MIT license - -use super::{Word, IV, SIGMA}; - -// G is the mixing function, called eight times per round in the compression -// function. V is the 16-word state vector of the compression function, usually -// described as a 4x4 matrix. A, B, C, and D are the mixing indices, set by the -// caller first to the four columns of V, and then to its four diagonals. X and -// Y are words of input, chosen by the caller according to the message -// schedule, SIGMA. -#[inline(always)] -const fn g(v: &mut [Word; 16], a: usize, b: usize, c: usize, d: usize, x: Word, y: Word) { - v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); - v[d] = (v[d] ^ v[a]).rotate_right(32); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(24); - v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); - v[d] = (v[d] ^ v[a]).rotate_right(16); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(63); -} - -#[inline(always)] -const fn round(r: usize, m: &[Word; 16], v: &mut [Word; 16]) { - // Select the message schedule based on the round. - let s = SIGMA[r % 10]; - - // Mix the columns. - g(v, 0, 4, 8, 12, m[s[0] as usize], m[s[1] as usize]); - g(v, 1, 5, 9, 13, m[s[2] as usize], m[s[3] as usize]); - g(v, 2, 6, 10, 14, m[s[4] as usize], m[s[5] as usize]); - g(v, 3, 7, 11, 15, m[s[6] as usize], m[s[7] as usize]); - - // Mix the rows. - g(v, 0, 5, 10, 15, m[s[8] as usize], m[s[9] as usize]); - g(v, 1, 6, 11, 12, m[s[10] as usize], m[s[11] as usize]); - g(v, 2, 7, 8, 13, m[s[12] as usize], m[s[13] as usize]); - g(v, 3, 4, 9, 14, m[s[14] as usize], m[s[15] as usize]); -} - -pub(super) fn compress(rounds: u32, words: &mut [Word; 8], m: &[Word; 16], t: &[Word; 2], f: bool) { - // Initialize the compression state. - let mut v = [ - words[0], - words[1], - words[2], - words[3], - words[4], - words[5], - words[6], - words[7], - IV[0], - IV[1], - IV[2], - IV[3], - IV[4] ^ t[0], - IV[5] ^ t[1], - IV[6] ^ if f { !0 } else { 0 }, - IV[7], - ]; - - for i in 0..rounds as usize { - round(i, m, &mut v); - } - - words[0] ^= v[0] ^ v[8]; - words[1] ^= v[1] ^ v[9]; - words[2] ^= v[2] ^ v[10]; - words[3] ^= v[3] ^ v[11]; - words[4] ^= v[4] ^ v[12]; - words[5] ^= v[5] ^ v[13]; - words[6] ^= v[6] ^ v[14]; - words[7] ^= v[7] ^ v[15]; -} diff --git a/crates/accelerators/src/ops/bls12_381/map.rs b/crates/accelerators/src/ops/bls12_381/map.rs index c08625647..93ab05d7a 100644 --- a/crates/accelerators/src/ops/bls12_381/map.rs +++ b/crates/accelerators/src/ops/bls12_381/map.rs @@ -21,7 +21,9 @@ pub fn bls12_381_map_fp_to_g1( output: &mut ZkvmBls12381G1Point, ) -> Result<(), Error> { let fp = read_fq(&fp.data)?; - let point = WBMap::map_to_curve(fp).map_err(|_| Error::FieldElementInvalid)?.clear_cofactor(); + let point = WBMap::map_to_curve(fp) + .expect("the arkworks WB map is defined for every field element") + .clear_cofactor(); encode_g1_point(&point, output); Ok(()) @@ -35,7 +37,7 @@ pub fn bls12_381_map_fp2_to_g2( let c0 = read_fq(&fp2.data[..BLS_FP_LEN])?; let c1 = read_fq(&fp2.data[BLS_FP_LEN..])?; let point = WBMap::map_to_curve(Fq2::new(c0, c1)) - .map_err(|_| Error::FieldElementInvalid)? + .expect("the arkworks WB map is defined for every field element") .clear_cofactor(); encode_g2_point(&point, output); @@ -53,7 +55,7 @@ fn read_fq(input_be: &[u8]) -> Result { /// Writes a field element as big-endian bytes. fn encode_fq(fq: &Fq, output: &mut [u8]) { - fq.serialize_uncompressed(&mut output[..]).expect("Failed to serialize field element"); + fq.serialize_uncompressed(&mut output[..]).expect("field element serialization is infallible"); output.reverse(); } diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 17e19695b..2b14a8564 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -19,7 +19,7 @@ use openvm_ecc_guest::{ use openvm_pairing::{bls12_381::Bls12_381, PairingCheck}; use crate::{ - ops::Error, + ops::{Error, StreamError}, types::{ ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, @@ -52,19 +52,33 @@ pub fn bls12_381_g1_msm( pairs: &[ZkvmBls12381G1MsmPair], output: &mut ZkvmBls12381G1Point, ) -> Result<(), Error> { - if pairs.is_empty() { - output.data = [0u8; 96]; - return Ok(()); - } + *output = bls12_381_g1_msm_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) + .map_err(|error| match error { + StreamError::Operation(error) => error, + StreamError::Source(never) => match never {}, + })?; + Ok(()) +} - let mut points = Vec::with_capacity(pairs.len()); - let mut scalars = Vec::with_capacity(pairs.len()); +/// BLS12-381 G1 MSM over a fallible stream, preserving input-error order. +pub fn bls12_381_g1_msm_iter( + pairs: impl IntoIterator>, +) -> Result> { + let pairs = pairs.into_iter(); + let capacity = pairs.size_hint().0; + + let mut points = Vec::with_capacity(capacity); + let mut scalars = Vec::with_capacity(capacity); for pair in pairs { - points.push(read_bls_g1_point(&pair.point)?); + let pair = pair.map_err(StreamError::Source)?; + points.push(read_bls_g1_point(&pair.point).map_err(StreamError::Operation)?); scalars.push(read_bls_scalar(&pair.scalar)); } - encode_bls_g1_point(&Bls12_381::msm(&scalars, &points), output); - Ok(()) + let mut output = ZkvmBls12381G1Point { data: [0; 96] }; + if !points.is_empty() { + encode_bls_g1_point(&Bls12_381::msm(&scalars, &points), &mut output); + } + Ok(output) } /// BLS12-381 G2 point addition (precompile 0x0d). @@ -90,19 +104,33 @@ pub fn bls12_381_g2_msm( pairs: &[ZkvmBls12381G2MsmPair], output: &mut ZkvmBls12381G2Point, ) -> Result<(), Error> { - if pairs.is_empty() { - output.data = [0u8; 192]; - return Ok(()); - } + *output = bls12_381_g2_msm_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) + .map_err(|error| match error { + StreamError::Operation(error) => error, + StreamError::Source(never) => match never {}, + })?; + Ok(()) +} - let mut points = Vec::with_capacity(pairs.len()); - let mut scalars = Vec::with_capacity(pairs.len()); +/// BLS12-381 G2 MSM over a fallible stream, preserving input-error order. +pub fn bls12_381_g2_msm_iter( + pairs: impl IntoIterator>, +) -> Result> { + let pairs = pairs.into_iter(); + let capacity = pairs.size_hint().0; + + let mut points = Vec::with_capacity(capacity); + let mut scalars = Vec::with_capacity(capacity); for pair in pairs { - points.push(read_bls_g2_point(&pair.point)?); + let pair = pair.map_err(StreamError::Source)?; + points.push(read_bls_g2_point(&pair.point).map_err(StreamError::Operation)?); scalars.push(read_bls_scalar(&pair.scalar)); } - encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points), output); - Ok(()) + let mut output = ZkvmBls12381G2Point { data: [0; 192] }; + if !points.is_empty() { + encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points), &mut output); + } + Ok(output) } /// BLS12-381 pairing check (precompile 0x0f). @@ -113,18 +141,32 @@ pub fn bls12_381_pairing_check( verified: &mut bool, ) -> Result<(), Error> { *verified = false; + let value = bls12_381_pairing_check_iter(pairs.iter().copied())?; + *verified = value; + Ok(()) +} - if pairs.is_empty() { - *verified = true; - return Ok(()); - } +/// BLS12-381 pairing check over a stream of encoded pairs. +pub fn bls12_381_pairing_check_iter( + pairs: impl IntoIterator, +) -> Result { + let pairs = pairs.into_iter(); + let capacity = pairs.size_hint().0; - let mut g1_points = Vec::with_capacity(pairs.len()); - let mut g2_points = Vec::with_capacity(pairs.len()); + let mut g1_points = Vec::with_capacity(capacity); + let mut g2_points = Vec::with_capacity(capacity); for pair in pairs { - let g1 = read_bls_g1_point(&pair.g1)?; - let g2 = read_bls_g2_point(&pair.g2)?; + let g1 = read_bls_g1_point(&pair.g1).map_err(|error| match error { + Error::PointNotOnCurve => Error::BlsG1PointNotOnCurve, + Error::PointNotInSubgroup => Error::BlsG1PointNotInSubgroup, + error => error, + })?; + let g2 = read_bls_g2_point(&pair.g2).map_err(|error| match error { + Error::PointNotOnCurve => Error::BlsG2PointNotOnCurve, + Error::PointNotInSubgroup => Error::BlsG2PointNotInSubgroup, + error => error, + })?; let (g1_x, g1_y) = g1.into_coords(); let (g2_x, g2_y) = g2.into_coords(); @@ -133,6 +175,8 @@ pub fn bls12_381_pairing_check( g2_points.push(AffinePoint::new(g2_x, g2_y)); } - *verified = Bls12_381::pairing_check(&g1_points, &g2_points).is_ok(); - Ok(()) + if g1_points.is_empty() { + return Ok(true); + } + Ok(Bls12_381::pairing_check(&g1_points, &g2_points).is_ok()) } diff --git a/crates/accelerators/src/ops/bn254/mod.rs b/crates/accelerators/src/ops/bn254/mod.rs index 10041166a..a2fcad052 100644 --- a/crates/accelerators/src/ops/bn254/mod.rs +++ b/crates/accelerators/src/ops/bn254/mod.rs @@ -12,7 +12,7 @@ use openvm_ecc_guest::{ use openvm_pairing::{bn254::Bn254, PairingCheck}; use crate::{ - ops::Error, + ops::{Error, StreamError}, types::{ZkvmBn254G1Point, ZkvmBn254PairingPair, ZkvmBn254Scalar}, }; @@ -46,18 +46,30 @@ pub fn bn254_pairing_check( verified: &mut bool, ) -> Result<(), Error> { *verified = false; + let value = + bn254_pairing_check_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) + .map_err(|error| match error { + StreamError::Operation(error) => error, + StreamError::Source(never) => match never {}, + })?; + *verified = value; + Ok(()) +} - if pairs.is_empty() { - *verified = true; - return Ok(()); - } +/// BN254 pairing check over a stream of encoded pairs. +pub fn bn254_pairing_check_iter( + pairs: impl IntoIterator>, +) -> Result> { + let pairs = pairs.into_iter(); + let capacity = pairs.size_hint().0; - let mut g1_points = Vec::with_capacity(pairs.len()); - let mut g2_points = Vec::with_capacity(pairs.len()); + let mut g1_points = Vec::with_capacity(capacity); + let mut g2_points = Vec::with_capacity(capacity); for pair in pairs { - let g1 = read_bn_g1_point(&pair.g1)?; - let g2 = read_bn_g2_point(&pair.g2)?; + let pair = pair.map_err(StreamError::Source)?; + let g1 = read_bn_g1_point(&pair.g1).map_err(StreamError::Operation)?; + let g2 = read_bn_g2_point(&pair.g2).map_err(StreamError::Operation)?; let (g1_x, g1_y) = g1.into_coords(); let (g2_x, g2_y) = g2.into_coords(); @@ -66,6 +78,8 @@ pub fn bn254_pairing_check( g2_points.push(AffinePoint::new(g2_x, g2_y)); } - *verified = Bn254::pairing_check(&g1_points, &g2_points).is_ok(); - Ok(()) + if g1_points.is_empty() { + return Ok(true); + } + Ok(Bn254::pairing_check(&g1_points, &g2_points).is_ok()) } diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 258a55ac0..2072ba6aa 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -12,16 +12,25 @@ mod hash; mod kzg; mod modexp; -pub use blake2::blake2f; +pub use blake2::{blake2f, blake2f_words}; pub use bls12_381::{ - bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g1_msm_iter, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_g2_msm_iter, bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, + bls12_381_pairing_check, bls12_381_pairing_check_iter, }; -pub use bn254::{bn254_g1_add, bn254_g1_mul, bn254_pairing_check}; +pub use bn254::{bn254_g1_add, bn254_g1_mul, bn254_pairing_check, bn254_pairing_check_iter}; pub use ecdsa::{secp256k1_ecrecover, secp256k1_verify, secp256r1_verify}; pub use hash::{keccak256, ripemd160, sha256}; pub use kzg::kzg_point_eval; -pub use modexp::modexp; +pub use modexp::{modexp, modexp_result}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StreamError { + /// The input iterator produced an error. + Source(E), + /// An accelerator operation rejected an input. + Operation(Error), +} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { @@ -31,6 +40,14 @@ pub enum Error { PointNotOnCurve, /// A point is on the curve but not in the prime-order subgroup. PointNotInSubgroup, + /// A BLS12-381 pairing G1 point does not satisfy the curve equation. + BlsG1PointNotOnCurve, + /// A BLS12-381 pairing G1 point is not in the prime-order subgroup. + BlsG1PointNotInSubgroup, + /// A BLS12-381 pairing G2 point does not satisfy the curve equation. + BlsG2PointNotOnCurve, + /// A BLS12-381 pairing G2 point is not in the prime-order subgroup. + BlsG2PointNotInSubgroup, /// The BLAKE2f final-block flag is neither 0 nor 1. InvalidFinalFlag, /// A signature could not be parsed or key recovery failed. diff --git a/crates/accelerators/src/ops/modexp.rs b/crates/accelerators/src/ops/modexp.rs index 5652d7362..88afd49fa 100644 --- a/crates/accelerators/src/ops/modexp.rs +++ b/crates/accelerators/src/ops/modexp.rs @@ -17,21 +17,36 @@ const BN_SCALAR_LEN: usize = 32; pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8], output: &mut [u8]) { assert_eq!(output.len(), modulus.len(), "output must be exactly modulus-sized"); - let result = if is_bn254_fr(modulus) { + output.copy_from_slice(&modexp_result(base, exp, modulus)); +} + +/// Compute `base^exp % modulus`, returning exactly `modulus.len()` bytes. +pub fn modexp_result(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { + let mut result = if is_bn254_fr(modulus) { accelerated_modexp_bn254_fr(base, exp) } else { aurora_engine_modexp::modexp(base, exp, modulus) }; - // The result is numerically reduced, but its byte representation may be - // shorter or longer (leading zeros); right-align it. - if result.len() >= output.len() { - output.copy_from_slice(&result[result.len() - output.len()..]); - } else { - let pad = output.len() - result.len(); - output[..pad].fill(0); - output[pad..].copy_from_slice(&result); + // The result is numerically reduced, but its byte representation may not + // be modulus-sized. Reuse its allocation while right-aligning it. + let output_len = modulus.len(); + match result.len().cmp(&output_len) { + core::cmp::Ordering::Greater => { + let start = result.len() - output_len; + result.copy_within(start.., 0); + result.truncate(output_len); + } + core::cmp::Ordering::Less => { + let value_len = result.len(); + let padding = output_len - value_len; + result.resize(output_len, 0); + result.copy_within(0..value_len, padding); + result[..padding].fill(0); + } + core::cmp::Ordering::Equal => {} } + result } /// Returns true if the modulus (big-endian, possibly with leading zeros) equals BN254 Fr. @@ -60,17 +75,18 @@ fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { mod tests { use super::*; - /// BN254 Fr modulus in big-endian bytes - fn bn254_fr_modulus_be() -> Vec { - bn::Scalar::MODULUS.as_ref().iter().rev().copied().collect() - } + /// EIP-197 BN254 scalar-field modulus, independently specified in big-endian order. + const BN254_FR: [u8; 32] = [ + 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, + 0x5d, 0x28, 0x33, 0xe8, 0x48, 0x79, 0xb9, 0x70, 0x91, 0x43, 0xe1, 0xf5, 0x93, 0xf0, 0x00, + 0x00, 0x01, + ]; /// Helper: run the accelerated path and compare against the aurora /// reference. The accelerated path always returns BN_SCALAR_LEN bytes, /// so the reference output is left-padded to match. fn check(base: &[u8], exp: &[u8]) { - let modulus = bn254_fr_modulus_be(); - let expected = aurora_engine_modexp::modexp(base, exp, &modulus); + let expected = aurora_engine_modexp::modexp(base, exp, &BN254_FR); let actual = accelerated_modexp_bn254_fr(base, exp); let mut expected_padded = vec![0u8; BN_SCALAR_LEN]; let offset = BN_SCALAR_LEN - expected.len(); @@ -81,18 +97,18 @@ mod tests { #[test] fn test_is_bn254_fr() { // Exact modulus - assert!(is_bn254_fr(&bn254_fr_modulus_be())); + assert!(is_bn254_fr(&BN254_FR)); // With leading zeros let mut padded = vec![0u8; 10]; - padded.extend_from_slice(&bn254_fr_modulus_be()); + padded.extend_from_slice(&BN254_FR); assert!(is_bn254_fr(&padded)); // All zeros → false assert!(!is_bn254_fr(&[0u8; 32])); // Wrong modulus (flip last bit) - let mut m = bn254_fr_modulus_be(); + let mut m = BN254_FR; *m.last_mut().unwrap() ^= 1; assert!(!is_bn254_fr(&m)); } @@ -108,9 +124,8 @@ mod tests { check(&[0, 0, 0, 3], &[5]); // leading zeros in base // --- short base, value >= modulus (triggers the reduce fallback) --- - let m = bn254_fr_modulus_be(); - check(&m, &[1]); // Fr mod Fr = 0, so 0^1 = 0 - let mut m_plus_1 = m.clone(); + check(&BN254_FR, &[1]); // Fr mod Fr = 0, so 0^1 = 0 + let mut m_plus_1 = BN254_FR; *m_plus_1.last_mut().unwrap() += 1; check(&m_plus_1, &[2]); // (Fr+1)^2 mod Fr = 1 check(&[0xff; 32], &[1]); // max 256-bit value, >= modulus diff --git a/crates/accelerators/src/types.rs b/crates/accelerators/src/types.rs index 56c2694ef..b76b7da11 100644 --- a/crates/accelerators/src/types.rs +++ b/crates/accelerators/src/types.rs @@ -139,34 +139,12 @@ pub type ZkvmKzgCommitment = ZkvmBytes48; pub type ZkvmKzgProof = ZkvmBytes48; pub type ZkvmKzgFieldElement = ZkvmBytes32; -// Assert 8-byte alignment and sizes. +// Pin the non-trivial aggregate layouts exposed by the canonical C header. const _: () = { - use core::mem::{align_of, size_of}; - - assert!(size_of::() == 4); - assert!(align_of::() == 4); - - assert!(size_of::() == 16); - assert!(size_of::() == 32); - assert!(size_of::() == 48); - assert!(size_of::() == 64); - assert!(size_of::() == 96); - assert!(size_of::() == 128); - assert!(size_of::() == 192); + use core::mem::size_of; + assert!(size_of::() == 192); assert!(size_of::() == 128); assert!(size_of::() == 224); assert!(size_of::() == 288); - - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); - assert!(align_of::() == 8); }; diff --git a/crates/accelerators/tests/conformance/bls12_381.rs b/crates/accelerators/tests/conformance/bls12_381.rs index d1d827d73..87e3aabd3 100644 --- a/crates/accelerators/tests/conformance/bls12_381.rs +++ b/crates/accelerators/tests/conformance/bls12_381.rs @@ -179,7 +179,7 @@ fn bls12_rejects_invalid_points() { 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_eq!(bls12_381_pairing_check(&pairs, &mut verified), Err(Error::BlsG1PointNotOnCurve)); assert!(!verified); } @@ -314,13 +314,17 @@ fn bls12_map_fp2_to_g2_vectors() { /// return the point itself. #[test] fn bls12_map_lands_in_prime_order_subgroup() { - let mapped = ZkvmBls12381G1Point { data: MAP_FP_TO_G1_VECTORS[0].1 }; + let mut mapped = ZkvmBls12381G1Point { data: [0; 96] }; + bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: MAP_FP_TO_G1_VECTORS[0].0 }, &mut mapped) + .unwrap(); let mut output = ZkvmBls12381G1Point { data: [0; 96] }; bls12_381_g1_msm(&[ZkvmBls12381G1MsmPair { point: mapped, scalar: scalar(1) }], &mut output) .expect("mapped G1 point must be in the prime-order subgroup"); assert_eq!(output.data, mapped.data); - let mapped = ZkvmBls12381G2Point { data: MAP_FP2_TO_G2_VECTORS[0].1 }; + let mut mapped = ZkvmBls12381G2Point { data: [0; 192] }; + bls12_381_map_fp2_to_g2(&ZkvmBls12381Fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }, &mut mapped) + .unwrap(); let mut output = ZkvmBls12381G2Point { data: [0; 192] }; bls12_381_g2_msm(&[ZkvmBls12381G2MsmPair { point: mapped, scalar: scalar(1) }], &mut output) .expect("mapped G2 point must be in the prime-order subgroup"); diff --git a/crates/accelerators/tests/conformance/ecdsa.rs b/crates/accelerators/tests/conformance/ecdsa.rs index 3230f324e..e4710250d 100644 --- a/crates/accelerators/tests/conformance/ecdsa.rs +++ b/crates/accelerators/tests/conformance/ecdsa.rs @@ -4,7 +4,7 @@ use hex_literal::hex; use openvm_accelerators::{ - ffi::zkvm_secp256r1_verify, + ffi::{zkvm_secp256k1_ecrecover, zkvm_secp256k1_verify, zkvm_secp256r1_verify}, ops::{keccak256, secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, Error}, types::{ ZkvmKeccak256Hash, ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, @@ -72,10 +72,10 @@ fn zkvm_secp256r1_verify_smoke() { assert_eq!(status, ZkvmStatus::Ok); assert!(verified); - // Malformed inputs map to the failure status. + // Malformed cryptographic inputs are a completed verification with a false result. let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZkvmStatus::Ok); assert!(!verified); } @@ -156,3 +156,22 @@ fn secp256k1_verify_roundtrip() { assert_eq!(result, Err(Error::PointNotOnCurve)); assert!(!verified); } + +#[test] +fn zkvm_secp256k1_recover_and_verify() { + let mut pubkey = core::mem::MaybeUninit::::uninit(); + let status = unsafe { zkvm_secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, pubkey.as_mut_ptr()) }; + assert_eq!(status, ZkvmStatus::Ok); + let pubkey = unsafe { pubkey.assume_init() }; + + let mut verified = core::mem::MaybeUninit::::uninit(); + let status = unsafe { zkvm_secp256k1_verify(&K1_MSG, &K1_SIG, &pubkey, verified.as_mut_ptr()) }; + assert_eq!(status, ZkvmStatus::Ok); + let mut verified = unsafe { verified.assume_init() }; + assert!(verified); + + let bad_pubkey = ZkvmSecp256k1Pubkey { data: [0xff; 64] }; + let status = unsafe { zkvm_secp256k1_verify(&K1_MSG, &K1_SIG, &bad_pubkey, &mut verified) }; + assert_eq!(status, ZkvmStatus::Ok); + assert!(!verified); +} diff --git a/crates/accelerators/tests/conformance/kzg.rs b/crates/accelerators/tests/conformance/kzg.rs index 74dba8e1f..0c425139a 100644 --- a/crates/accelerators/tests/conformance/kzg.rs +++ b/crates/accelerators/tests/conformance/kzg.rs @@ -70,11 +70,11 @@ fn zkvm_kzg_point_eval_smoke() { assert_eq!(status, ZkvmStatus::Ok); assert!(verified); - // Malformed inputs map to the failure status. + // Malformed cryptographic inputs are a completed verification with a false result. let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; garbage.data[0] = 0x01; let status = unsafe { zkvm_kzg_point_eval(&garbage, &z, &y, &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZkvmStatus::Ok); assert!(!verified); } diff --git a/crates/accelerators/tests/conformance/modexp.rs b/crates/accelerators/tests/conformance/modexp.rs index 760015463..d72f12ce0 100644 --- a/crates/accelerators/tests/conformance/modexp.rs +++ b/crates/accelerators/tests/conformance/modexp.rs @@ -27,17 +27,21 @@ fn modexp_small() { fn modexp_matches_reference() { // The BN254-Fr accelerated path, compared right-aligned against the // aurora reference. - let mut output = [0u8; 32]; + let mut output = [0xa5; 32]; modexp(&[0xab; 32], &[0x07], &BN254_FR, &mut output); let reference = aurora_engine_modexp::modexp(&[0xab; 32], &[0x07], &BN254_FR); - assert_eq!(output[32 - reference.len()..], reference[..]); + let mut expected = [0; 32]; + expected[32 - reference.len()..].copy_from_slice(&reference); + assert_eq!(output, expected); // The generic path with a non-special modulus. let modulus = [0xef; 24]; - let mut output = [0u8; 24]; + let mut output = [0xa5; 24]; modexp(&[0x12; 40], &[0x34; 3], &modulus, &mut output); let reference = aurora_engine_modexp::modexp(&[0x12; 40], &[0x34; 3], &modulus); - assert_eq!(output[24 - reference.len()..], reference[..]); + let mut expected = [0; 24]; + expected[24 - reference.len()..].copy_from_slice(&reference); + assert_eq!(output, expected); } #[test] diff --git a/crates/revm-crypto/Cargo.toml b/crates/revm-crypto/Cargo.toml index 226cc5ef1..fea11ff40 100644 --- a/crates/revm-crypto/Cargo.toml +++ b/crates/revm-crypto/Cargo.toml @@ -12,33 +12,19 @@ workspace = true [dependencies] # revm revm.workspace = true +openvm-accelerators.workspace = true # alloy alloy-primitives = { workspace = true, features = ["map-foldhash"] } alloy-consensus = { workspace = true, features = ["crypto-backend"] } -# OpenVM dependencies for optimized crypto (only needed when running in zkvm) -openvm-curve-utils = { workspace = true, features = ["bn254", "bls12_381"] } -openvm-ecc-guest = { workspace = true } -openvm-sha2 = { workspace = true } -openvm-pairing = { workspace = true, features = ["bn254", "bls12_381"] } -openvm-k256 = { workspace = true } -openvm-p256 = { workspace = true } -openvm-kzg = { workspace = true, features = ["use-intrinsics"] } -openvm-keccak256 = { workspace = true } -aurora-engine-modexp = { version = "1.2.0", default-features = false } - [target.'cfg(all(target_arch = "riscv64", any(target_os = "none", target_os = "openvm")))'.dependencies] revm-primitives = { workspace = true, features = ["hashbrown"] } alloy-primitives = { workspace = true, features = ["native-keccak"] } -[target.'cfg(not(any(target_os = "none", target_os = "openvm")))'.dependencies] -openvm-sha2 = { workspace = true, features = ["import_sha2"] } -openvm-keccak256 = { workspace = true, features = ["tiny_keccak"] } - [features] default = [] -std = [] +std = ["openvm-accelerators/std"] [package.metadata.cargo-shear] ignored = ["revm-primitives"] diff --git a/crates/revm-crypto/src/lib.rs b/crates/revm-crypto/src/lib.rs index 090e3e623..28abb25b9 100644 --- a/crates/revm-crypto/src/lib.rs +++ b/crates/revm-crypto/src/lib.rs @@ -1,30 +1,29 @@ -//! OpenVM Crypto Implementation for REVM +//! OpenVM crypto providers for REVM and Alloy. //! -//! This module provides OpenVM-optimized implementations of cryptographic operations -//! for both transaction validation (via Alloy crypto provider) and precompile execution. +//! Cryptographic operations live in `openvm-accelerators`; this crate only +//! adapts their byte-oriented API to REVM and Alloy's provider traits. #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; + use alloy_consensus::crypto::{ backend::{install_default_provider, CryptoProvider}, RecoveryError, }; use alloy_primitives::Address; -use openvm_ecc_guest::{ - algebra::IntMod, - weierstrass::{IntrinsicCurve, WeierstrassPoint}, - AffinePoint, Group, -}; -use openvm_k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; -use openvm_keccak256::keccak256; -use openvm_kzg::{Bytes32, Bytes48, KzgProof}; -use openvm_pairing::{ - bls12_381::{self as bls, Bls12_381}, - bn254::{self as bn, Bn254}, - PairingCheck, +use openvm_accelerators::{ + ops::{self, Error, StreamError}, + types::{ + ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, + ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBn254G1Point, + ZkvmBn254G2Point, ZkvmBn254PairingPair, ZkvmBn254Scalar, ZkvmBytes32, ZkvmKeccak256Hash, + ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmRipemd160Hash, ZkvmSecp256k1Hash, + ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, + ZkvmSecp256r1Signature, ZkvmSha256Hash, + }, }; use revm::{ install_crypto, @@ -35,23 +34,11 @@ use revm::{ }, bls12_381_const::{ FP_LENGTH as BLS_FP_LEN, G1_LENGTH as BLS_G1_LEN, G2_LENGTH as BLS_G2_LEN, - SCALAR_LENGTH as BLS_SCALAR_LEN, }, Crypto, PrecompileHalt, }, }; -use openvm_curve_utils::SubgroupCheck; - -// BN254 constants -const BN_FQ_LEN: usize = 32; -const BN_G1_LEN: usize = 64; -const BN_G2_LEN: usize = 128; -/// BN_SCALAR_LEN specifies the number of bytes needed to represent an Fr element. -/// This is an element in the scalar field of BN254. -const BN_SCALAR_LEN: usize = 32; - -/// OpenVM k256 backend for Alloy crypto operations (transaction validation) #[derive(Debug, Default)] struct OpenVmK256Provider; @@ -61,34 +48,14 @@ impl CryptoProvider for OpenVmK256Provider { sig: &[u8; 65], msg: &[u8; 32], ) -> Result { - // Extract components: sig[0..32]=r, sig[32..64]=s, sig[64]=recovery_id - // Parse signature using OpenVM k256 - let mut signature = Signature::from_slice(&sig[..64]).map_err(|_| RecoveryError::new())?; - - // Normalize signature if needed - let mut recid = sig[64]; - if let Some(sig_normalized) = signature.normalize_s() { - signature = sig_normalized; - recid ^= 1; - } - - // Create recovery ID - let recovery_id = RecoveryId::from_byte(recid).ok_or(RecoveryError::new())?; - - // Recover public key using OpenVM - let recovered_key = - VerifyingKey::recover_from_prehash_noverify(msg, &signature.to_bytes(), recovery_id) - .map_err(|_| RecoveryError::new())?; - - // Hash the uncompressed SEC1 key without the 0x04 prefix. - let public_key = recovered_key.to_encoded_point(false); - let encoded_pubkey = &public_key.as_bytes()[1..65]; - - // Hash to get Ethereum address - let pubkey_hash = keccak256(encoded_pubkey); - let address_bytes = &pubkey_hash[12..32]; // Last 20 bytes + let recovery_id = sig[64]; + let msg = ZkvmSecp256k1Hash { data: *msg }; + let sig = ZkvmSecp256k1Signature { data: sig[..64].try_into().unwrap() }; + let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; + ops::secp256k1_ecrecover(&msg, &sig, recovery_id, &mut pubkey) + .map_err(|_| RecoveryError::new())?; - Ok(Address::from_slice(address_bytes)) + Ok(address_from_pubkey(&pubkey.data)) } fn verify_and_compute_signer_unchecked( @@ -97,508 +64,305 @@ impl CryptoProvider for OpenVmK256Provider { sig: &[u8; 64], msg: &[u8; 32], ) -> Result { - let vk = VerifyingKey::from_sec1_bytes(pubkey).map_err(|_| RecoveryError::new())?; - - let mut signature = Signature::from_slice(sig).map_err(|_| RecoveryError::new())?; - if let Some(sig_normalized) = signature.normalize_s() { - signature = sig_normalized; + if pubkey[0] != 0x04 { + return Err(RecoveryError::new()); } - vk.verify_prehash(msg.as_ref(), &signature).map_err(|_| RecoveryError::new())?; + let msg = ZkvmSecp256k1Hash { data: *msg }; + let sig = ZkvmSecp256k1Signature { data: *sig }; + let pubkey = ZkvmSecp256k1Pubkey { data: pubkey[1..].try_into().unwrap() }; + let mut verified = false; + ops::secp256k1_verify(&msg, &sig, &pubkey, &mut verified) + .map_err(|_| RecoveryError::new())?; + if !verified { + return Err(RecoveryError::new()); + } - // Compute address directly from the provided pubkey bytes (skip 0x04 prefix) - let pubkey_hash = keccak256(&pubkey[1..65]); - Ok(Address::from_slice(&pubkey_hash[12..32])) + Ok(address_from_pubkey(&pubkey.data)) } } -/// OpenVM custom crypto implementation for faster precompiles +// Kept separate so both Alloy provider methods use exactly the standard-interface hash path. +fn address_from_pubkey(pubkey: &[u8; 64]) -> Address { + let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; + ops::keccak256(pubkey, &mut hash); + Address::from_slice(&hash.data[12..]) +} + #[derive(Debug, Default)] struct OpenVmCrypto; impl Crypto for OpenVmCrypto { - /// Custom SHA-256 implementation with openvm optimization fn sha256(&self, input: &[u8]) -> [u8; 32] { - #[cfg(not(openvm_intrinsics))] - use openvm_sha2::Digest; - openvm_sha2::Sha256::digest(input).into() + let mut output = ZkvmSha256Hash { data: [0; 32] }; + ops::sha256(input, &mut output); + output.data } - /// Custom BN254 G1 addition with openvm optimization - fn bn254_g1_add(&self, p1_bytes: &[u8], p2_bytes: &[u8]) -> Result<[u8; 64], PrecompileHalt> { - let p1 = read_bn_g1_point(p1_bytes)?; - let p2 = read_bn_g1_point(p2_bytes)?; - let result = p1 + p2; - Ok(encode_bn_g1_point(result)) + fn ripemd160(&self, input: &[u8]) -> [u8; 32] { + let mut output = ZkvmRipemd160Hash { data: [0; 32] }; + ops::ripemd160(input, &mut output); + output.data } - /// Custom BN254 G1 scalar multiplication with openvm optimization - fn bn254_g1_mul( - &self, - point_bytes: &[u8], - scalar_bytes: &[u8], - ) -> Result<[u8; 64], PrecompileHalt> { - let p = read_bn_g1_point(point_bytes)?; - let s = read_bn_scalar(scalar_bytes); - let result = Bn254::msm(&[s], &[p]); - Ok(encode_bn_g1_point(result)) + fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> { + let p1 = + ZkvmBn254G1Point { data: p1.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)? }; + let p2 = + ZkvmBn254G1Point { data: p2.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)? }; + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + ops::bn254_g1_add(&p1, &p2, &mut output).map_err(map_bn_error)?; + Ok(output.data) + } + + fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> { + let point = ZkvmBn254G1Point { + data: point.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let scalar = ZkvmBn254Scalar { + data: scalar.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let mut output = ZkvmBn254G1Point { data: [0; 64] }; + ops::bn254_g1_mul(&point, &scalar, &mut output).map_err(map_bn_error)?; + Ok(output.data) } - /// Custom BN254 pairing check with openvm optimization fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result { - if pairs.is_empty() { - return Ok(true); - } - let mut g1_points = Vec::with_capacity(pairs.len()); - let mut g2_points = Vec::with_capacity(pairs.len()); + let pairs = pairs.iter().map(|(g1, g2)| { + Ok(ZkvmBn254PairingPair { + g1: ZkvmBn254G1Point { + data: (*g1).try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }, + g2: ZkvmBn254G2Point { + data: (*g2).try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }, + }) + }); + ops::bn254_pairing_check_iter(pairs).map_err(|error| map_stream_error(error, map_bn_error)) + } - for (g1_bytes, g2_bytes) in pairs { - let g1 = read_bn_g1_point(g1_bytes)?; - let g2 = read_bn_g2_point(g2_bytes)?; + fn secp256k1_ecrecover( + &self, + sig: &[u8; 64], + recid: u8, + msg: &[u8; 32], + ) -> Result<[u8; 32], PrecompileHalt> { + let msg = ZkvmSecp256k1Hash { data: *msg }; + let sig = ZkvmSecp256k1Signature { data: *sig }; + let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; + ops::secp256k1_ecrecover(&msg, &sig, recid, &mut pubkey) + .map_err(|_| PrecompileHalt::Secp256k1RecoverFailed)?; - let (g1_x, g1_y) = g1.into_coords(); - let g1 = AffinePoint::new(g1_x, g1_y); + let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; + ops::keccak256(&pubkey.data, &mut hash); + hash.data[..12].fill(0); + Ok(hash.data) + } - let (g2_x, g2_y) = g2.into_coords(); - let g2 = AffinePoint::new(g2_x, g2_y); + fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileHalt> { + Ok(ops::modexp_result(base, exp, modulus)) + } - g1_points.push(g1); - g2_points.push(g2); - } + fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { + ops::blake2f_words(rounds, h, m, t, f); + } - let pairing_result = Bn254::pairing_check(&g1_points, &g2_points).is_ok(); - Ok(pairing_result) + fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool { + let msg = ZkvmSecp256r1Hash { data: *msg }; + let sig = ZkvmSecp256r1Signature { data: *sig }; + let pubkey = ZkvmSecp256r1Pubkey { data: *pk }; + let mut verified = false; + ops::secp256r1_verify(&msg, &sig, &pubkey, &mut verified).is_ok() && verified + } + + fn verify_kzg_proof( + &self, + z: &[u8; 32], + y: &[u8; 32], + commitment: &[u8; 48], + proof: &[u8; 48], + ) -> Result<(), PrecompileHalt> { + let commitment = ZkvmKzgCommitment { data: *commitment }; + let z = ZkvmKzgFieldElement { data: *z }; + let y = ZkvmKzgFieldElement { data: *y }; + let proof = ZkvmKzgProof { data: *proof }; + let mut verified = false; + ops::kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) + .map_err(|_| PrecompileHalt::BlobVerifyKzgProofFailed)?; + if verified { + Ok(()) + } else { + Err(PrecompileHalt::BlobVerifyKzgProofFailed) + } } - /// Custom BLS12-381 G1 addition with openvm optimization fn bls12_381_g1_add( &self, a: BlsG1Point, b: BlsG1Point, ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - // EIP-2537 G1ADD validates on-curve only, not subgroup membership. - let p1 = read_bls_g1_point_no_subgroup_check(&a)?; - let p2 = read_bls_g1_point_no_subgroup_check(&b)?; - let sum = p1 + p2; - Ok(encode_bls_g1_point(&sum)) + let a = bls_g1(a); + let b = bls_g1(b); + let mut output = ZkvmBls12381G1Point { data: [0; BLS_G1_LEN] }; + ops::bls12_381_g1_add(&a, &b, &mut output).map_err(map_bls_g1_error)?; + Ok(output.data) } - /// Custom BLS12-381 G1 MSM with openvm optimization fn bls12_381_g1_msm( &self, pairs: &mut dyn Iterator>, ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - let mut scalars = Vec::new(); - let mut points = Vec::new(); - - for pair in pairs { - let (point_bytes, scalar_bytes) = pair?; - points.push(read_bls_g1_point(&point_bytes)?); - scalars.push(read_bls_scalar(&scalar_bytes)); - } - - if points.is_empty() { - return Ok([0u8; BLS_G1_LEN]); - } - - let result = Bls12_381::msm(&scalars, &points); - Ok(encode_bls_g1_point(&result)) + let pairs = pairs.map(|pair| { + let (point, scalar) = pair?; + Ok(ZkvmBls12381G1MsmPair { point: bls_g1(point), scalar: ZkvmBytes32 { data: scalar } }) + }); + ops::bls12_381_g1_msm_iter(pairs) + .map(|output| output.data) + .map_err(|error| map_stream_error(error, map_bls_g1_error)) } - /// Custom BLS12-381 G2 addition with openvm optimization fn bls12_381_g2_add( &self, a: BlsG2Point, b: BlsG2Point, ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - // EIP-2537 G2ADD validates on-curve only, not subgroup membership. - let p1 = read_bls_g2_point_no_subgroup_check(&a)?; - let p2 = read_bls_g2_point_no_subgroup_check(&b)?; - let sum = p1 + p2; - Ok(encode_bls_g2_point(&sum)) + let a = bls_g2(a); + let b = bls_g2(b); + let mut output = ZkvmBls12381G2Point { data: [0; BLS_G2_LEN] }; + ops::bls12_381_g2_add(&a, &b, &mut output).map_err(map_bls_g2_error)?; + Ok(output.data) } - /// Custom BLS12-381 G2 MSM with openvm optimization fn bls12_381_g2_msm( &self, pairs: &mut dyn Iterator>, ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - let mut scalars = Vec::new(); - let mut points = Vec::new(); - - for pair in pairs { - let (point_bytes, scalar_bytes) = pair?; - points.push(read_bls_g2_point(&point_bytes)?); - scalars.push(read_bls_scalar(&scalar_bytes)); - } - - if points.is_empty() { - return Ok([0u8; BLS_G2_LEN]); - } - - // directly using openvm_ecc_guest::msm here - let result = openvm_ecc_guest::msm(&scalars, &points); - Ok(encode_bls_g2_point(&result)) + let pairs = pairs.map(|pair| { + let (point, scalar) = pair?; + Ok(ZkvmBls12381G2MsmPair { point: bls_g2(point), scalar: ZkvmBytes32 { data: scalar } }) + }); + ops::bls12_381_g2_msm_iter(pairs) + .map(|output| output.data) + .map_err(|error| map_stream_error(error, map_bls_g2_error)) } - /// Custom BLS12-381 pairing check with openvm optimization fn bls12_381_pairing_check( &self, pairs: &[(BlsG1Point, BlsG2Point)], ) -> Result { - if pairs.is_empty() { - return Ok(true); - } - - let mut g1_points = Vec::with_capacity(pairs.len()); - let mut g2_points = Vec::with_capacity(pairs.len()); - - for (g1_bytes, g2_bytes) in pairs { - let g1 = read_bls_g1_point(g1_bytes)?; - let g2 = read_bls_g2_point(g2_bytes)?; - - 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)); - } - - let pairing_result = Bls12_381::pairing_check(&g1_points, &g2_points).is_ok(); - Ok(pairing_result) + let pairs = pairs + .iter() + .copied() + .map(|(g1, g2)| ZkvmBls12381PairingPair { g1: bls_g1(g1), g2: bls_g2(g2) }); + ops::bls12_381_pairing_check_iter(pairs).map_err(map_bls_pairing_error) } - /// Custom secp256k1 ECDSA signature recovery with openvm optimization - fn secp256k1_ecrecover( + fn bls12_381_fp_to_g1( &self, - sig_bytes: &[u8; 64], - mut recid: u8, - msg_hash: &[u8; 32], - ) -> Result<[u8; 32], PrecompileHalt> { - let mut sig = Signature::from_slice(sig_bytes) - .map_err(|_| PrecompileHalt::other("Invalid signature format"))?; - - if let Some(sig_normalized) = sig.normalize_s() { - sig = sig_normalized; - recid ^= 1; - } - - let recovery_id = RecoveryId::from_byte(recid) - .ok_or_else(|| PrecompileHalt::other("Invalid recovery ID"))?; - - let recovered_key = - VerifyingKey::recover_from_prehash_noverify(msg_hash, &sig.to_bytes(), recovery_id) - .map_err(|_| PrecompileHalt::other("Key recovery failed"))?; - - let public_key = recovered_key.to_encoded_point(false); - let encoded_pubkey = &public_key.as_bytes()[1..65]; - - let pubkey_hash = keccak256(encoded_pubkey); - let mut address = [0u8; 32]; - address[12..].copy_from_slice(&pubkey_hash[12..]); - - Ok(address) - } - - /// Custom secp256r1 signature verification with openvm optimization - fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool { - use openvm_p256::{ - ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}, - EncodedPoint, - }; - - // Can fail only if the input is not exact length. - let Ok(signature) = Signature::from_slice(sig) else { - return false; - }; - // Decode the public key bytes (x,y coordinates) using EncodedPoint - let encoded_point = EncodedPoint::from_untagged_bytes(&(*pk).into()); - // Create VerifyingKey from the encoded point - let Ok(public_key) = VerifyingKey::from_encoded_point(&encoded_point) else { - return false; - }; - - public_key.verify_prehash(msg, &signature).is_ok() + fp: &[u8; BLS_FP_LEN], + ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { + let fp = ZkvmBls12381Fp { data: *fp }; + let mut output = ZkvmBls12381G1Point { data: [0; BLS_G1_LEN] }; + ops::bls12_381_map_fp_to_g1(&fp, &mut output).map_err(map_bls_field_error)?; + Ok(output.data) } - /// Custom KZG point evaluation with configurable backends - fn verify_kzg_proof( + fn bls12_381_fp2_to_g2( &self, - z: &[u8; 32], - y: &[u8; 32], - commitment: &[u8; 48], - proof: &[u8; 48], - ) -> Result<(), PrecompileHalt> { - let env = openvm_kzg::EnvKzgSettings::default(); - let kzg_settings = env.get(); - - let commitment_bytes = Bytes48::from_slice(commitment) - .map_err(|_| PrecompileHalt::other("invalid commitment bytes"))?; - let z_bytes = - Bytes32::from_slice(z).map_err(|_| PrecompileHalt::other("invalid z bytes"))?; - let y_bytes = - Bytes32::from_slice(y).map_err(|_| PrecompileHalt::other("invalid y bytes"))?; - let proof_bytes = - Bytes48::from_slice(proof).map_err(|_| PrecompileHalt::other("invalid proof bytes"))?; - - let valid = KzgProof::verify_kzg_proof( - &commitment_bytes, - &z_bytes, - &y_bytes, - &proof_bytes, - kzg_settings, - ) - .map_err(|_| PrecompileHalt::other("openvm kzg proof verification failed"))?; - if valid { - Ok(()) - } else { - Err(PrecompileHalt::BlobVerifyKzgProofFailed) - } - } - - /// Custom modular exponentiation with BN254 Fr acceleration - fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileHalt> { - if is_bn254_fr(modulus) { - return Ok(accelerated_modexp_bn254_fr(base, exp)); - } - Ok(aurora_engine_modexp::modexp(base, exp, modulus)) + fp2: ([u8; BLS_FP_LEN], [u8; BLS_FP_LEN]), + ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { + let mut data = [0; BLS_FP_LEN * 2]; + data[..BLS_FP_LEN].copy_from_slice(&fp2.0); + data[BLS_FP_LEN..].copy_from_slice(&fp2.1); + let fp2 = ZkvmBls12381Fp2 { data }; + let mut output = ZkvmBls12381G2Point { data: [0; BLS_G2_LEN] }; + ops::bls12_381_map_fp2_to_g2(&fp2, &mut output).map_err(map_bls_field_error)?; + Ok(output.data) } } -/// Returns true if the modulus (big-endian, possibly with leading zeros) equals BN254 Fr. -fn is_bn254_fr(modulus: &[u8]) -> bool { - // Strip leading zeros - let stripped = match modulus.iter().position(|&b| b != 0) { - Some(i) => &modulus[i..], - None => return false, // all zeros - }; - // bn::Scalar::MODULUS is little-endian; compare against reversed input - stripped.len() == BN_SCALAR_LEN && stripped.iter().rev().eq(bn::Scalar::MODULUS.as_ref().iter()) +fn bls_g1((x, y): BlsG1Point) -> ZkvmBls12381G1Point { + let mut data = [0; BLS_G1_LEN]; + data[..BLS_FP_LEN].copy_from_slice(&x); + data[BLS_FP_LEN..].copy_from_slice(&y); + ZkvmBls12381G1Point { data } } -/// Accelerated modexp for BN254 Fr using field arithmetic intrinsics. -fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { - use openvm_ecc_guest::algebra::{ExpBytes, Reduce}; - - // OpenVM's field reduction requires inputs to be aligned to the field byte size. - let padded_len = base.len().next_multiple_of(BN_SCALAR_LEN).max(BN_SCALAR_LEN); - let mut padded = vec![0u8; padded_len]; - padded[padded_len - base.len()..].copy_from_slice(base); - let base_fr = bn::Scalar::reduce_be_bytes(&padded); - - base_fr.exp_bytes(true, exp).to_be_bytes().as_ref().to_vec() -} - -/// Install OpenVM crypto implementations globally -pub fn install_openvm_crypto() -> Result> { - // Install OpenVM k256 provider for Alloy (transaction validation) - install_default_provider(Arc::new(OpenVmK256Provider))?; - - // Install OpenVM crypto for REVM precompiles - let installed = install_crypto(OpenVmCrypto); - - Ok(installed) -} - -// Helper functions for BN254 operations - -#[inline] -fn read_bn_fq(input: &[u8]) -> Result { - if input.len() < BN_FQ_LEN { - Err(PrecompileHalt::Bn254FieldPointNotAMember) - } else { - bn::Fp::from_be_bytes(&input[..BN_FQ_LEN]).ok_or(PrecompileHalt::Bn254FieldPointNotAMember) +fn bls_g2((x0, x1, y0, y1): BlsG2Point) -> ZkvmBls12381G2Point { + let mut data = [0; BLS_G2_LEN]; + for (output, coordinate) in data.chunks_exact_mut(BLS_FP_LEN).zip([x0, x1, y0, y1]) { + output.copy_from_slice(&coordinate); } + ZkvmBls12381G2Point { data } } -#[inline] -fn read_bn_fq2(input: &[u8]) -> Result { - let y = read_bn_fq(&input[..BN_FQ_LEN])?; - let x = read_bn_fq(&input[BN_FQ_LEN..BN_FQ_LEN * 2])?; - Ok(bn::Fp2::new(x, y)) -} - -#[inline] -fn read_bn_g1_point(input: &[u8]) -> Result { - if input.len() != BN_G1_LEN { - return Err(PrecompileHalt::Bn254PairLength); - } - let px = read_bn_fq(&input[0..BN_FQ_LEN])?; - let py = read_bn_fq(&input[BN_FQ_LEN..BN_G1_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(PrecompileHalt::Bn254AffineGFailedToCreate)?; - if point.is_in_correct_subgroup() { - Ok(point) - } else { - Err(PrecompileHalt::Bn254AffineGFailedToCreate) +fn map_stream_error( + error: StreamError, + map_operation: fn(Error) -> PrecompileHalt, +) -> PrecompileHalt { + match error { + StreamError::Source(error) => error, + StreamError::Operation(error) => map_operation(error), } } -#[inline] -fn read_bn_g2_point(input: &[u8]) -> Result { - if input.len() != BN_G2_LEN { - return Err(PrecompileHalt::Bn254PairLength); - } - let c0 = read_bn_fq2(&input[0..BN_G1_LEN])?; - let c1 = read_bn_fq2(&input[BN_G1_LEN..BN_G2_LEN])?; - // SAFETY: `read_bn_fq2` produces canonical Fp2 elements; `from_xy` itself checks the curve - // equation and returns `None` if `(c0, c1)` is not on the twist. - let point = unsafe { bn::G2Affine::from_xy(c0, c1) } - .ok_or(PrecompileHalt::Bn254AffineGFailedToCreate)?; - if point.is_in_correct_subgroup() { - Ok(point) - } else { - Err(PrecompileHalt::Bn254AffineGFailedToCreate) +fn map_bn_error(error: Error) -> PrecompileHalt { + match error { + Error::FieldElementInvalid => PrecompileHalt::Bn254FieldPointNotAMember, + Error::PointNotOnCurve | Error::PointNotInSubgroup => { + PrecompileHalt::Bn254AffineGFailedToCreate + } + _ => PrecompileHalt::other("unexpected BN254 accelerator error"), } } -#[inline] -fn encode_bn_g1_point(point: bn::G1Affine) -> [u8; BN_G1_LEN] { - let mut output = [0u8; BN_G1_LEN]; - - 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[i] = x_bytes[BN_FQ_LEN - 1 - i]; - output[i + BN_FQ_LEN] = y_bytes[BN_FQ_LEN - 1 - i]; +fn map_bls_g1_error(error: Error) -> PrecompileHalt { + match error { + Error::PointNotInSubgroup => PrecompileHalt::Bls12381G1NotInSubgroup, + Error::PointNotOnCurve => PrecompileHalt::Bls12381G1NotOnCurve, + Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, + _ => PrecompileHalt::other("unexpected BLS12-381 G1 accelerator error"), } - output } -/// Reads a scalar from the input slice -/// -/// Note: The scalar does not need to be canonical. -/// -/// # Panics -/// -/// If `input.len()` is not equal to [`BN_SCALAR_LEN`]. -#[inline] -fn read_bn_scalar(input: &[u8]) -> bn::Scalar { - assert_eq!( - input.len(), - BN_SCALAR_LEN, - "unexpected scalar length. got {}, expected {BN_SCALAR_LEN}", - input.len() - ); - bn::Scalar::from_be_bytes_unchecked(input) -} - -// Helper functions for BLS12-381 operations - -#[inline] -fn read_bls_fp(input: &[u8]) -> Result { - if input.len() != BLS_FP_LEN { - return Err(PrecompileHalt::other("invalid BLS12-381 fp length")); +fn map_bls_g2_error(error: Error) -> PrecompileHalt { + match error { + Error::PointNotInSubgroup => PrecompileHalt::Bls12381G2NotInSubgroup, + Error::PointNotOnCurve => PrecompileHalt::Bls12381G2NotOnCurve, + Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, + _ => PrecompileHalt::other("unexpected BLS12-381 G2 accelerator error"), } - bls::Fp::from_be_bytes(input) - .ok_or_else(|| PrecompileHalt::other("element not in BLS12-381 base field")) -} - -#[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] -fn read_bls_g1_point_no_subgroup_check( - point: &BlsG1Point, -) -> Result { - let px = read_bls_fp(&point.0)?; - let py = read_bls_fp(&point.1)?; - // 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(PrecompileHalt::Bls12381G1NotOnCurve) -} - -#[inline] -fn read_bls_g1_point(point: &BlsG1Point) -> Result { - let point = read_bls_g1_point_no_subgroup_check(point)?; - if point.is_in_correct_subgroup() { - Ok(point) - } else { - Err(PrecompileHalt::Bls12381G1NotInSubgroup) +fn map_bls_pairing_error(error: Error) -> PrecompileHalt { + match error { + Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, + Error::BlsG1PointNotOnCurve => PrecompileHalt::Bls12381G1NotOnCurve, + Error::BlsG1PointNotInSubgroup => PrecompileHalt::Bls12381G1NotInSubgroup, + Error::BlsG2PointNotOnCurve => PrecompileHalt::Bls12381G2NotOnCurve, + Error::BlsG2PointNotInSubgroup => PrecompileHalt::Bls12381G2NotInSubgroup, + _ => PrecompileHalt::other("unexpected BLS12-381 pairing accelerator error"), } } -#[inline] -fn read_bls_g2_point_no_subgroup_check( - point: &BlsG2Point, -) -> Result { - let x = read_bls_fp2(&point.0, &point.1)?; - let y = read_bls_fp2(&point.2, &point.3)?; - // 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(PrecompileHalt::Bls12381G2NotOnCurve) -} - -#[inline] -fn read_bls_g2_point(point: &BlsG2Point) -> Result { - let point = read_bls_g2_point_no_subgroup_check(point)?; - if point.is_in_correct_subgroup() { - Ok(point) - } else { - Err(PrecompileHalt::Bls12381G2NotInSubgroup) +fn map_bls_field_error(error: Error) -> PrecompileHalt { + match error { + Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, + _ => PrecompileHalt::other("unexpected BLS12-381 map accelerator error"), } } -#[inline] -fn read_bls_scalar(input: &[u8]) -> bls::Scalar { - assert_eq!( - input.len(), - BLS_SCALAR_LEN, - "unexpected scalar length. got {}, expected {BLS_SCALAR_LEN}", - input.len() - ); - bls::Scalar::from_be_bytes_unchecked(input) -} - -#[inline] -fn encode_bls_g1_point(point: &bls::G1Affine) -> [u8; BLS_G1_LEN] { - if point.is_identity() { - return [0u8; BLS_G1_LEN]; - } - - let mut output = [0u8; BLS_G1_LEN]; - 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[i] = x_bytes[BLS_FP_LEN - 1 - i]; - output[i + BLS_FP_LEN] = y_bytes[BLS_FP_LEN - 1 - i]; - } - output -} - -#[inline] -fn encode_bls_g2_point(point: &bls::G2Affine) -> [u8; BLS_G2_LEN] { - if point.is_identity() { - return [0u8; BLS_G2_LEN]; - } - - let mut output = [0u8; BLS_G2_LEN]; - 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[i] = x_c0[BLS_FP_LEN - 1 - i]; - output[i + BLS_FP_LEN] = x_c1[BLS_FP_LEN - 1 - i]; - output[i + (2 * BLS_FP_LEN)] = y_c0[BLS_FP_LEN - 1 - i]; - output[i + (3 * BLS_FP_LEN)] = y_c1[BLS_FP_LEN - 1 - i]; - } - output +/// Install the OpenVM implementations globally. +pub fn install_openvm_crypto() -> Result> { + install_default_provider(Arc::new(OpenVmK256Provider))?; + Ok(install_crypto(OpenVmCrypto)) } #[cfg(test)] mod tests { use super::*; + use revm::precompile::DefaultCrypto; - /// Runs `secp256r1_verify_signature` on a 160-byte P256VERIFY input (msg || sig || pk). fn p256_verify_input(input_hex: &str) -> bool { let input = alloy_primitives::hex::decode(input_hex).unwrap(); assert_eq!(input.len(), 160); @@ -609,125 +373,112 @@ mod tests { ) } - // Test vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors, - // as used by revm-precompile's secp256r1 tests. + // Vectors from daimo-eth/p256-verifier, also used by revm-precompile. #[test] - fn test_secp256r1_verify_signature() { - // valid signature + fn secp256r1_verify_signature() { assert!(p256_verify_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e")); - assert!(p256_verify_input("3fec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817ccf50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de6302b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d3228d3b940258c75fe2a413cb70baa21dc2e352fc5")); - // wrong message assert!(!p256_verify_input("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e")); - // signature values out of range - assert!(!p256_verify_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e")); - // public key not on the curve - assert!(!p256_verify_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); } - /// BN254 Fr modulus in big-endian bytes - fn bn254_fr_modulus_be() -> Vec { - let m = bn::Scalar::MODULUS; - m.as_ref().iter().rev().copied().collect() - } + #[test] + fn modexp_dispatch_and_padding() { + let modulus = alloy_primitives::hex::decode( + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + ) + .unwrap(); + let accelerated = OpenVmCrypto.modexp(&[3], &[5], &modulus).unwrap(); + assert_eq!(accelerated.len(), 32); + assert!(accelerated[..31].iter().all(|byte| *byte == 0)); + assert_eq!(accelerated[31], 243); - /// Reference implementation: aurora_engine_modexp - fn reference_modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { - aurora_engine_modexp::modexp(base, exp, modulus) + assert_eq!(OpenVmCrypto.modexp(&[3], &[4], &[7]).unwrap(), [4]); } - /// Helper: run accelerated and compare against reference. - /// The accelerated path always returns BN_SCALAR_LEN bytes, so we left-pad the - /// reference output to match. - fn check(base: &[u8], exp: &[u8]) { - let modulus = bn254_fr_modulus_be(); - let expected = reference_modexp(base, exp, &modulus); - let actual = accelerated_modexp_bn254_fr(base, exp); - let mut expected_padded = vec![0u8; BN_SCALAR_LEN]; - let offset = BN_SCALAR_LEN - expected.len(); - expected_padded[offset..].copy_from_slice(&expected); - assert_eq!(actual, expected_padded, "base={base:?}, exp={exp:?}"); + #[test] + fn ripemd160_adapter_uses_evm_padding() { + assert_eq!( + OpenVmCrypto.ripemd160(b"abc"), + alloy_primitives::hex!( + "0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc" + ) + ); } #[test] - fn test_is_bn254_fr() { - // Exact modulus - assert!(is_bn254_fr(&bn254_fr_modulus_be())); - - // With leading zeros - let mut padded = vec![0u8; 10]; - padded.extend_from_slice(&bn254_fr_modulus_be()); - assert!(is_bn254_fr(&padded)); - - // All zeros → false - assert!(!is_bn254_fr(&[0u8; 32])); + fn adapters_match_revm_for_portable_primitives() { + let input = b"OpenVM accelerator provider"; + assert_eq!(OpenVmCrypto.sha256(input), DefaultCrypto.sha256(input)); + assert_eq!(OpenVmCrypto.ripemd160(input), DefaultCrypto.ripemd160(input)); + assert_eq!( + OpenVmCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]), + DefaultCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]) + ); - // Wrong modulus (flip last bit) - let mut m = bn254_fr_modulus_be(); - *m.last_mut().unwrap() ^= 1; - assert!(!is_bn254_fr(&m)); + let mut actual = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, + ]; + let mut expected = actual; + let message = [0x0123_4567_89ab_cdef; 16]; + let offset = [0x1020_3040_5060_7080, 0x90a0_b0c0_d0e0_f000]; + OpenVmCrypto.blake2_compress(12, &mut actual, &message, &offset, true); + DefaultCrypto.blake2_compress(12, &mut expected, &message, &offset, true); + assert_eq!(actual, expected); } #[test] - fn test_accelerated_modexp_bn254_fr() { - // --- short base (<=32 bytes), value < modulus --- - check(&[3], &[5]); // 3^5 mod Fr - check(&[0], &[5]); // 0^5 = 0 - check(&[3], &[0]); // 3^0 = 1 - check(&[0], &[0]); // 0^0 = 1 by convention - check(&[], &[]); // empty inputs - check(&[0, 0, 0, 3], &[5]); // leading zeros in base - - // --- short base, value >= modulus (triggers reduce fallback) --- - let m = bn254_fr_modulus_be(); - check(&m, &[1]); // Fr mod Fr = 0, so 0^1 = 0 - let mut m_plus_1 = m.clone(); - *m_plus_1.last_mut().unwrap() = m_plus_1.last().unwrap().wrapping_add(1); - check(&m_plus_1, &[2]); // (Fr+1)^2 mod Fr = 1 - check(&[0xff; 32], &[1]); // max 256-bit value, >= modulus - - // --- large base (> 32 bytes, reduce_be_bytes path) --- - check(&[0xab; 64], &[3]); // aligned (multiple of 32) - check(&[0x42; 100], &[2]); // unaligned (tests padding fix) - check(&[0xab; 64], &[0xff; 32]); // large base + large exponent - - // --- larger exponents --- - check(&[2], &[0xff; 32]); // 2^(2^256-1) mod Fr - check(&[2], &[0, 0, 0, 5]); // leading zeros in exponent - check(&[3], &[0xab; 64]); // exponent > 32 bytes - - // --- cross-path consistency: same value through different code paths --- - // 33-byte base with leading zero (reduce_be_bytes path) vs 32-byte base (from_be_bytes - // path) - let base_32 = [0xab; 32]; - let mut base_33 = vec![0u8]; - base_33.extend_from_slice(&base_32); - let exp = &[7]; + fn adapters_preserve_revm_error_variants() { + let invalid_bn_point = [0xff; 64]; + assert_eq!( + OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]), + Err(PrecompileHalt::Bn254FieldPointNotAMember) + ); assert_eq!( - accelerated_modexp_bn254_fr(&base_32, exp), - accelerated_modexp_bn254_fr(&base_33, exp), - "33-byte base with leading zero must match 32-byte base" + OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]), + DefaultCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]) + ); + + let noncanonical_fp = [0xff; BLS_FP_LEN]; + assert_eq!( + OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp), + Err(PrecompileHalt::NonCanonicalFp) + ); + assert_eq!( + OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp), + DefaultCrypto.bls12_381_fp_to_g1(&noncanonical_fp) + ); + + assert_eq!( + OpenVmCrypto.secp256k1_ecrecover(&[0; 64], 0, &[0; 32]), + Err(PrecompileHalt::Secp256k1RecoverFailed) ); } - /// Test the `Crypto::modexp` dispatch: accelerated path for BN254 Fr, - /// aurora fallback for other moduli. #[test] - fn test_modexp_dispatch() { - let crypto = OpenVmCrypto; - let fr_mod = bn254_fr_modulus_be(); - - // Accelerated path: BN254 Fr modulus - let accel = crypto.modexp(&[3], &[5], &fr_mod).unwrap(); - let reference = reference_modexp(&[3], &[5], &fr_mod); - let mut ref_padded = vec![0u8; BN_SCALAR_LEN]; - let offset = BN_SCALAR_LEN - reference.len(); - ref_padded[offset..].copy_from_slice(&reference); - assert_eq!(accel, ref_padded, "accelerated path should match reference"); - - // Fallback path: non-BN254 modulus (e.g. small prime 7) - let other_mod = &[7]; - let fallback = crypto.modexp(&[3], &[4], other_mod).unwrap(); - let expected = reference_modexp(&[3], &[4], other_mod); - assert_eq!(fallback, expected, "fallback path should match reference"); + fn streaming_adapters_report_the_first_invalid_pair() { + let invalid_bn_g1 = [0xff; 64]; + let identity_bn_g2 = [0; 128]; + let identity_bn_g1 = [0; 64]; + let short_bn_g2 = [0; 127]; + let bn_pairs = + [(&invalid_bn_g1[..], &identity_bn_g2[..]), (&identity_bn_g1[..], &short_bn_g2[..])]; + assert_eq!( + OpenVmCrypto.bn254_pairing_check(&bn_pairs), + Err(PrecompileHalt::Bn254FieldPointNotAMember) + ); + + let invalid_g1 = ([0xff; BLS_FP_LEN], [0xff; BLS_FP_LEN]); + let mut g1_pairs = + [Ok((invalid_g1, [0; 32])), Err(PrecompileHalt::Bls12381ScalarInputLength)].into_iter(); + assert_eq!( + OpenVmCrypto.bls12_381_g1_msm(&mut g1_pairs), + Err(PrecompileHalt::NonCanonicalFp) + ); } } From 7b0e120c8ebd5b0a42f3154fb9343823322d5741 Mon Sep 17 00:00:00 2001 From: Ayush Shukla Date: Tue, 11 Aug 2026 23:17:13 +0200 Subject: [PATCH 39/44] fix: keep accelerator interface client independent --- Cargo.lock | 1 - Cargo.toml | 1 - bin/stateless-guest/Cargo.lock | 1 - crates/accelerators/Cargo.toml | 1 - crates/accelerators/src/ops/blake2/mod.rs | 35 +++++++- .../accelerators/src/ops/blake2/portable.rs | 83 +++++++++++++++++++ .../accelerators/tests/conformance/blake2.rs | 2 + 7 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 crates/accelerators/src/ops/blake2/portable.rs diff --git a/Cargo.lock b/Cargo.lock index a89c4bd7f..05abc19af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6804,7 +6804,6 @@ dependencies = [ "openvm-pairing-guest", "openvm-sha2", "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", - "revm-precompile 36.0.3", "ripemd", ] diff --git a/Cargo.toml b/Cargo.toml index 0e45bb4a8..7606f8b2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,7 +87,6 @@ reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v2.3.0", d # revm revm = { version = "=40.0.3", features = ["serde"], default-features = false } -revm-precompile = { version = "=36.0.3", default-features = false } revm-primitives = { version = "=24.0.1", default-features = false } # alloy diff --git a/bin/stateless-guest/Cargo.lock b/bin/stateless-guest/Cargo.lock index 8279dd5bf..330271d54 100644 --- a/bin/stateless-guest/Cargo.lock +++ b/bin/stateless-guest/Cargo.lock @@ -2481,7 +2481,6 @@ dependencies = [ "openvm-pairing-guest", "openvm-sha2", "p256 0.13.2 (git+https://github.com/openvm-org/openvm.git?branch=develop-v2.1.0)", - "revm-precompile", "ripemd", ] diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 5b3baff20..8362f2ff1 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -25,7 +25,6 @@ ark-ec = { version = "0.5", default-features = false } ark-serialize = { version = "0.5", default-features = false } aurora-engine-modexp = { version = "1.2.0", default-features = false } ripemd = { version = "0.1.3", default-features = false } -revm-precompile.workspace = true # The OpenVM-accelerated k256 fork; its ECDSA recovery relies on zkVM hints # and is unimplemented outside the guest. diff --git a/crates/accelerators/src/ops/blake2/mod.rs b/crates/accelerators/src/ops/blake2/mod.rs index 4714055a7..5bae255b6 100644 --- a/crates/accelerators/src/ops/blake2/mod.rs +++ b/crates/accelerators/src/ops/blake2/mod.rs @@ -1,13 +1,42 @@ //! BLAKE2b compression function F (EIP-152). //! -//! Uses REVM's EIP-152 implementation so the standard interface and REVM's -//! precompile provider share one compression function. +//! Operates on raw BLAKE2b state with an arbitrary round count. + +mod portable; use crate::{ ops::Error, types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, }; +type Word = u64; + +const IV: [Word; 8] = [ + 0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179, +]; + +// The message schedule has period 10 (RFC 7693 section 2.7). EIP-152 permits +// arbitrary round counts, so rounds beyond the standard 12 must use `r % 10`. +const SIGMA: [[u8; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + /// Apply the BLAKE2 compression function F to the state vector `h` in place. /// /// `h`, `m` and `t` hold little-endian words; `f` is the final-block @@ -47,5 +76,5 @@ pub fn blake2f( /// Apply BLAKE2 compression to word-oriented state without byte conversion. #[inline] pub fn blake2f_words(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { - revm_precompile::blake2::compress(rounds, h, m, t, f); + portable::compress(rounds, h, m, t, f); } diff --git a/crates/accelerators/src/ops/blake2/portable.rs b/crates/accelerators/src/ops/blake2/portable.rs new file mode 100644 index 000000000..9bc4cc1db --- /dev/null +++ b/crates/accelerators/src/ops/blake2/portable.rs @@ -0,0 +1,83 @@ +// Ported from revm-precompile 36.0.3's EIP-152 adaptation: +// https://docs.rs/crate/revm-precompile/36.0.3/source/src/blake2/portable.rs +// That implementation is adapted from blake2b_simd: +// https://github.com/oconnor663/blake2_simd +// +// Copyright (c) 2018 Jack O'Connor +// Copyright (c) 2021-2026 draganrakita +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use super::{Word, IV, SIGMA}; + +#[inline(always)] +const fn g(v: &mut [Word; 16], a: usize, b: usize, c: usize, d: usize, x: Word, y: Word) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +#[inline(always)] +const fn round(round: usize, m: &[Word; 16], v: &mut [Word; 16]) { + let schedule = SIGMA[round % SIGMA.len()]; + + g(v, 0, 4, 8, 12, m[schedule[0] as usize], m[schedule[1] as usize]); + g(v, 1, 5, 9, 13, m[schedule[2] as usize], m[schedule[3] as usize]); + g(v, 2, 6, 10, 14, m[schedule[4] as usize], m[schedule[5] as usize]); + g(v, 3, 7, 11, 15, m[schedule[6] as usize], m[schedule[7] as usize]); + + g(v, 0, 5, 10, 15, m[schedule[8] as usize], m[schedule[9] as usize]); + g(v, 1, 6, 11, 12, m[schedule[10] as usize], m[schedule[11] as usize]); + g(v, 2, 7, 8, 13, m[schedule[12] as usize], m[schedule[13] as usize]); + g(v, 3, 4, 9, 14, m[schedule[14] as usize], m[schedule[15] as usize]); +} + +pub(super) fn compress(rounds: u32, h: &mut [Word; 8], m: &[Word; 16], t: &[Word; 2], f: bool) { + let mut v = [ + h[0], + h[1], + h[2], + h[3], + h[4], + h[5], + h[6], + h[7], + IV[0], + IV[1], + IV[2], + IV[3], + IV[4] ^ t[0], + IV[5] ^ t[1], + IV[6] ^ if f { Word::MAX } else { 0 }, + IV[7], + ]; + + for round_index in 0..rounds as usize { + round(round_index, m, &mut v); + } + + for (index, word) in h.iter_mut().enumerate() { + *word ^= v[index] ^ v[index + 8]; + } +} diff --git a/crates/accelerators/tests/conformance/blake2.rs b/crates/accelerators/tests/conformance/blake2.rs index 05f147c22..a0c5f967e 100644 --- a/crates/accelerators/tests/conformance/blake2.rs +++ b/crates/accelerators/tests/conformance/blake2.rs @@ -100,8 +100,10 @@ fn zkvm_blake2f_smoke() { ); // An invalid final flag maps to the failure status. + let valid_output = h; let status = unsafe { zkvm_blake2f(12, &mut h, &m, &t, 2) }; assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(h, valid_output); } #[test] From e1bd2325356d348086eaf659faa3e0a6ab8fcd0e Mon Sep 17 00:00:00 2001 From: Ayush Shukla Date: Wed, 12 Aug 2026 00:20:12 +0200 Subject: [PATCH 40/44] refactor: simplify accelerator API --- crates/accelerators/Cargo.toml | 9 +- crates/accelerators/src/ffi/blake2.rs | 27 ++- crates/accelerators/src/ffi/bls12_381.rs | 71 ++++--- crates/accelerators/src/ffi/bn254.rs | 20 +- crates/accelerators/src/ffi/ecdsa.rs | 13 +- crates/accelerators/src/ffi/hash.rs | 9 +- crates/accelerators/src/ffi/kzg.rs | 4 +- crates/accelerators/src/ffi/mod.rs | 5 +- crates/accelerators/src/ffi/modexp.rs | 2 +- crates/accelerators/src/lib.rs | 19 +- crates/accelerators/src/ops/blake2/mod.rs | 43 +--- .../accelerators/src/ops/bls12_381/codec.rs | 56 +++--- crates/accelerators/src/ops/bls12_381/map.rs | 55 +++-- crates/accelerators/src/ops/bls12_381/mod.rs | 119 +++-------- crates/accelerators/src/ops/bn254/codec.rs | 40 ++-- crates/accelerators/src/ops/bn254/mod.rs | 54 ++--- .../accelerators/src/ops/ecdsa/secp256k1.rs | 51 ++--- .../accelerators/src/ops/ecdsa/secp256r1.rs | 31 +-- crates/accelerators/src/ops/hash.rs | 28 +-- crates/accelerators/src/ops/kzg.rs | 37 ++-- crates/accelerators/src/ops/mod.rs | 25 ++- crates/accelerators/src/ops/modexp.rs | 13 +- .../tests/{conformance => }/blake2.rs | 33 +-- .../tests/{conformance => }/bls12_381.rs | 181 ++++++++--------- .../tests/{conformance => }/bn254.rs | 46 ++--- crates/accelerators/tests/conformance/main.rs | 12 -- .../tests/{conformance => }/ecdsa.rs | 65 ++---- .../tests/{conformance => }/hash.rs | 32 +-- .../tests/{conformance => }/kzg.rs | 24 +-- .../tests/{conformance => }/modexp.rs | 21 +- crates/revm-crypto/src/lib.rs | 190 ++++-------------- 31 files changed, 495 insertions(+), 840 deletions(-) rename crates/accelerators/tests/{conformance => }/blake2.rs (80%) rename crates/accelerators/tests/{conformance => }/bls12_381.rs (70%) rename crates/accelerators/tests/{conformance => }/bn254.rs (81%) delete mode 100644 crates/accelerators/tests/conformance/main.rs rename crates/accelerators/tests/{conformance => }/ecdsa.rs (66%) rename crates/accelerators/tests/{conformance => }/hash.rs (84%) rename crates/accelerators/tests/{conformance => }/kzg.rs (81%) rename crates/accelerators/tests/{conformance => }/modexp.rs (83%) diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index 8362f2ff1..d70690b93 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openvm-accelerators" -description = "OpenVM implementation of the zkVM Cryptographic Accelerators C Interface" +description = "OpenVM cryptographic accelerator interface" version.workspace = true edition.workspace = true homepage.workspace = true @@ -51,14 +51,7 @@ ignored = ["openvm-pairing-guest"] [package.metadata.cargo-machete] ignored = ["openvm-pairing-guest"] -[[test]] -name = "conformance" -path = "tests/conformance/main.rs" -required-features = ["ffi"] - [features] default = ["ffi"] -# The extern "C" `zkvm_*` symbols. Rust consumers that only need `ops` can -# disable this. ffi = [] std = [] diff --git a/crates/accelerators/src/ffi/blake2.rs b/crates/accelerators/src/ffi/blake2.rs index a08840a7f..3fda48d18 100644 --- a/crates/accelerators/src/ffi/blake2.rs +++ b/crates/accelerators/src/ffi/blake2.rs @@ -23,15 +23,32 @@ pub unsafe extern "C" fn zkvm_blake2f( t: *const ZkvmBlake2fOffset, f: u8, ) -> ZkvmStatus { - if h.is_null() || m.is_null() || t.is_null() { + if h.is_null() || m.is_null() || t.is_null() || f > 1 { return ZkvmStatus::Fail; } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (mut state, message, offset) = unsafe { (h.read(), m.read(), t.read()) }; - if ops::blake2f(rounds, &mut state, &message, &offset, f).is_err() { - return ZkvmStatus::Fail; + let (state, message, offset) = unsafe { (h.read(), m.read(), t.read()) }; + + let mut state_words = [0; 8]; + for (word, bytes) in state_words.iter_mut().zip(state.data.as_chunks::<8>().0) { + *word = u64::from_le_bytes(*bytes); + } + let mut message_words = [0; 16]; + for (word, bytes) in message_words.iter_mut().zip(message.data.as_chunks::<8>().0) { + *word = u64::from_le_bytes(*bytes); + } + let offset_words = [ + u64::from_le_bytes(offset.data[..8].try_into().unwrap()), + u64::from_le_bytes(offset.data[8..].try_into().unwrap()), + ]; + + ops::blake2f(rounds, &mut state_words, &message_words, &offset_words, f == 1); + + let mut value = ZkvmBlake2fState { data: [0; 64] }; + for (bytes, word) in value.data.as_chunks_mut::<8>().0.iter_mut().zip(state_words) { + *bytes = word.to_le_bytes(); } // SAFETY: `h` is non-NULL and valid for writes. - unsafe { h.write(state) }; + unsafe { h.write(value) }; ZkvmStatus::Ok } diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs index 3e802b12a..489ecfd59 100644 --- a/crates/accelerators/src/ffi/bls12_381.rs +++ b/crates/accelerators/src/ffi/bls12_381.rs @@ -28,11 +28,10 @@ pub unsafe extern "C" fn zkvm_bls12_g1_add( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - let mut value = ZkvmBls12381G1Point { data: [0; 96] }; - match ops::bls12_381_g1_add(&p1, &p2, &mut value) { - Ok(()) => { + match ops::bls12_381_g1_add(bls_g1(p1.data), bls_g1(p2.data)) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G1Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -60,11 +59,13 @@ pub unsafe extern "C" fn zkvm_bls12_g1_msm( // 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) } }; - let mut value = ZkvmBls12381G1Point { data: [0; 96] }; - match ops::bls12_381_g1_msm(pairs, &mut value) { - Ok(()) => { + let pairs = pairs.iter().map(|pair| { + Ok::<_, core::convert::Infallible>((bls_g1(pair.point.data), pair.scalar.data)) + }); + match ops::bls12_381_g1_msm(pairs) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G1Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -91,11 +92,10 @@ pub unsafe extern "C" fn zkvm_bls12_g2_add( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - let mut value = ZkvmBls12381G2Point { data: [0; 192] }; - match ops::bls12_381_g2_add(&p1, &p2, &mut value) { - Ok(()) => { + match ops::bls12_381_g2_add(bls_g2(p1.data), bls_g2(p2.data)) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G2Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -123,11 +123,13 @@ pub unsafe extern "C" fn zkvm_bls12_g2_msm( // 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) } }; - let mut value = ZkvmBls12381G2Point { data: [0; 192] }; - match ops::bls12_381_g2_msm(pairs, &mut value) { - Ok(()) => { + let pairs = pairs.iter().map(|pair| { + Ok::<_, core::convert::Infallible>((bls_g2(pair.point.data), pair.scalar.data)) + }); + match ops::bls12_381_g2_msm(pairs) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G2Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -156,9 +158,9 @@ pub unsafe extern "C" fn zkvm_bls12_pairing( // 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) } }; - let mut value = false; - match ops::bls12_381_pairing_check(pairs, &mut value) { - Ok(()) => { + let pairs = pairs.iter().map(|pair| (bls_g1(pair.g1.data), bls_g2(pair.g2.data))); + match ops::bls12_381_pairing_check(pairs) { + Ok(value) => { // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. unsafe { verified.write(value) }; ZkvmStatus::Ok @@ -171,6 +173,19 @@ pub unsafe extern "C" fn zkvm_bls12_pairing( } } +fn bls_g1(data: [u8; 96]) -> ([u8; 48], [u8; 48]) { + (data[..48].try_into().unwrap(), data[48..].try_into().unwrap()) +} + +fn bls_g2(data: [u8; 192]) -> ([u8; 48], [u8; 48], [u8; 48], [u8; 48]) { + ( + data[..48].try_into().unwrap(), + data[48..96].try_into().unwrap(), + data[96..144].try_into().unwrap(), + data[144..].try_into().unwrap(), + ) +} + /// BLS12-381 map field element to G1 (precompile 0x10, EIP-2537). /// /// Returns [`ZkvmStatus::Fail`] if either pointer is NULL or the field element @@ -190,11 +205,10 @@ pub unsafe extern "C" fn zkvm_bls12_map_fp_to_g1( } // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. let field_element = unsafe { field_element.read() }; - let mut value = ZkvmBls12381G1Point { data: [0; 96] }; - match ops::bls12_381_map_fp_to_g1(&field_element, &mut value) { - Ok(()) => { + match ops::bls12_381_map_fp_to_g1(&field_element.data) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G1Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -220,11 +234,14 @@ pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( } // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. let field_element = unsafe { field_element.read() }; - let mut value = ZkvmBls12381G2Point { data: [0; 192] }; - match ops::bls12_381_map_fp2_to_g2(&field_element, &mut value) { - Ok(()) => { + let fp2 = ( + field_element.data[..48].try_into().unwrap(), + field_element.data[48..].try_into().unwrap(), + ); + match ops::bls12_381_map_fp2_to_g2(fp2) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBls12381G2Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, diff --git a/crates/accelerators/src/ffi/bn254.rs b/crates/accelerators/src/ffi/bn254.rs index 7dae5301f..d0d34b884 100644 --- a/crates/accelerators/src/ffi/bn254.rs +++ b/crates/accelerators/src/ffi/bn254.rs @@ -25,11 +25,10 @@ pub unsafe extern "C" fn zkvm_bn254_g1_add( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - let mut value = ZkvmBn254G1Point { data: [0; 64] }; - match ops::bn254_g1_add(&p1, &p2, &mut value) { - Ok(()) => { + match ops::bn254_g1_add(&p1.data, &p2.data) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBn254G1Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -59,11 +58,10 @@ pub unsafe extern "C" fn zkvm_bn254_g1_mul( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (point, scalar) = unsafe { (point.read(), scalar.read()) }; - let mut value = ZkvmBn254G1Point { data: [0; 64] }; - match ops::bn254_g1_mul(&point, &scalar, &mut value) { - Ok(()) => { + match ops::bn254_g1_mul(&point.data, &scalar.data) { + Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(value) }; + unsafe { result.write(ZkvmBn254G1Point { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -91,9 +89,9 @@ pub unsafe extern "C" fn zkvm_bn254_pairing( // 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) } }; - let mut value = false; - match ops::bn254_pairing_check(pairs, &mut value) { - Ok(()) => { + let pairs = pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())); + match ops::bn254_pairing_check(pairs) { + Ok(value) => { // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. unsafe { verified.write(value) }; ZkvmStatus::Ok diff --git a/crates/accelerators/src/ffi/ecdsa.rs b/crates/accelerators/src/ffi/ecdsa.rs index a3396c33b..a262a360c 100644 --- a/crates/accelerators/src/ffi/ecdsa.rs +++ b/crates/accelerators/src/ffi/ecdsa.rs @@ -31,11 +31,10 @@ pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( } // SAFETY: the non-NULL inputs are valid for reads. Copying before writing supports overlap. let (msg, sig) = unsafe { (msg.read(), sig.read()) }; - let mut value = ZkvmSecp256k1Pubkey { data: [0; 64] }; - match ops::secp256k1_ecrecover(&msg, &sig, recid, &mut value) { - Ok(()) => { + match ops::secp256k1_ecrecover(&msg.data, &sig.data, recid) { + Ok(data) => { // SAFETY: `output` is non-NULL and valid for writes. - unsafe { output.write(value) }; + unsafe { output.write(ZkvmSecp256k1Pubkey { data }) }; ZkvmStatus::Ok } Err(_) => ZkvmStatus::Fail, @@ -66,8 +65,7 @@ pub unsafe extern "C" fn zkvm_secp256k1_verify( } // SAFETY: the non-NULL inputs are valid for reads. let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; - let mut value = false; - let _ = ops::secp256k1_verify(&msg, &sig, &pubkey, &mut value); + let value = ops::secp256k1_verify(&msg.data, &sig.data, &pubkey.data); // SAFETY: `verified` is non-NULL and valid for writes. unsafe { verified.write(value) }; ZkvmStatus::Ok @@ -97,8 +95,7 @@ pub unsafe extern "C" fn zkvm_secp256r1_verify( } // SAFETY: the non-NULL inputs are valid for reads. let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; - let mut value = false; - let _ = ops::secp256r1_verify(&msg, &sig, &pubkey, &mut value); + let value = ops::secp256r1_verify(&msg.data, &sig.data, &pubkey.data); // SAFETY: `verified` is non-NULL and valid for writes. unsafe { verified.write(value) }; ZkvmStatus::Ok diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs index 16f950bb7..50978a19b 100644 --- a/crates/accelerators/src/ffi/hash.rs +++ b/crates/accelerators/src/ffi/hash.rs @@ -26,8 +26,7 @@ pub unsafe extern "C" fn zkvm_keccak256( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let mut value = ZkvmKeccak256Hash { data: [0; 32] }; - ops::keccak256(data, &mut value); + let value = ZkvmKeccak256Hash { data: ops::keccak256(data) }; // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so // overlapping input/output storage is supported. unsafe { output.write(value) }; @@ -55,8 +54,7 @@ pub unsafe extern "C" fn zkvm_sha256( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let mut value = ZkvmSha256Hash { data: [0; 32] }; - ops::sha256(data, &mut value); + let value = ZkvmSha256Hash { data: ops::sha256(data) }; // SAFETY: see `zkvm_keccak256`. unsafe { output.write(value) }; ZkvmStatus::Ok @@ -86,8 +84,7 @@ pub unsafe extern "C" fn zkvm_ripemd160( } // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let mut value = ZkvmRipemd160Hash { data: [0; 32] }; - ops::ripemd160(data, &mut value); + let value = ZkvmRipemd160Hash { data: ops::ripemd160(data) }; // SAFETY: see `zkvm_keccak256`. unsafe { output.write(value) }; ZkvmStatus::Ok diff --git a/crates/accelerators/src/ffi/kzg.rs b/crates/accelerators/src/ffi/kzg.rs index 4b23d1bed..e2310f5df 100644 --- a/crates/accelerators/src/ffi/kzg.rs +++ b/crates/accelerators/src/ffi/kzg.rs @@ -30,8 +30,8 @@ pub unsafe extern "C" fn zkvm_kzg_point_eval( // SAFETY: the non-NULL inputs are valid for reads. let (commitment, z, y, proof) = unsafe { (commitment.read(), z.read(), y.read(), proof.read()) }; - let mut value = false; - let _ = ops::kzg_point_eval(&commitment, &z, &y, &proof, &mut value); + let value = + ops::kzg_point_eval(&commitment.data, &z.data, &y.data, &proof.data).unwrap_or(false); // SAFETY: `verified` is non-NULL and valid for writes. unsafe { verified.write(value) }; ZkvmStatus::Ok diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs index 87dc17f4d..3ecddb08c 100644 --- a/crates/accelerators/src/ffi/mod.rs +++ b/crates/accelerators/src/ffi/mod.rs @@ -1,8 +1,7 @@ //! The `extern "C"` layer: `zkvm_*` symbols matching `zkvm_accelerators.h`. //! -//! Every function is a thin wrapper over [`crate::ops`]: it checks pointers, -//! converts them to references, calls the operation, and maps the result to -//! [`crate::types::ZkvmStatus`]. No other logic lives here. +//! Each function validates the ABI inputs, converts their representation, +//! calls [`crate::ops`], and maps the result to [`crate::types::ZkvmStatus`]. mod blake2; mod bls12_381; diff --git a/crates/accelerators/src/ffi/modexp.rs b/crates/accelerators/src/ffi/modexp.rs index db86c7a13..652eaabba 100644 --- a/crates/accelerators/src/ffi/modexp.rs +++ b/crates/accelerators/src/ffi/modexp.rs @@ -37,7 +37,7 @@ pub unsafe extern "C" fn zkvm_modexp( // SAFETY: non-NULL checked above; validity is guaranteed by the caller. let modulus = if mod_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(modulus, mod_len) } }; - let value = ops::modexp_result(base, exp, modulus); + let value = ops::modexp(base, exp, modulus); if mod_len != 0 { // SAFETY: `output` is non-NULL and valid for `mod_len` writes. `value` cannot overlap it, // and all caller-provided input reads are complete. diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs index e851bff8e..affa9e773 100644 --- a/crates/accelerators/src/lib.rs +++ b/crates/accelerators/src/lib.rs @@ -1,10 +1,21 @@ -//! OpenVM implementation of the zkVM Cryptographic Accelerators C Interface. +//! OpenVM implementation of the zkVM cryptographic accelerator interface. +//! +//! Points and scalars use fixed-size big-endian encodings. BLS12-381 G2 uses +//! `x_c0 || x_c1 || y_c0 || y_c1`; BN254 G2 uses the EIP-197 +//! `x_c1 || x_c0 || y_c1 || y_c0` order. #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; #[cfg(feature = "ffi")] -pub mod ffi; -pub mod ops; -pub mod types; +mod ffi; +mod ops; +#[cfg(feature = "ffi")] +mod types; + +#[cfg(feature = "ffi")] +pub use ffi::*; +pub use ops::*; +#[cfg(feature = "ffi")] +pub use types::*; diff --git a/crates/accelerators/src/ops/blake2/mod.rs b/crates/accelerators/src/ops/blake2/mod.rs index 5bae255b6..800b08e69 100644 --- a/crates/accelerators/src/ops/blake2/mod.rs +++ b/crates/accelerators/src/ops/blake2/mod.rs @@ -4,11 +4,6 @@ mod portable; -use crate::{ - ops::Error, - types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState}, -}; - type Word = u64; const IV: [Word; 8] = [ @@ -37,44 +32,8 @@ const SIGMA: [[u8; 16]; 10] = [ [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], ]; -/// Apply the BLAKE2 compression function F to the state vector `h` in place. -/// -/// `h`, `m` and `t` hold little-endian words; `f` is the final-block -/// indicator and must be `0` or `1`. -pub fn blake2f( - rounds: u32, - h: &mut ZkvmBlake2fState, - m: &ZkvmBlake2fMessage, - t: &ZkvmBlake2fOffset, - f: u8, -) -> Result<(), Error> { - if f > 1 { - return Err(Error::InvalidFinalFlag); - } - - let mut state = [0u64; 8]; - for (word, chunk) in state.iter_mut().zip(h.data.chunks_exact(8)) { - *word = u64::from_le_bytes(chunk.try_into().unwrap()); - } - let mut message = [0u64; 16]; - for (word, chunk) in message.iter_mut().zip(m.data.chunks_exact(8)) { - *word = u64::from_le_bytes(chunk.try_into().unwrap()); - } - let offset = [ - u64::from_le_bytes(t.data[..8].try_into().unwrap()), - u64::from_le_bytes(t.data[8..].try_into().unwrap()), - ]; - - blake2f_words(rounds, &mut state, &message, &offset, f == 1); - - for (chunk, word) in h.data.chunks_exact_mut(8).zip(state.iter()) { - chunk.copy_from_slice(&word.to_le_bytes()); - } - Ok(()) -} - /// Apply BLAKE2 compression to word-oriented state without byte conversion. #[inline] -pub fn blake2f_words(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { +pub fn blake2f(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { portable::compress(rounds, h, m, t, f); } diff --git a/crates/accelerators/src/ops/bls12_381/codec.rs b/crates/accelerators/src/ops/bls12_381/codec.rs index f6c5d7ebc..7f72feeaf 100644 --- a/crates/accelerators/src/ops/bls12_381/codec.rs +++ b/crates/accelerators/src/ops/bls12_381/codec.rs @@ -6,10 +6,7 @@ use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint, Group}; use openvm_pairing::bls12_381 as bls; use super::BLS_FP_LEN; -use crate::{ - ops::Error, - types::{ZkvmBls12381G1Point, ZkvmBls12381G2Point, ZkvmBls12381Scalar}, -}; +use crate::ops::{BlsG1, BlsG2, Error}; #[inline] fn read_bls_fp(input: &[u8]) -> Result { @@ -24,18 +21,16 @@ fn read_bls_fp2(c0: &[u8], c1: &[u8]) -> Result { } #[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..])?; +pub(super) fn read_bls_g1_point_no_subgroup_check(point: &BlsG1) -> Result { + let px = read_bls_fp(&point.0)?; + let py = read_bls_fp(&point.1)?; // 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 { +pub(super) fn read_bls_g1_point(point: &BlsG1) -> Result { let point = read_bls_g1_point_no_subgroup_check(point)?; if point.is_in_correct_subgroup() { Ok(point) @@ -45,19 +40,16 @@ pub(super) fn read_bls_g1_point(point: &ZkvmBls12381G1Point) -> Result 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..])?; +pub(super) fn read_bls_g2_point_no_subgroup_check(point: &BlsG2) -> Result { + let x = read_bls_fp2(&point.0, &point.1)?; + let y = read_bls_fp2(&point.2, &point.3)?; // 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 { +pub(super) fn read_bls_g2_point(point: &BlsG2) -> Result { let point = read_bls_g2_point_no_subgroup_check(point)?; if point.is_in_correct_subgroup() { Ok(point) @@ -67,30 +59,31 @@ pub(super) fn read_bls_g2_point(point: &ZkvmBls12381G2Point) -> Result bls::Scalar { - bls::Scalar::from_be_bytes_unchecked(&input.data) +pub(super) fn read_bls_scalar(input: &[u8; 32]) -> bls::Scalar { + bls::Scalar::from_be_bytes_unchecked(input) } #[inline] -pub(super) fn encode_bls_g1_point(point: &bls::G1Affine, output: &mut ZkvmBls12381G1Point) { +pub(super) fn encode_bls_g1_point(point: &bls::G1Affine) -> [u8; 96] { + let mut output = [0; 96]; if point.is_identity() { - output.data.fill(0); - return; + return output; } 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]; + output[i] = x_bytes[BLS_FP_LEN - 1 - i]; + output[i + BLS_FP_LEN] = y_bytes[BLS_FP_LEN - 1 - i]; } + output } #[inline] -pub(super) fn encode_bls_g2_point(point: &bls::G2Affine, output: &mut ZkvmBls12381G2Point) { +pub(super) fn encode_bls_g2_point(point: &bls::G2Affine) -> [u8; 192] { + let mut output = [0; 192]; if point.is_identity() { - output.data.fill(0); - return; + return output; } let x = point.x(); @@ -100,9 +93,10 @@ pub(super) fn encode_bls_g2_point(point: &bls::G2Affine, output: &mut ZkvmBls123 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]; + output[i] = x_c0[BLS_FP_LEN - 1 - i]; + output[i + BLS_FP_LEN] = x_c1[BLS_FP_LEN - 1 - i]; + output[i + (2 * BLS_FP_LEN)] = y_c0[BLS_FP_LEN - 1 - i]; + output[i + (3 * BLS_FP_LEN)] = y_c1[BLS_FP_LEN - 1 - i]; } + output } diff --git a/crates/accelerators/src/ops/bls12_381/map.rs b/crates/accelerators/src/ops/bls12_381/map.rs index 93ab05d7a..c16f3bb73 100644 --- a/crates/accelerators/src/ops/bls12_381/map.rs +++ b/crates/accelerators/src/ops/bls12_381/map.rs @@ -10,38 +10,27 @@ use ark_ec::{ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use super::BLS_FP_LEN; -use crate::{ - ops::Error, - types::{ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1Point, ZkvmBls12381G2Point}, -}; +use crate::ops::Error; /// BLS12-381 map field element to G1 (precompile 0x10). -pub fn bls12_381_map_fp_to_g1( - fp: &ZkvmBls12381Fp, - output: &mut ZkvmBls12381G1Point, -) -> Result<(), Error> { - let fp = read_fq(&fp.data)?; +pub fn bls12_381_map_fp_to_g1(fp: &[u8; 48]) -> Result<[u8; 96], Error> { + let fp = read_fq(fp)?; let point = WBMap::map_to_curve(fp) .expect("the arkworks WB map is defined for every field element") .clear_cofactor(); - encode_g1_point(&point, output); - Ok(()) + Ok(encode_g1_point(&point)) } -/// BLS12-381 map field element to G2 (precompile 0x11). Input is `c0 || c1`. -pub fn bls12_381_map_fp2_to_g2( - fp2: &ZkvmBls12381Fp2, - output: &mut ZkvmBls12381G2Point, -) -> Result<(), Error> { - let c0 = read_fq(&fp2.data[..BLS_FP_LEN])?; - let c1 = read_fq(&fp2.data[BLS_FP_LEN..])?; +/// BLS12-381 map field element to G2 (precompile 0x11). +pub fn bls12_381_map_fp2_to_g2(fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], Error> { + let c0 = read_fq(&fp2.0)?; + let c1 = read_fq(&fp2.1)?; let point = WBMap::map_to_curve(Fq2::new(c0, c1)) .expect("the arkworks WB map is defined for every field element") .clear_cofactor(); - encode_g2_point(&point, output); - Ok(()) + Ok(encode_g2_point(&point)) } /// Reads a big-endian field element, rejecting non-canonical encodings. @@ -60,26 +49,28 @@ fn encode_fq(fq: &Fq, output: &mut [u8]) { } /// Writes a G1 point as `x || y`; the point at infinity encodes as zeros. -fn encode_g1_point(point: &G1Affine, output: &mut ZkvmBls12381G1Point) { +fn encode_g1_point(point: &G1Affine) -> [u8; 96] { + let mut output = [0; 96]; let Some((x, y)) = point.xy() else { - output.data.fill(0); - return; + return output; }; - encode_fq(&x, &mut output.data[..BLS_FP_LEN]); - encode_fq(&y, &mut output.data[BLS_FP_LEN..]); + encode_fq(&x, &mut output[..BLS_FP_LEN]); + encode_fq(&y, &mut output[BLS_FP_LEN..]); + output } /// Writes a G2 point as `x_c0 || x_c1 || y_c0 || y_c1`; the point at infinity /// encodes as zeros. -fn encode_g2_point(point: &G2Affine, output: &mut ZkvmBls12381G2Point) { +fn encode_g2_point(point: &G2Affine) -> [u8; 192] { + let mut output = [0; 192]; let Some((x, y)) = point.xy() else { - output.data.fill(0); - return; + return output; }; - encode_fq(&x.c0, &mut output.data[..BLS_FP_LEN]); - encode_fq(&x.c1, &mut output.data[BLS_FP_LEN..2 * BLS_FP_LEN]); - encode_fq(&y.c0, &mut output.data[2 * BLS_FP_LEN..3 * BLS_FP_LEN]); - encode_fq(&y.c1, &mut output.data[3 * BLS_FP_LEN..]); + encode_fq(&x.c0, &mut output[..BLS_FP_LEN]); + encode_fq(&x.c1, &mut output[BLS_FP_LEN..2 * BLS_FP_LEN]); + encode_fq(&y.c0, &mut output[2 * BLS_FP_LEN..3 * BLS_FP_LEN]); + encode_fq(&y.c1, &mut output[3 * BLS_FP_LEN..]); + output } diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 2b14a8564..68d21064f 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -18,13 +18,7 @@ use openvm_ecc_guest::{ }; use openvm_pairing::{bls12_381::Bls12_381, PairingCheck}; -use crate::{ - ops::{Error, StreamError}, - types::{ - ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, - ZkvmBls12381PairingPair, - }, -}; +use crate::ops::{BlsG1, BlsG2, Error, StreamError}; /// The number of bytes needed to represent an element of the base field Fp. const BLS_FP_LEN: usize = 48; @@ -33,122 +27,75 @@ const BLS_FP_LEN: usize = 48; /// /// 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(()) +pub fn bls12_381_g1_add(p1: BlsG1, p2: BlsG1) -> Result<[u8; 96], Error> { + let p1 = read_bls_g1_point_no_subgroup_check(&p1)?; + let p2 = read_bls_g1_point_no_subgroup_check(&p2)?; + Ok(encode_bls_g1_point(&(p1 + p2))) } /// 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> { - *output = bls12_381_g1_msm_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) - .map_err(|error| match error { - StreamError::Operation(error) => error, - StreamError::Source(never) => match never {}, - })?; - Ok(()) -} - -/// BLS12-381 G1 MSM over a fallible stream, preserving input-error order. -pub fn bls12_381_g1_msm_iter( - pairs: impl IntoIterator>, -) -> Result> { +pub fn bls12_381_g1_msm( + pairs: impl IntoIterator>, +) -> Result<[u8; 96], StreamError> { let pairs = pairs.into_iter(); let capacity = pairs.size_hint().0; let mut points = Vec::with_capacity(capacity); let mut scalars = Vec::with_capacity(capacity); for pair in pairs { - let pair = pair.map_err(StreamError::Source)?; - points.push(read_bls_g1_point(&pair.point).map_err(StreamError::Operation)?); - scalars.push(read_bls_scalar(&pair.scalar)); + let (point, scalar) = pair.map_err(StreamError::Source)?; + points.push(read_bls_g1_point(&point).map_err(StreamError::Operation)?); + scalars.push(read_bls_scalar(&scalar)); } - let mut output = ZkvmBls12381G1Point { data: [0; 96] }; - if !points.is_empty() { - encode_bls_g1_point(&Bls12_381::msm(&scalars, &points), &mut output); + if points.is_empty() { + Ok([0; 96]) + } else { + Ok(encode_bls_g1_point(&Bls12_381::msm(&scalars, &points))) } - Ok(output) } /// 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(()) +pub fn bls12_381_g2_add(p1: BlsG2, p2: BlsG2) -> Result<[u8; 192], Error> { + let p1 = read_bls_g2_point_no_subgroup_check(&p1)?; + let p2 = read_bls_g2_point_no_subgroup_check(&p2)?; + Ok(encode_bls_g2_point(&(p1 + p2))) } /// 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> { - *output = bls12_381_g2_msm_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) - .map_err(|error| match error { - StreamError::Operation(error) => error, - StreamError::Source(never) => match never {}, - })?; - Ok(()) -} - -/// BLS12-381 G2 MSM over a fallible stream, preserving input-error order. -pub fn bls12_381_g2_msm_iter( - pairs: impl IntoIterator>, -) -> Result> { +pub fn bls12_381_g2_msm( + pairs: impl IntoIterator>, +) -> Result<[u8; 192], StreamError> { let pairs = pairs.into_iter(); let capacity = pairs.size_hint().0; let mut points = Vec::with_capacity(capacity); let mut scalars = Vec::with_capacity(capacity); for pair in pairs { - let pair = pair.map_err(StreamError::Source)?; - points.push(read_bls_g2_point(&pair.point).map_err(StreamError::Operation)?); - scalars.push(read_bls_scalar(&pair.scalar)); + let (point, scalar) = pair.map_err(StreamError::Source)?; + points.push(read_bls_g2_point(&point).map_err(StreamError::Operation)?); + scalars.push(read_bls_scalar(&scalar)); } - let mut output = ZkvmBls12381G2Point { data: [0; 192] }; - if !points.is_empty() { - encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points), &mut output); + if points.is_empty() { + Ok([0; 192]) + } else { + Ok(encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points))) } - Ok(output) } /// 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; - let value = bls12_381_pairing_check_iter(pairs.iter().copied())?; - *verified = value; - Ok(()) -} - -/// BLS12-381 pairing check over a stream of encoded pairs. -pub fn bls12_381_pairing_check_iter( - pairs: impl IntoIterator, + pairs: impl IntoIterator, ) -> Result { let pairs = pairs.into_iter(); let capacity = pairs.size_hint().0; @@ -156,13 +103,13 @@ pub fn bls12_381_pairing_check_iter( let mut g1_points = Vec::with_capacity(capacity); let mut g2_points = Vec::with_capacity(capacity); - for pair in pairs { - let g1 = read_bls_g1_point(&pair.g1).map_err(|error| match error { + for (g1, g2) in pairs { + let g1 = read_bls_g1_point(&g1).map_err(|error| match error { Error::PointNotOnCurve => Error::BlsG1PointNotOnCurve, Error::PointNotInSubgroup => Error::BlsG1PointNotInSubgroup, error => error, })?; - let g2 = read_bls_g2_point(&pair.g2).map_err(|error| match error { + let g2 = read_bls_g2_point(&g2).map_err(|error| match error { Error::PointNotOnCurve => Error::BlsG2PointNotOnCurve, Error::PointNotInSubgroup => Error::BlsG2PointNotInSubgroup, error => error, diff --git a/crates/accelerators/src/ops/bn254/codec.rs b/crates/accelerators/src/ops/bn254/codec.rs index b940549ff..4ac9b7439 100644 --- a/crates/accelerators/src/ops/bn254/codec.rs +++ b/crates/accelerators/src/ops/bn254/codec.rs @@ -5,12 +5,11 @@ 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}, -}; +use crate::ops::Error; const BN_FQ_LEN: usize = 32; +const BN_G1_LEN: usize = BN_FQ_LEN * 2; +const BN_G2_LEN: usize = BN_G1_LEN * 2; #[inline] fn read_bn_fq(input: &[u8]) -> Result { @@ -26,9 +25,12 @@ fn read_bn_fq2(input: &[u8]) -> Result { } #[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..])?; +pub(super) fn read_bn_g1_point(input: &[u8]) -> Result { + if input.len() != BN_G1_LEN { + return Err(Error::InvalidLength); + } + let px = read_bn_fq(&input[..BN_FQ_LEN])?; + let py = read_bn_fq(&input[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)?; @@ -40,9 +42,12 @@ pub(super) fn read_bn_g1_point(input: &ZkvmBn254G1Point) -> Result Result { - let x = read_bn_fq2(&input.data[..BN_FQ_LEN * 2])?; - let y = read_bn_fq2(&input.data[BN_FQ_LEN * 2..])?; +pub(super) fn read_bn_g2_point(input: &[u8]) -> Result { + if input.len() != BN_G2_LEN { + return Err(Error::InvalidLength); + } + let x = read_bn_fq2(&input[..BN_G1_LEN])?; + let y = read_bn_fq2(&input[BN_G1_LEN..])?; // 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)?; @@ -54,16 +59,21 @@ pub(super) fn read_bn_g2_point(input: &ZkvmBn254G2Point) -> Result bn::Scalar { - bn::Scalar::from_be_bytes_unchecked(&input.data) +pub(super) fn read_bn_scalar(input: &[u8]) -> Result { + if input.len() != BN_FQ_LEN { + return Err(Error::InvalidLength); + } + Ok(bn::Scalar::from_be_bytes_unchecked(input)) } #[inline] -pub(super) fn encode_bn_g1_point(point: bn::G1Affine, output: &mut ZkvmBn254G1Point) { +pub(super) fn encode_bn_g1_point(point: bn::G1Affine) -> [u8; BN_G1_LEN] { + let mut output = [0; BN_G1_LEN]; 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]; + output[i] = x_bytes[BN_FQ_LEN - 1 - i]; + output[i + BN_FQ_LEN] = y_bytes[BN_FQ_LEN - 1 - i]; } + output } diff --git a/crates/accelerators/src/ops/bn254/mod.rs b/crates/accelerators/src/ops/bn254/mod.rs index a2fcad052..394752f6d 100644 --- a/crates/accelerators/src/ops/bn254/mod.rs +++ b/crates/accelerators/src/ops/bn254/mod.rs @@ -11,65 +11,35 @@ use openvm_ecc_guest::{ }; use openvm_pairing::{bn254::Bn254, PairingCheck}; -use crate::{ - ops::{Error, StreamError}, - types::{ZkvmBn254G1Point, ZkvmBn254PairingPair, ZkvmBn254Scalar}, -}; +use crate::ops::Error; /// BN254 G1 point addition (precompile 0x06). -pub fn bn254_g1_add( - p1: &ZkvmBn254G1Point, - p2: &ZkvmBn254G1Point, - output: &mut ZkvmBn254G1Point, -) -> Result<(), Error> { +pub fn bn254_g1_add(p1: &[u8], p2: &[u8]) -> Result<[u8; 64], Error> { let p1 = read_bn_g1_point(p1)?; let p2 = read_bn_g1_point(p2)?; - encode_bn_g1_point(p1 + p2, output); - Ok(()) + Ok(encode_bn_g1_point(p1 + p2)) } /// BN254 G1 scalar multiplication (precompile 0x07). -pub fn bn254_g1_mul( - point: &ZkvmBn254G1Point, - scalar: &ZkvmBn254Scalar, - output: &mut ZkvmBn254G1Point, -) -> Result<(), Error> { +pub fn bn254_g1_mul(point: &[u8], scalar: &[u8]) -> Result<[u8; 64], Error> { let p = read_bn_g1_point(point)?; - let s = read_bn_scalar(scalar); - encode_bn_g1_point(Bn254::msm(&[s], &[p]), output); - Ok(()) + let s = read_bn_scalar(scalar)?; + Ok(encode_bn_g1_point(Bn254::msm(&[s], &[p]))) } /// BN254 pairing check (precompile 0x08). -pub fn bn254_pairing_check( - pairs: &[ZkvmBn254PairingPair], - verified: &mut bool, -) -> Result<(), Error> { - *verified = false; - let value = - bn254_pairing_check_iter(pairs.iter().copied().map(Ok::<_, core::convert::Infallible>)) - .map_err(|error| match error { - StreamError::Operation(error) => error, - StreamError::Source(never) => match never {}, - })?; - *verified = value; - Ok(()) -} - -/// BN254 pairing check over a stream of encoded pairs. -pub fn bn254_pairing_check_iter( - pairs: impl IntoIterator>, -) -> Result> { +pub fn bn254_pairing_check<'a>( + pairs: impl IntoIterator, +) -> Result { let pairs = pairs.into_iter(); let capacity = pairs.size_hint().0; let mut g1_points = Vec::with_capacity(capacity); let mut g2_points = Vec::with_capacity(capacity); - for pair in pairs { - let pair = pair.map_err(StreamError::Source)?; - let g1 = read_bn_g1_point(&pair.g1).map_err(StreamError::Operation)?; - let g2 = read_bn_g2_point(&pair.g2).map_err(StreamError::Operation)?; + for (g1, g2) in pairs { + let g1 = read_bn_g1_point(g1)?; + let g2 = read_bn_g2_point(g2)?; let (g1_x, g1_y) = g1.into_coords(); let (g2_x, g2_y) = g2.into_coords(); diff --git a/crates/accelerators/src/ops/ecdsa/secp256k1.rs b/crates/accelerators/src/ops/ecdsa/secp256k1.rs index a69dfdbea..8ee8ca338 100644 --- a/crates/accelerators/src/ops/ecdsa/secp256k1.rs +++ b/crates/accelerators/src/ops/ecdsa/secp256k1.rs @@ -8,22 +8,18 @@ use openvm_k256 as k256; use k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; -use crate::{ - ops::Error, - types::{ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature}, -}; +use crate::ops::Error; /// Recover the uncompressed secp256k1 public key from an ECDSA signature -/// over `msg` into `output`. +/// over `msg`. /// /// Both low-s and high-s signatures are accepted. pub fn secp256k1_ecrecover( - msg: &ZkvmSecp256k1Hash, - sig: &ZkvmSecp256k1Signature, + msg: &[u8; 32], + sig: &[u8; 64], mut recid: u8, - output: &mut ZkvmSecp256k1Pubkey, -) -> Result<(), Error> { - let mut signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; +) -> Result<[u8; 64], Error> { + let mut signature = Signature::from_slice(sig).map_err(|_| Error::InvalidSignature)?; // k256 requires a low-s signature for recovery; normalizing flips the // recovery id parity but recovers the same key. if let Some(normalized) = signature.normalize_s() { @@ -33,39 +29,32 @@ pub fn secp256k1_ecrecover( let recovery_id = RecoveryId::from_byte(recid).ok_or(Error::InvalidSignature)?; #[cfg(any(target_os = "none", target_os = "openvm"))] - let key = - VerifyingKey::recover_from_prehash_noverify(&msg.data, &signature.to_bytes(), recovery_id) - .map_err(|_| Error::InvalidSignature)?; + let key = VerifyingKey::recover_from_prehash_noverify(msg, &signature.to_bytes(), recovery_id) + .map_err(|_| Error::InvalidSignature)?; #[cfg(not(any(target_os = "none", target_os = "openvm")))] - let key = VerifyingKey::recover_from_prehash(&msg.data, &signature, recovery_id) + let key = VerifyingKey::recover_from_prehash(msg, &signature, recovery_id) .map_err(|_| Error::InvalidSignature)?; let point = key.to_encoded_point(false); - output.data.copy_from_slice(&point.as_bytes()[1..65]); - Ok(()) + Ok(point.as_bytes()[1..65].try_into().unwrap()) } -/// Verify an ECDSA signature over secp256k1 against an uncompressed public -/// key, writing the result to `verified`. +/// Verify an ECDSA signature over secp256k1 against an uncompressed public key. /// /// Both low-s and high-s signatures are accepted. -pub fn secp256k1_verify( - msg: &ZkvmSecp256k1Hash, - sig: &ZkvmSecp256k1Signature, - pubkey: &ZkvmSecp256k1Pubkey, - verified: &mut bool, -) -> Result<(), Error> { - *verified = false; - +pub fn secp256k1_verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { let mut sec1 = [0u8; 65]; sec1[0] = 0x04; - sec1[1..].copy_from_slice(&pubkey.data); - let key = VerifyingKey::from_sec1_bytes(&sec1).map_err(|_| Error::PointNotOnCurve)?; - let mut signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; + sec1[1..].copy_from_slice(pubkey); + let Ok(key) = VerifyingKey::from_sec1_bytes(&sec1) else { + return false; + }; + let Ok(mut signature) = Signature::from_slice(sig) else { + return false; + }; // k256 rejects high-s signatures in verification. Normalize to accept both forms. if let Some(normalized) = signature.normalize_s() { signature = normalized; } - *verified = key.verify_prehash(&msg.data, &signature).is_ok(); - Ok(()) + key.verify_prehash(msg, &signature).is_ok() } diff --git a/crates/accelerators/src/ops/ecdsa/secp256r1.rs b/crates/accelerators/src/ops/ecdsa/secp256r1.rs index fd21c0a16..3a355a36a 100644 --- a/crates/accelerators/src/ops/ecdsa/secp256r1.rs +++ b/crates/accelerators/src/ops/ecdsa/secp256r1.rs @@ -5,25 +5,14 @@ use openvm_p256::{ EncodedPoint, }; -use crate::{ - ops::Error, - types::{ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature}, -}; - -/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed -/// public key, writing the result to `verified`. -pub fn secp256r1_verify( - msg: &ZkvmSecp256r1Hash, - sig: &ZkvmSecp256r1Signature, - pubkey: &ZkvmSecp256r1Pubkey, - verified: &mut bool, -) -> Result<(), Error> { - *verified = false; - - let encoded_point = EncodedPoint::from_untagged_bytes(&pubkey.data.into()); - let key = - VerifyingKey::from_encoded_point(&encoded_point).map_err(|_| Error::PointNotOnCurve)?; - let signature = Signature::from_slice(&sig.data).map_err(|_| Error::InvalidSignature)?; - *verified = key.verify_prehash(&msg.data, &signature).is_ok(); - Ok(()) +/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed public key. +pub fn secp256r1_verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { + let encoded_point = EncodedPoint::from_untagged_bytes(&(*pubkey).into()); + let Ok(key) = VerifyingKey::from_encoded_point(&encoded_point) else { + return false; + }; + let Ok(signature) = Signature::from_slice(sig) else { + return false; + }; + key.verify_prehash(msg, &signature).is_ok() } diff --git a/crates/accelerators/src/ops/hash.rs b/crates/accelerators/src/ops/hash.rs index 3bae39f29..847186648 100644 --- a/crates/accelerators/src/ops/hash.rs +++ b/crates/accelerators/src/ops/hash.rs @@ -1,30 +1,30 @@ //! Hash operations. -use crate::types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash}; - -/// Compute the Keccak-256 hash of `data` into `output`. +/// Compute the Keccak-256 hash of `data`. #[inline] -pub fn keccak256(data: &[u8], output: &mut ZkvmKeccak256Hash) { - openvm_keccak256::set_keccak256(data, &mut output.data); +pub fn keccak256(data: &[u8]) -> [u8; 32] { + openvm_keccak256::keccak256(data) } -/// Compute the SHA-256 hash of `data` into `output`. +/// Compute the SHA-256 hash of `data`. #[inline] -pub fn sha256(data: &[u8], output: &mut ZkvmSha256Hash) { +pub fn sha256(data: &[u8]) -> [u8; 32] { #[cfg(not(openvm_intrinsics))] use openvm_sha2::Digest; - output.data = openvm_sha2::Sha256::digest(data).into(); + openvm_sha2::Sha256::digest(data).into() } -/// Compute the RIPEMD-160 hash of `data` into `output`. +/// Compute the RIPEMD-160 hash of `data`. /// -/// The 20-byte digest is written to `output.data[12..]`; the first 12 bytes -/// are zeroed, matching the EVM word layout. +/// The 20-byte digest is written to the final 20 bytes; the first 12 bytes are +/// zeroed, matching the EVM word layout. #[inline] -pub fn ripemd160(data: &[u8], output: &mut ZkvmRipemd160Hash) { +pub fn ripemd160(data: &[u8]) -> [u8; 32] { use ripemd::Digest; let mut hasher = ripemd::Ripemd160::new(); hasher.update(data); - output.data[..12].fill(0); - hasher.finalize_into((&mut output.data[12..]).into()); + + let mut output = [0; 32]; + hasher.finalize_into((&mut output[12..]).into()); + output } diff --git a/crates/accelerators/src/ops/kzg.rs b/crates/accelerators/src/ops/kzg.rs index 3cca34cc4..d7df493fb 100644 --- a/crates/accelerators/src/ops/kzg.rs +++ b/crates/accelerators/src/ops/kzg.rs @@ -2,35 +2,28 @@ use openvm_kzg::{Bytes32, Bytes48, KzgProof}; -use crate::{ - ops::Error, - types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof}, -}; +use crate::ops::Error; /// Verify a KZG proof that the blob committed to by `commitment` evaluates -/// to `y` at point `z`, writing the result to `verified`. +/// to `y` at point `z`. /// /// Errors mean the check could not run — a commitment or proof that is not -/// a valid compressed G1 point, or an out-of-range field element; `verified` -/// is `false` only when a well-formed proof does not verify. +/// a valid compressed G1 point, or an out-of-range field element. `Ok(false)` +/// means a well-formed proof did not verify. pub fn kzg_point_eval( - commitment: &ZkvmKzgCommitment, - z: &ZkvmKzgFieldElement, - y: &ZkvmKzgFieldElement, - proof: &ZkvmKzgProof, - verified: &mut bool, -) -> Result<(), Error> { - *verified = false; - + commitment: &[u8; 48], + z: &[u8; 32], + y: &[u8; 32], + proof: &[u8; 48], +) -> Result { let env = openvm_kzg::EnvKzgSettings::default(); let kzg_settings = env.get(); - let commitment = Bytes48::from_slice(&commitment.data).map_err(|_| Error::KzgInvalidInput)?; - let z = Bytes32::from_slice(&z.data).map_err(|_| Error::KzgInvalidInput)?; - let y = Bytes32::from_slice(&y.data).map_err(|_| Error::KzgInvalidInput)?; - let proof = Bytes48::from_slice(&proof.data).map_err(|_| Error::KzgInvalidInput)?; + let commitment = Bytes48::from_slice(commitment).map_err(|_| Error::KzgInvalidInput)?; + let z = Bytes32::from_slice(z).map_err(|_| Error::KzgInvalidInput)?; + let y = Bytes32::from_slice(y).map_err(|_| Error::KzgInvalidInput)?; + let proof = Bytes48::from_slice(proof).map_err(|_| Error::KzgInvalidInput)?; - *verified = KzgProof::verify_kzg_proof(&commitment, &z, &y, &proof, kzg_settings) - .map_err(|_| Error::KzgInvalidInput)?; - Ok(()) + KzgProof::verify_kzg_proof(&commitment, &z, &y, &proof, kzg_settings) + .map_err(|_| Error::KzgInvalidInput) } diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs index 2072ba6aa..b43fff455 100644 --- a/crates/accelerators/src/ops/mod.rs +++ b/crates/accelerators/src/ops/mod.rs @@ -1,7 +1,7 @@ //! OpenVM-accelerated implementations of the zkVM accelerator operations. //! -//! All functions operate on fixed-size big-endian byte encodings; BLS12-381 -//! G2 is `x_c0 || x_c1 || y_c0 || y_c1`, BN254 G2 uses the EIP-197 +//! Functions use ordinary Rust arrays, slices and tuples. BLS12-381 G2 is +//! `(x_c0, x_c1, y_c0, y_c1)`; BN254 G2 byte slices use the EIP-197 //! `x_c1 || x_c0 || y_c1 || y_c0` order. mod blake2; @@ -12,17 +12,22 @@ mod hash; mod kzg; mod modexp; -pub use blake2::{blake2f, blake2f_words}; +pub use blake2::blake2f; pub use bls12_381::{ - bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g1_msm_iter, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_g2_msm_iter, bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, - bls12_381_pairing_check, bls12_381_pairing_check_iter, + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, }; -pub use bn254::{bn254_g1_add, bn254_g1_mul, bn254_pairing_check, bn254_pairing_check_iter}; +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; -pub use modexp::{modexp, modexp_result}; +pub use modexp::modexp; + +/// Uncompressed BLS12-381 G1 coordinates `(x, y)`, big-endian. +pub type BlsG1 = ([u8; 48], [u8; 48]); + +/// Uncompressed BLS12-381 G2 coordinates `(x_c0, x_c1, y_c0, y_c1)`, big-endian. +pub type BlsG2 = ([u8; 48], [u8; 48], [u8; 48], [u8; 48]); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StreamError { @@ -34,6 +39,8 @@ pub enum StreamError { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { + /// An input does not have the length required by the operation. + InvalidLength, /// A field element is out of range or otherwise not a field member. FieldElementInvalid, /// A point encoding does not satisfy the curve equation. @@ -48,8 +55,6 @@ pub enum Error { BlsG2PointNotOnCurve, /// A BLS12-381 pairing G2 point is not in the prime-order subgroup. BlsG2PointNotInSubgroup, - /// The BLAKE2f final-block flag is neither 0 nor 1. - InvalidFinalFlag, /// A signature could not be parsed or key recovery failed. InvalidSignature, /// KZG commitment/proof/field-element inputs are malformed. diff --git a/crates/accelerators/src/ops/modexp.rs b/crates/accelerators/src/ops/modexp.rs index 88afd49fa..223d87aff 100644 --- a/crates/accelerators/src/ops/modexp.rs +++ b/crates/accelerators/src/ops/modexp.rs @@ -9,19 +9,8 @@ use openvm_pairing::bn254 as bn; /// field Fr. const BN_SCALAR_LEN: usize = 32; -/// Compute `base^exp % modulus` into `output`, left-padded with zeros. -/// -/// # Panics -/// -/// Panics if `output.len() != modulus.len()`. -pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8], output: &mut [u8]) { - assert_eq!(output.len(), modulus.len(), "output must be exactly modulus-sized"); - - output.copy_from_slice(&modexp_result(base, exp, modulus)); -} - /// Compute `base^exp % modulus`, returning exactly `modulus.len()` bytes. -pub fn modexp_result(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { +pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { let mut result = if is_bn254_fr(modulus) { accelerated_modexp_bn254_fr(base, exp) } else { diff --git a/crates/accelerators/tests/conformance/blake2.rs b/crates/accelerators/tests/blake2.rs similarity index 80% rename from crates/accelerators/tests/conformance/blake2.rs rename to crates/accelerators/tests/blake2.rs index a0c5f967e..2c0c03a52 100644 --- a/crates/accelerators/tests/conformance/blake2.rs +++ b/crates/accelerators/tests/blake2.rs @@ -1,10 +1,10 @@ //! BLAKE2f conformance: the official EIP-152 test vectors 4-7. +#![cfg(feature = "ffi")] + use hex_literal::hex; use openvm_accelerators::{ - ffi::zkvm_blake2f, - ops::{blake2f, Error}, - types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus}, + blake2f, zkvm_blake2f, ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus, }; /// EIP-152 vectors 4-7 share the same h, m and t inputs. @@ -20,10 +20,22 @@ fn m() -> ZkvmBlake2fMessage { m } +fn state_words(bytes: &[u8; 64]) -> [u64; 8] { + core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) +} + +fn message_words(bytes: &[u8; 128]) -> [u64; 16] { + core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) +} + +fn offset_words(bytes: &[u8; 16]) -> [u64; 2] { + core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) +} + fn check(rounds: u32, f: u8, expected: [u8; 64]) { - let mut h = ZkvmBlake2fState { data: H }; - blake2f(rounds, &mut h, &m(), &ZkvmBlake2fOffset { data: T }, f).unwrap(); - assert_eq!(h.data, expected, "rounds={rounds}, f={f}"); + let mut h = state_words(&H); + blake2f(rounds, &mut h, &message_words(&m().data), &offset_words(&T), f == 1); + assert_eq!(h, state_words(&expected), "rounds={rounds}, f={f}"); } #[test] @@ -74,15 +86,6 @@ fn blake2f_eip152_vector_7_one_round() { ); } -#[test] -fn blake2f_invalid_final_flag() { - let mut h = ZkvmBlake2fState { data: H }; - let result = blake2f(12, &mut h, &m(), &ZkvmBlake2fOffset { data: T }, 2); - assert_eq!(result, Err(Error::InvalidFinalFlag)); - // The state must be untouched on failure. - assert_eq!(h.data, H); -} - #[test] fn zkvm_blake2f_smoke() { let mut h = ZkvmBlake2fState { data: H }; diff --git a/crates/accelerators/tests/conformance/bls12_381.rs b/crates/accelerators/tests/bls12_381.rs similarity index 70% rename from crates/accelerators/tests/conformance/bls12_381.rs rename to crates/accelerators/tests/bls12_381.rs index 87e3aabd3..14cfab5dd 100644 --- a/crates/accelerators/tests/conformance/bls12_381.rs +++ b/crates/accelerators/tests/bls12_381.rs @@ -1,20 +1,17 @@ //! BLS12-381 add/MSM/pairing/map conformance vectors. +#![cfg(feature = "ffi")] + +use core::convert::Infallible; + 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_map_fp2_to_g2, zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, - }, - ops::{ - bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, Error, - }, - types::{ - ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, - ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBls12381Scalar, - ZkvmStatus, - }, + bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, zkvm_bls12_g1_add, + zkvm_bls12_g1_msm, zkvm_bls12_g2_add, zkvm_bls12_g2_msm, zkvm_bls12_map_fp2_to_g2, + zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, BlsG1, BlsG2, Error, ZkvmBls12381Fp, + ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, + ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBls12381Scalar, ZkvmStatus, }; fn scalar(value: u8) -> ZkvmBls12381Scalar { @@ -23,6 +20,23 @@ fn scalar(value: u8) -> ZkvmBls12381Scalar { scalar } +fn bls_g1(point: ZkvmBls12381G1Point) -> BlsG1 { + (point.data[..48].try_into().unwrap(), point.data[48..].try_into().unwrap()) +} + +fn bls_g2(point: ZkvmBls12381G2Point) -> BlsG2 { + ( + point.data[..48].try_into().unwrap(), + point.data[48..96].try_into().unwrap(), + point.data[96..144].try_into().unwrap(), + point.data[144..].try_into().unwrap(), + ) +} + +fn bls_fp2(input: [u8; 96]) -> ([u8; 48], [u8; 48]) { + (input[..48].try_into().unwrap(), input[48..].try_into().unwrap()) +} + /// BLS12-381 G1 generator (`x || y`). const BLS_G1_GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { data: hex!( @@ -65,61 +79,46 @@ const BLS_R_MINUS_1: ZkvmBls12381Scalar = ZkvmBls12381Scalar { }; 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 + let pairs = [Ok::<_, Infallible>((bls_g1(BLS_G1_GEN), BLS_R_MINUS_1.data))]; + ZkvmBls12381G1Point { data: bls12_381_g1_msm(pairs).unwrap() } } #[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]); + let output = bls12_381_g1_add(bls_g1(BLS_G1_GEN), bls_g1(BLS_G1_GEN)).unwrap(); + assert_eq!(output, BLS_G1_2GEN.data); + + let pairs = [Ok::<_, Infallible>((bls_g1(BLS_G1_GEN), scalar(2).data))]; + let output = bls12_381_g1_msm(pairs).unwrap(); + assert_eq!(output, BLS_G1_2GEN.data); + + let output = + bls12_381_g1_msm(core::iter::empty::>()).unwrap(); + assert_eq!(output, [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]); + let output = bls12_381_g2_add(bls_g2(BLS_G2_GEN), bls_g2(BLS_G2_GEN)).unwrap(); + assert_eq!(output, BLS_G2_2GEN.data); + + let pairs = [Ok::<_, Infallible>((bls_g2(BLS_G2_GEN), scalar(2).data))]; + let output = bls12_381_g2_msm(pairs).unwrap(); + assert_eq!(output, BLS_G2_2GEN.data); + + let output = + bls12_381_g2_msm(core::iter::empty::>()).unwrap(); + assert_eq!(output, [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); + let pairs = [(bls_g1(BLS_G1_GEN), bls_g2(BLS_G2_GEN)), (bls_g1(neg_g1), bls_g2(BLS_G2_GEN))]; - bls12_381_pairing_check(&[], &mut verified).unwrap(); - assert!(verified); + assert!(bls12_381_pairing_check(pairs).unwrap()); + assert!(!bls12_381_pairing_check(pairs[..1].iter().copied()).unwrap()); + assert!(bls12_381_pairing_check(core::iter::empty::<(BlsG1, BlsG2)>()).unwrap()); } #[test] @@ -169,18 +168,14 @@ fn zkvm_bls12_pairing_smoke() { 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()); + assert!(bls12_381_g1_add(bls_g1(off_curve_g1), bls_g1(BLS_G1_GEN)).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()); + assert!(bls12_381_g2_add(bls_g2(off_curve_g2), bls_g2(BLS_G2_GEN)).is_err()); - let pairs = [ZkvmBls12381PairingPair { g1: off_curve_g1, g2: BLS_G2_GEN }]; - let mut verified = true; - assert_eq!(bls12_381_pairing_check(&pairs, &mut verified), Err(Error::BlsG1PointNotOnCurve)); - assert!(!verified); + let pairs = [(bls_g1(off_curve_g1), bls_g2(BLS_G2_GEN))]; + assert_eq!(bls12_381_pairing_check(pairs), Err(Error::BlsG1PointNotOnCurve)); } #[test] @@ -292,18 +287,16 @@ const BLS_FP_MODULUS: [u8; 48] = #[test] fn bls12_map_fp_to_g1_vectors() { for (input, expected) in MAP_FP_TO_G1_VECTORS { - let mut output = ZkvmBls12381G1Point { data: [0xff; 96] }; - bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: input }, &mut output).unwrap(); - assert_eq!(output.data, expected, "input={input:?}"); + let output = bls12_381_map_fp_to_g1(&input).unwrap(); + assert_eq!(output, expected, "input={input:?}"); } } #[test] fn bls12_map_fp2_to_g2_vectors() { for (input, expected) in MAP_FP2_TO_G2_VECTORS { - let mut output = ZkvmBls12381G2Point { data: [0xff; 192] }; - bls12_381_map_fp2_to_g2(&ZkvmBls12381Fp2 { data: input }, &mut output).unwrap(); - assert_eq!(output.data, expected, "input={input:?}"); + let output = bls12_381_map_fp2_to_g2(bls_fp2(input)).unwrap(); + assert_eq!(output, expected, "input={input:?}"); } } @@ -314,47 +307,31 @@ fn bls12_map_fp2_to_g2_vectors() { /// return the point itself. #[test] fn bls12_map_lands_in_prime_order_subgroup() { - let mut mapped = ZkvmBls12381G1Point { data: [0; 96] }; - bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: MAP_FP_TO_G1_VECTORS[0].0 }, &mut mapped) - .unwrap(); - let mut output = ZkvmBls12381G1Point { data: [0; 96] }; - bls12_381_g1_msm(&[ZkvmBls12381G1MsmPair { point: mapped, scalar: scalar(1) }], &mut output) - .expect("mapped G1 point must be in the prime-order subgroup"); - assert_eq!(output.data, mapped.data); - - let mut mapped = ZkvmBls12381G2Point { data: [0; 192] }; - bls12_381_map_fp2_to_g2(&ZkvmBls12381Fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }, &mut mapped) - .unwrap(); - let mut output = ZkvmBls12381G2Point { data: [0; 192] }; - bls12_381_g2_msm(&[ZkvmBls12381G2MsmPair { point: mapped, scalar: scalar(1) }], &mut output) - .expect("mapped G2 point must be in the prime-order subgroup"); - assert_eq!(output.data, mapped.data); + let mapped = bls12_381_map_fp_to_g1(&MAP_FP_TO_G1_VECTORS[0].0).unwrap(); + let pairs = + [Ok::<_, Infallible>((bls_g1(ZkvmBls12381G1Point { data: mapped }), scalar(1).data))]; + let output = + bls12_381_g1_msm(pairs).expect("mapped G1 point must be in the prime-order subgroup"); + assert_eq!(output, mapped); + + let mapped = bls12_381_map_fp2_to_g2(bls_fp2(MAP_FP2_TO_G2_VECTORS[0].0)).unwrap(); + let pairs = + [Ok::<_, Infallible>((bls_g2(ZkvmBls12381G2Point { data: mapped }), scalar(1).data))]; + let output = + bls12_381_g2_msm(pairs).expect("mapped G2 point must be in the prime-order subgroup"); + assert_eq!(output, mapped); } #[test] fn bls12_map_field_element_range() { - let mut g1 = ZkvmBls12381G1Point { data: [0; 96] }; - let mut g2 = ZkvmBls12381G2Point { data: [0; 192] }; - // The largest canonical element is accepted, the modulus itself is not. - assert_eq!(bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: BLS_FP_MAX }, &mut g1), Ok(())); - assert_eq!( - bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: BLS_FP_MODULUS }, &mut g1), - Err(Error::FieldElementInvalid) - ); - assert_eq!( - bls12_381_map_fp_to_g1(&ZkvmBls12381Fp { data: [0xff; 48] }, &mut g1), - Err(Error::FieldElementInvalid) - ); + assert!(bls12_381_map_fp_to_g1(&BLS_FP_MAX).is_ok()); + assert_eq!(bls12_381_map_fp_to_g1(&BLS_FP_MODULUS), Err(Error::FieldElementInvalid)); + assert_eq!(bls12_381_map_fp_to_g1(&[0xff; 48]), Err(Error::FieldElementInvalid)); // Either half of an Fp2 input is checked. - let mut c0_bad = ZkvmBls12381Fp2 { data: [0; 96] }; - c0_bad.data[..48].copy_from_slice(&BLS_FP_MODULUS); - assert_eq!(bls12_381_map_fp2_to_g2(&c0_bad, &mut g2), Err(Error::FieldElementInvalid)); - - let mut c1_bad = ZkvmBls12381Fp2 { data: [0; 96] }; - c1_bad.data[48..].copy_from_slice(&BLS_FP_MODULUS); - assert_eq!(bls12_381_map_fp2_to_g2(&c1_bad, &mut g2), Err(Error::FieldElementInvalid)); + assert_eq!(bls12_381_map_fp2_to_g2((BLS_FP_MODULUS, [0; 48])), Err(Error::FieldElementInvalid)); + assert_eq!(bls12_381_map_fp2_to_g2(([0; 48], BLS_FP_MODULUS)), Err(Error::FieldElementInvalid)); } #[test] diff --git a/crates/accelerators/tests/conformance/bn254.rs b/crates/accelerators/tests/bn254.rs similarity index 81% rename from crates/accelerators/tests/conformance/bn254.rs rename to crates/accelerators/tests/bn254.rs index dcbce7b16..c5a77666d 100644 --- a/crates/accelerators/tests/conformance/bn254.rs +++ b/crates/accelerators/tests/bn254.rs @@ -1,12 +1,12 @@ //! BN254 add/mul/pairing conformance vectors. +#![cfg(feature = "ffi")] + 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, - }, + bn254_g1_add, bn254_g1_mul, bn254_pairing_check, zkvm_bn254_g1_add, zkvm_bn254_g1_mul, + zkvm_bn254_pairing, Error, ZkvmBn254G1Point, ZkvmBn254G2Point, ZkvmBn254PairingPair, + ZkvmBn254Scalar, ZkvmStatus, }; fn scalar(value: u8) -> ZkvmBn254Scalar { @@ -52,14 +52,8 @@ const BN254_G2_GEN: ZkvmBn254G2Point = ZkvmBn254G2Point { #[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); + assert_eq!(bn254_g1_add(&point.data, &point.data).unwrap(), BN254_2GEN.data); + assert_eq!(bn254_g1_mul(&point.data, &scalar(2).data).unwrap(), BN254_2GEN.data); } #[test] @@ -68,16 +62,11 @@ fn bn254_pairing_vectors() { 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); + let raw = pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())); + assert!(bn254_pairing_check(raw).unwrap()); + assert!(!bn254_pairing_check([(pairs[0].g1.data.as_slice(), pairs[0].g2.data.as_slice(),)]) + .unwrap()); + assert!(bn254_pairing_check(core::iter::empty()).unwrap()); } #[test] @@ -118,15 +107,18 @@ fn bn254_rejects_invalid_point() { 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)); + assert_eq!(bn254_g1_add(¬_on_curve.data, &generator().data), 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); + assert_eq!( + bn254_pairing_check( + pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())) + ), + Err(Error::PointNotOnCurve) + ); } #[test] diff --git a/crates/accelerators/tests/conformance/main.rs b/crates/accelerators/tests/conformance/main.rs deleted file mode 100644 index 0b6034a30..000000000 --- a/crates/accelerators/tests/conformance/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Conformance tests for the accelerator operations, using official test -//! vectors and reference implementations. -//! -//! Modules mirror the `src/ops` layout: one file per domain. - -mod blake2; -mod bls12_381; -mod bn254; -mod ecdsa; -mod hash; -mod kzg; -mod modexp; diff --git a/crates/accelerators/tests/conformance/ecdsa.rs b/crates/accelerators/tests/ecdsa.rs similarity index 66% rename from crates/accelerators/tests/conformance/ecdsa.rs rename to crates/accelerators/tests/ecdsa.rs index e4710250d..3ba682cd8 100644 --- a/crates/accelerators/tests/conformance/ecdsa.rs +++ b/crates/accelerators/tests/ecdsa.rs @@ -2,14 +2,14 @@ //! //! Tested with vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors. +#![cfg(feature = "ffi")] + use hex_literal::hex; use openvm_accelerators::{ - ffi::{zkvm_secp256k1_ecrecover, zkvm_secp256k1_verify, zkvm_secp256r1_verify}, - ops::{keccak256, secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, Error}, - types::{ - ZkvmKeccak256Hash, ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, - ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus, - }, + keccak256, secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, zkvm_secp256k1_ecrecover, + zkvm_secp256k1_verify, zkvm_secp256r1_verify, Error, ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, + ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, + ZkvmStatus, }; /// Splits a 160-byte P256VERIFY input (msg || sig || pk) into its parts. @@ -32,35 +32,21 @@ const VALID: [u8; 160] = hex!( #[test] fn secp256r1_verify_vectors() { let (msg, sig, pubkey) = parts(&VALID); - let mut verified = false; - - secp256r1_verify(&msg, &sig, &pubkey, &mut verified).unwrap(); - assert!(verified); + assert!(secp256r1_verify(&msg.data, &sig.data, &pubkey.data)); - // Wrong message must not verify; `verified` must be overwritten. let mut wrong_msg = msg; wrong_msg.data[0] = 0x3c; - secp256r1_verify(&wrong_msg, &sig, &pubkey, &mut verified).unwrap(); - assert!(!verified); + assert!(!secp256r1_verify(&wrong_msg.data, &sig.data, &pubkey.data)); } #[test] fn secp256r1_verify_malformed_inputs() { let (msg, sig, _) = parts(&VALID); - let mut verified = true; - - // A signature with out-of-range values cannot be parsed. let bad_sig = ZkvmSecp256r1Signature { data: [0xff; 64] }; - let result = secp256r1_verify(&msg, &bad_sig, &parts(&VALID).2, &mut verified); - assert_eq!(result, Err(Error::InvalidSignature)); - assert!(!verified); + assert!(!secp256r1_verify(&msg.data, &bad_sig.data, &parts(&VALID).2.data)); - // A public key that is not on the curve cannot be parsed. - verified = true; let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; - let result = secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified); - assert_eq!(result, Err(Error::PointNotOnCurve)); - assert!(!verified); + assert!(!secp256r1_verify(&msg.data, &sig.data, &bad_pubkey.data)); } #[test] @@ -110,51 +96,36 @@ const K1_ADDRESS: [u8; 20] = hex!("7156526fbd7a3c72969b54f64e42c10fbb768c8a"); #[test] fn secp256k1_ecrecover_vector() { - let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; - secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, &mut pubkey).unwrap(); + let pubkey = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 1).unwrap(); // The Ethereum address is keccak(pubkey)[12..], derived here exactly as // a caller of the interface would. - let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; - keccak256(&pubkey.data, &mut hash); - assert_eq!(hash.data[12..], K1_ADDRESS); + assert_eq!(keccak256(&pubkey)[12..], K1_ADDRESS); } #[test] fn secp256k1_ecrecover_invalid_inputs() { - let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; - // Recovery ids above 3 are invalid. - let result = secp256k1_ecrecover(&K1_MSG, &K1_SIG, 4, &mut pubkey); + let result = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 4); assert_eq!(result, Err(Error::InvalidSignature)); // The zero signature cannot be parsed. let zero_sig = ZkvmSecp256k1Signature { data: [0; 64] }; - let result = secp256k1_ecrecover(&K1_MSG, &zero_sig, 0, &mut pubkey); + let result = secp256k1_ecrecover(&K1_MSG.data, &zero_sig.data, 0); assert_eq!(result, Err(Error::InvalidSignature)); } #[test] fn secp256k1_verify_roundtrip() { - let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; - secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, &mut pubkey).unwrap(); + let pubkey = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 1).unwrap(); + assert!(secp256k1_verify(&K1_MSG.data, &K1_SIG.data, &pubkey)); - let mut verified = false; - secp256k1_verify(&K1_MSG, &K1_SIG, &pubkey, &mut verified).unwrap(); - assert!(verified); - - // Wrong message must not verify; `verified` must be overwritten. let mut wrong_msg = K1_MSG; wrong_msg.data[0] ^= 1; - secp256k1_verify(&wrong_msg, &K1_SIG, &pubkey, &mut verified).unwrap(); - assert!(!verified); + assert!(!secp256k1_verify(&wrong_msg.data, &K1_SIG.data, &pubkey)); - // A public key that is not on the curve cannot be parsed. - verified = true; let bad_pubkey = ZkvmSecp256k1Pubkey { data: [0xff; 64] }; - let result = secp256k1_verify(&K1_MSG, &K1_SIG, &bad_pubkey, &mut verified); - assert_eq!(result, Err(Error::PointNotOnCurve)); - assert!(!verified); + assert!(!secp256k1_verify(&K1_MSG.data, &K1_SIG.data, &bad_pubkey.data)); } #[test] diff --git a/crates/accelerators/tests/conformance/hash.rs b/crates/accelerators/tests/hash.rs similarity index 84% rename from crates/accelerators/tests/conformance/hash.rs rename to crates/accelerators/tests/hash.rs index 339bc3f79..7c909bd4a 100644 --- a/crates/accelerators/tests/conformance/hash.rs +++ b/crates/accelerators/tests/hash.rs @@ -1,25 +1,22 @@ +#![cfg(feature = "ffi")] + //! Hash conformance vectors. use hex_literal::hex; use openvm_accelerators::{ - ffi::{zkvm_keccak256, zkvm_ripemd160, zkvm_sha256}, - ops::{keccak256, ripemd160, sha256}, - types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus}, + keccak256, ripemd160, sha256, zkvm_keccak256, zkvm_ripemd160, zkvm_sha256, ZkvmKeccak256Hash, + ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus, }; #[test] fn keccak256_vectors() { - let mut output = ZkvmKeccak256Hash { data: [0; 32] }; - - keccak256(b"", &mut output); assert_eq!( - output.data, + keccak256(b""), hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") ); - keccak256(b"abc", &mut output); assert_eq!( - output.data, + keccak256(b"abc"), hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") ); } @@ -58,17 +55,13 @@ fn zkvm_keccak256_null_pointers() { #[test] fn sha256_vectors() { - let mut output = ZkvmSha256Hash { data: [0; 32] }; - - sha256(b"", &mut output); assert_eq!( - output.data, + sha256(b""), hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") ); - sha256(b"abc", &mut output); assert_eq!( - output.data, + sha256(b"abc"), hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") ); } @@ -107,18 +100,13 @@ fn zkvm_sha256_null_pointers() { #[test] fn ripemd160_vectors() { - // Start from a dirty buffer to check the 12-byte zero padding is written. - let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; - - ripemd160(b"", &mut output); assert_eq!( - output.data, + ripemd160(b""), hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") ); - ripemd160(b"abc", &mut output); assert_eq!( - output.data, + ripemd160(b"abc"), hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") ); } diff --git a/crates/accelerators/tests/conformance/kzg.rs b/crates/accelerators/tests/kzg.rs similarity index 81% rename from crates/accelerators/tests/conformance/kzg.rs rename to crates/accelerators/tests/kzg.rs index 0c425139a..9505446c6 100644 --- a/crates/accelerators/tests/conformance/kzg.rs +++ b/crates/accelerators/tests/kzg.rs @@ -1,10 +1,11 @@ //! KZG point-evaluation conformance using the point-at-infinity commitment, //! which commits to the zero polynomial (p(z) = 0 for every z). +#![cfg(feature = "ffi")] + use openvm_accelerators::{ - ffi::zkvm_kzg_point_eval, - ops::{kzg_point_eval, Error}, - types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmStatus}, + kzg_point_eval, zkvm_kzg_point_eval, Error, ZkvmKzgCommitment, ZkvmKzgFieldElement, + ZkvmKzgProof, ZkvmStatus, }; /// The compressed point at infinity: 0xc0 followed by zeros. @@ -25,37 +26,28 @@ fn kzg_point_eval_infinity_commitment() { let commitment = infinity(); let proof: ZkvmKzgProof = infinity(); let z = scalar(2); - let mut verified = false; - // The zero polynomial evaluates to 0 at every z; the infinity proof // attests it. - kzg_point_eval(&commitment, &z, &scalar(0), &proof, &mut verified).unwrap(); - assert!(verified); + assert!(kzg_point_eval(&commitment.data, &z.data, &scalar(0).data, &proof.data).unwrap()); // Claiming y = 1 for the zero polynomial must not verify. - kzg_point_eval(&commitment, &z, &scalar(1), &proof, &mut verified).unwrap(); - assert!(!verified); + assert!(!kzg_point_eval(&commitment.data, &z.data, &scalar(1).data, &proof.data).unwrap()); } #[test] fn kzg_point_eval_malformed_inputs() { let z = scalar(2); let y = scalar(0); - let mut verified = true; - // Not a valid compressed-point prefix. let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; garbage.data[0] = 0x01; - let result = kzg_point_eval(&garbage, &z, &y, &infinity(), &mut verified); + let result = kzg_point_eval(&garbage.data, &z.data, &y.data, &infinity().data); assert_eq!(result, Err(Error::KzgInvalidInput)); - assert!(!verified); // An out-of-range evaluation point (>= the BLS scalar field order). let big_z = ZkvmKzgFieldElement { data: [0xff; 32] }; - verified = true; - let result = kzg_point_eval(&infinity(), &big_z, &y, &infinity(), &mut verified); + let result = kzg_point_eval(&infinity().data, &big_z.data, &y.data, &infinity().data); assert_eq!(result, Err(Error::KzgInvalidInput)); - assert!(!verified); } #[test] diff --git a/crates/accelerators/tests/conformance/modexp.rs b/crates/accelerators/tests/modexp.rs similarity index 83% rename from crates/accelerators/tests/conformance/modexp.rs rename to crates/accelerators/tests/modexp.rs index d72f12ce0..b97baa3f1 100644 --- a/crates/accelerators/tests/conformance/modexp.rs +++ b/crates/accelerators/tests/modexp.rs @@ -1,7 +1,9 @@ //! Modexp conformance vectors. +#![cfg(feature = "ffi")] + use hex_literal::hex; -use openvm_accelerators::{ffi::zkvm_modexp, ops::modexp, types::ZkvmStatus}; +use openvm_accelerators::{modexp, zkvm_modexp, ZkvmStatus}; /// BN254 Fr (the scalar field) modulus, big-endian. Not to be confused with /// the base field prime, which shares the leading bytes. @@ -10,25 +12,19 @@ const BN254_FR: [u8; 32] = hex!("30644e72e131a029b85045b68181585d2833e84879b9709 #[test] fn modexp_small() { // 3^5 mod 7 = 5 - let mut output = [0xffu8; 1]; - modexp(&[3], &[5], &[7], &mut output); - assert_eq!(output, [5]); + assert_eq!(modexp(&[3], &[5], &[7]), [5]); // Output is left-padded to the modulus length. - let mut output = [0xffu8; 2]; - modexp(&[3], &[5], &[0, 7], &mut output); - assert_eq!(output, [0, 5]); + assert_eq!(modexp(&[3], &[5], &[0, 7]), [0, 5]); - // A zero-length modulus writes nothing. - modexp(&[3], &[5], &[], &mut []); + assert!(modexp(&[3], &[5], &[]).is_empty()); } #[test] fn modexp_matches_reference() { // The BN254-Fr accelerated path, compared right-aligned against the // aurora reference. - let mut output = [0xa5; 32]; - modexp(&[0xab; 32], &[0x07], &BN254_FR, &mut output); + let output = modexp(&[0xab; 32], &[0x07], &BN254_FR); let reference = aurora_engine_modexp::modexp(&[0xab; 32], &[0x07], &BN254_FR); let mut expected = [0; 32]; expected[32 - reference.len()..].copy_from_slice(&reference); @@ -36,8 +32,7 @@ fn modexp_matches_reference() { // The generic path with a non-special modulus. let modulus = [0xef; 24]; - let mut output = [0xa5; 24]; - modexp(&[0x12; 40], &[0x34; 3], &modulus, &mut output); + let output = modexp(&[0x12; 40], &[0x34; 3], &modulus); let reference = aurora_engine_modexp::modexp(&[0x12; 40], &[0x34; 3], &modulus); let mut expected = [0; 24]; expected[24 - reference.len()..].copy_from_slice(&reference); diff --git a/crates/revm-crypto/src/lib.rs b/crates/revm-crypto/src/lib.rs index 28abb25b9..51df00da9 100644 --- a/crates/revm-crypto/src/lib.rs +++ b/crates/revm-crypto/src/lib.rs @@ -15,15 +15,10 @@ use alloy_consensus::crypto::{ }; use alloy_primitives::Address; use openvm_accelerators::{ - ops::{self, Error, StreamError}, - types::{ - ZkvmBls12381Fp, ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, - ZkvmBls12381G2MsmPair, ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBn254G1Point, - ZkvmBn254G2Point, ZkvmBn254PairingPair, ZkvmBn254Scalar, ZkvmBytes32, ZkvmKeccak256Hash, - ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmRipemd160Hash, ZkvmSecp256k1Hash, - ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, - ZkvmSecp256r1Signature, ZkvmSha256Hash, - }, + blake2f, bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, + bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, bn254_g1_add, + bn254_g1_mul, bn254_pairing_check, keccak256, kzg_point_eval, modexp, ripemd160, + secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, sha256, Error, StreamError, }; use revm::{ install_crypto, @@ -48,14 +43,9 @@ impl CryptoProvider for OpenVmK256Provider { sig: &[u8; 65], msg: &[u8; 32], ) -> Result { - let recovery_id = sig[64]; - let msg = ZkvmSecp256k1Hash { data: *msg }; - let sig = ZkvmSecp256k1Signature { data: sig[..64].try_into().unwrap() }; - let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; - ops::secp256k1_ecrecover(&msg, &sig, recovery_id, &mut pubkey) + let pubkey = secp256k1_ecrecover(msg, sig[..64].try_into().unwrap(), sig[64]) .map_err(|_| RecoveryError::new())?; - - Ok(address_from_pubkey(&pubkey.data)) + Ok(address_from_pubkey(&pubkey)) } fn verify_and_compute_signer_unchecked( @@ -68,25 +58,17 @@ impl CryptoProvider for OpenVmK256Provider { return Err(RecoveryError::new()); } - let msg = ZkvmSecp256k1Hash { data: *msg }; - let sig = ZkvmSecp256k1Signature { data: *sig }; - let pubkey = ZkvmSecp256k1Pubkey { data: pubkey[1..].try_into().unwrap() }; - let mut verified = false; - ops::secp256k1_verify(&msg, &sig, &pubkey, &mut verified) - .map_err(|_| RecoveryError::new())?; - if !verified { + let pubkey: &[u8; 64] = pubkey[1..].try_into().unwrap(); + if !secp256k1_verify(msg, sig, pubkey) { return Err(RecoveryError::new()); } - Ok(address_from_pubkey(&pubkey.data)) + Ok(address_from_pubkey(pubkey)) } } -// Kept separate so both Alloy provider methods use exactly the standard-interface hash path. fn address_from_pubkey(pubkey: &[u8; 64]) -> Address { - let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; - ops::keccak256(pubkey, &mut hash); - Address::from_slice(&hash.data[12..]) + Address::from_slice(&keccak256(pubkey)[12..]) } #[derive(Debug, Default)] @@ -94,51 +76,23 @@ struct OpenVmCrypto; impl Crypto for OpenVmCrypto { fn sha256(&self, input: &[u8]) -> [u8; 32] { - let mut output = ZkvmSha256Hash { data: [0; 32] }; - ops::sha256(input, &mut output); - output.data + sha256(input) } fn ripemd160(&self, input: &[u8]) -> [u8; 32] { - let mut output = ZkvmRipemd160Hash { data: [0; 32] }; - ops::ripemd160(input, &mut output); - output.data + ripemd160(input) } fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> { - let p1 = - ZkvmBn254G1Point { data: p1.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)? }; - let p2 = - ZkvmBn254G1Point { data: p2.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)? }; - let mut output = ZkvmBn254G1Point { data: [0; 64] }; - ops::bn254_g1_add(&p1, &p2, &mut output).map_err(map_bn_error)?; - Ok(output.data) + bn254_g1_add(p1, p2).map_err(map_bn_error) } fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> { - let point = ZkvmBn254G1Point { - data: point.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, - }; - let scalar = ZkvmBn254Scalar { - data: scalar.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, - }; - let mut output = ZkvmBn254G1Point { data: [0; 64] }; - ops::bn254_g1_mul(&point, &scalar, &mut output).map_err(map_bn_error)?; - Ok(output.data) + bn254_g1_mul(point, scalar).map_err(map_bn_error) } fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result { - let pairs = pairs.iter().map(|(g1, g2)| { - Ok(ZkvmBn254PairingPair { - g1: ZkvmBn254G1Point { - data: (*g1).try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, - }, - g2: ZkvmBn254G2Point { - data: (*g2).try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, - }, - }) - }); - ops::bn254_pairing_check_iter(pairs).map_err(|error| map_stream_error(error, map_bn_error)) + bn254_pairing_check(pairs.iter().copied()).map_err(map_bn_error) } fn secp256k1_ecrecover( @@ -147,32 +101,24 @@ impl Crypto for OpenVmCrypto { recid: u8, msg: &[u8; 32], ) -> Result<[u8; 32], PrecompileHalt> { - let msg = ZkvmSecp256k1Hash { data: *msg }; - let sig = ZkvmSecp256k1Signature { data: *sig }; - let mut pubkey = ZkvmSecp256k1Pubkey { data: [0; 64] }; - ops::secp256k1_ecrecover(&msg, &sig, recid, &mut pubkey) + let pubkey = secp256k1_ecrecover(msg, sig, recid) .map_err(|_| PrecompileHalt::Secp256k1RecoverFailed)?; - let mut hash = ZkvmKeccak256Hash { data: [0; 32] }; - ops::keccak256(&pubkey.data, &mut hash); - hash.data[..12].fill(0); - Ok(hash.data) + let mut hash = keccak256(&pubkey); + hash[..12].fill(0); + Ok(hash) } fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileHalt> { - Ok(ops::modexp_result(base, exp, modulus)) + Ok(modexp(base, exp, modulus)) } fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { - ops::blake2f_words(rounds, h, m, t, f); + blake2f(rounds, h, m, t, f); } fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool { - let msg = ZkvmSecp256r1Hash { data: *msg }; - let sig = ZkvmSecp256r1Signature { data: *sig }; - let pubkey = ZkvmSecp256r1Pubkey { data: *pk }; - let mut verified = false; - ops::secp256r1_verify(&msg, &sig, &pubkey, &mut verified).is_ok() && verified + secp256r1_verify(msg, sig, pk) } fn verify_kzg_proof( @@ -182,12 +128,7 @@ impl Crypto for OpenVmCrypto { commitment: &[u8; 48], proof: &[u8; 48], ) -> Result<(), PrecompileHalt> { - let commitment = ZkvmKzgCommitment { data: *commitment }; - let z = ZkvmKzgFieldElement { data: *z }; - let y = ZkvmKzgFieldElement { data: *y }; - let proof = ZkvmKzgProof { data: *proof }; - let mut verified = false; - ops::kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) + let verified = kzg_point_eval(commitment, z, y, proof) .map_err(|_| PrecompileHalt::BlobVerifyKzgProofFailed)?; if verified { Ok(()) @@ -201,24 +142,14 @@ impl Crypto for OpenVmCrypto { a: BlsG1Point, b: BlsG1Point, ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - let a = bls_g1(a); - let b = bls_g1(b); - let mut output = ZkvmBls12381G1Point { data: [0; BLS_G1_LEN] }; - ops::bls12_381_g1_add(&a, &b, &mut output).map_err(map_bls_g1_error)?; - Ok(output.data) + bls12_381_g1_add(a, b).map_err(map_bls_g1_error) } fn bls12_381_g1_msm( &self, pairs: &mut dyn Iterator>, ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - let pairs = pairs.map(|pair| { - let (point, scalar) = pair?; - Ok(ZkvmBls12381G1MsmPair { point: bls_g1(point), scalar: ZkvmBytes32 { data: scalar } }) - }); - ops::bls12_381_g1_msm_iter(pairs) - .map(|output| output.data) - .map_err(|error| map_stream_error(error, map_bls_g1_error)) + bls12_381_g1_msm(pairs).map_err(|error| map_stream_error(error, map_bls_g1_error)) } fn bls12_381_g2_add( @@ -226,76 +157,38 @@ impl Crypto for OpenVmCrypto { a: BlsG2Point, b: BlsG2Point, ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - let a = bls_g2(a); - let b = bls_g2(b); - let mut output = ZkvmBls12381G2Point { data: [0; BLS_G2_LEN] }; - ops::bls12_381_g2_add(&a, &b, &mut output).map_err(map_bls_g2_error)?; - Ok(output.data) + bls12_381_g2_add(a, b).map_err(map_bls_g2_error) } fn bls12_381_g2_msm( &self, pairs: &mut dyn Iterator>, ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - let pairs = pairs.map(|pair| { - let (point, scalar) = pair?; - Ok(ZkvmBls12381G2MsmPair { point: bls_g2(point), scalar: ZkvmBytes32 { data: scalar } }) - }); - ops::bls12_381_g2_msm_iter(pairs) - .map(|output| output.data) - .map_err(|error| map_stream_error(error, map_bls_g2_error)) + bls12_381_g2_msm(pairs).map_err(|error| map_stream_error(error, map_bls_g2_error)) } fn bls12_381_pairing_check( &self, pairs: &[(BlsG1Point, BlsG2Point)], ) -> Result { - let pairs = pairs - .iter() - .copied() - .map(|(g1, g2)| ZkvmBls12381PairingPair { g1: bls_g1(g1), g2: bls_g2(g2) }); - ops::bls12_381_pairing_check_iter(pairs).map_err(map_bls_pairing_error) + bls12_381_pairing_check(pairs.iter().copied()).map_err(map_bls_pairing_error) } fn bls12_381_fp_to_g1( &self, fp: &[u8; BLS_FP_LEN], ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - let fp = ZkvmBls12381Fp { data: *fp }; - let mut output = ZkvmBls12381G1Point { data: [0; BLS_G1_LEN] }; - ops::bls12_381_map_fp_to_g1(&fp, &mut output).map_err(map_bls_field_error)?; - Ok(output.data) + bls12_381_map_fp_to_g1(fp).map_err(map_bls_field_error) } fn bls12_381_fp2_to_g2( &self, fp2: ([u8; BLS_FP_LEN], [u8; BLS_FP_LEN]), ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - let mut data = [0; BLS_FP_LEN * 2]; - data[..BLS_FP_LEN].copy_from_slice(&fp2.0); - data[BLS_FP_LEN..].copy_from_slice(&fp2.1); - let fp2 = ZkvmBls12381Fp2 { data }; - let mut output = ZkvmBls12381G2Point { data: [0; BLS_G2_LEN] }; - ops::bls12_381_map_fp2_to_g2(&fp2, &mut output).map_err(map_bls_field_error)?; - Ok(output.data) + bls12_381_map_fp2_to_g2(fp2).map_err(map_bls_field_error) } } -fn bls_g1((x, y): BlsG1Point) -> ZkvmBls12381G1Point { - let mut data = [0; BLS_G1_LEN]; - data[..BLS_FP_LEN].copy_from_slice(&x); - data[BLS_FP_LEN..].copy_from_slice(&y); - ZkvmBls12381G1Point { data } -} - -fn bls_g2((x0, x1, y0, y1): BlsG2Point) -> ZkvmBls12381G2Point { - let mut data = [0; BLS_G2_LEN]; - for (output, coordinate) in data.chunks_exact_mut(BLS_FP_LEN).zip([x0, x1, y0, y1]) { - output.copy_from_slice(&coordinate); - } - ZkvmBls12381G2Point { data } -} - fn map_stream_error( error: StreamError, map_operation: fn(Error) -> PrecompileHalt, @@ -308,6 +201,7 @@ fn map_stream_error( fn map_bn_error(error: Error) -> PrecompileHalt { match error { + Error::InvalidLength => PrecompileHalt::Bn254PairLength, Error::FieldElementInvalid => PrecompileHalt::Bn254FieldPointNotAMember, Error::PointNotOnCurve | Error::PointNotInSubgroup => { PrecompileHalt::Bn254AffineGFailedToCreate @@ -435,24 +329,14 @@ mod tests { #[test] fn adapters_preserve_revm_error_variants() { let invalid_bn_point = [0xff; 64]; - assert_eq!( - OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]), - Err(PrecompileHalt::Bn254FieldPointNotAMember) - ); - assert_eq!( - OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]), - DefaultCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]) - ); + let actual = OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]); + assert_eq!(actual, Err(PrecompileHalt::Bn254FieldPointNotAMember)); + assert_eq!(actual, DefaultCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64])); let noncanonical_fp = [0xff; BLS_FP_LEN]; - assert_eq!( - OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp), - Err(PrecompileHalt::NonCanonicalFp) - ); - assert_eq!( - OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp), - DefaultCrypto.bls12_381_fp_to_g1(&noncanonical_fp) - ); + let actual = OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp); + assert_eq!(actual, Err(PrecompileHalt::NonCanonicalFp)); + assert_eq!(actual, DefaultCrypto.bls12_381_fp_to_g1(&noncanonical_fp)); assert_eq!( OpenVmCrypto.secp256k1_ecrecover(&[0; 64], 0, &[0; 32]), From c8b26b686a8b83699b1dd33a79e7ac1ada589bad Mon Sep 17 00:00:00 2001 From: Ayush Shukla Date: Wed, 12 Aug 2026 01:28:39 +0200 Subject: [PATCH 41/44] perf: inline BLS accelerator boundaries --- crates/accelerators/src/ffi/bls12_381.rs | 6 +++--- crates/accelerators/src/ops/bls12_381/map.rs | 4 +++- crates/accelerators/src/ops/bls12_381/mod.rs | 14 +++++++------ crates/accelerators/tests/bls12_381.rs | 22 +++++++++++++------- crates/revm-crypto/src/lib.rs | 6 +++--- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs index 489ecfd59..b7e78d1ad 100644 --- a/crates/accelerators/src/ffi/bls12_381.rs +++ b/crates/accelerators/src/ffi/bls12_381.rs @@ -28,7 +28,7 @@ pub unsafe extern "C" fn zkvm_bls12_g1_add( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - match ops::bls12_381_g1_add(bls_g1(p1.data), bls_g1(p2.data)) { + match ops::bls12_381_g1_add(&bls_g1(p1.data), &bls_g1(p2.data)) { Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. unsafe { result.write(ZkvmBls12381G1Point { data }) }; @@ -92,7 +92,7 @@ pub unsafe extern "C" fn zkvm_bls12_g2_add( } // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - match ops::bls12_381_g2_add(bls_g2(p1.data), bls_g2(p2.data)) { + match ops::bls12_381_g2_add(&bls_g2(p1.data), &bls_g2(p2.data)) { Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. unsafe { result.write(ZkvmBls12381G2Point { data }) }; @@ -238,7 +238,7 @@ pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( field_element.data[..48].try_into().unwrap(), field_element.data[48..].try_into().unwrap(), ); - match ops::bls12_381_map_fp2_to_g2(fp2) { + match ops::bls12_381_map_fp2_to_g2(&fp2) { Ok(data) => { // SAFETY: `result` is non-NULL and valid for writes. unsafe { result.write(ZkvmBls12381G2Point { data }) }; diff --git a/crates/accelerators/src/ops/bls12_381/map.rs b/crates/accelerators/src/ops/bls12_381/map.rs index c16f3bb73..5fba2b2dd 100644 --- a/crates/accelerators/src/ops/bls12_381/map.rs +++ b/crates/accelerators/src/ops/bls12_381/map.rs @@ -13,6 +13,7 @@ use super::BLS_FP_LEN; use crate::ops::Error; /// BLS12-381 map field element to G1 (precompile 0x10). +#[inline] pub fn bls12_381_map_fp_to_g1(fp: &[u8; 48]) -> Result<[u8; 96], Error> { let fp = read_fq(fp)?; let point = WBMap::map_to_curve(fp) @@ -23,7 +24,8 @@ pub fn bls12_381_map_fp_to_g1(fp: &[u8; 48]) -> Result<[u8; 96], Error> { } /// BLS12-381 map field element to G2 (precompile 0x11). -pub fn bls12_381_map_fp2_to_g2(fp2: ([u8; 48], [u8; 48])) -> Result<[u8; 192], Error> { +#[inline] +pub fn bls12_381_map_fp2_to_g2(fp2: &([u8; 48], [u8; 48])) -> Result<[u8; 192], Error> { let c0 = read_fq(&fp2.0)?; let c1 = read_fq(&fp2.1)?; let point = WBMap::map_to_curve(Fq2::new(c0, c1)) diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs index 68d21064f..dcd2cd905 100644 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ b/crates/accelerators/src/ops/bls12_381/mod.rs @@ -27,9 +27,10 @@ const BLS_FP_LEN: usize = 48; /// /// Per EIP-2537 G1ADD, inputs are validated on-curve only, not for subgroup /// membership. -pub fn bls12_381_g1_add(p1: BlsG1, p2: BlsG1) -> Result<[u8; 96], Error> { - let p1 = read_bls_g1_point_no_subgroup_check(&p1)?; - let p2 = read_bls_g1_point_no_subgroup_check(&p2)?; +#[inline] +pub fn bls12_381_g1_add(p1: &BlsG1, p2: &BlsG1) -> Result<[u8; 96], Error> { + let p1 = read_bls_g1_point_no_subgroup_check(p1)?; + let p2 = read_bls_g1_point_no_subgroup_check(p2)?; Ok(encode_bls_g1_point(&(p1 + p2))) } @@ -61,9 +62,10 @@ pub fn bls12_381_g1_msm( /// /// Per EIP-2537 G2ADD, inputs are validated on-curve only, not for subgroup /// membership. -pub fn bls12_381_g2_add(p1: BlsG2, p2: BlsG2) -> Result<[u8; 192], Error> { - let p1 = read_bls_g2_point_no_subgroup_check(&p1)?; - let p2 = read_bls_g2_point_no_subgroup_check(&p2)?; +#[inline] +pub fn bls12_381_g2_add(p1: &BlsG2, p2: &BlsG2) -> Result<[u8; 192], Error> { + let p1 = read_bls_g2_point_no_subgroup_check(p1)?; + let p2 = read_bls_g2_point_no_subgroup_check(p2)?; Ok(encode_bls_g2_point(&(p1 + p2))) } diff --git a/crates/accelerators/tests/bls12_381.rs b/crates/accelerators/tests/bls12_381.rs index 14cfab5dd..f78efe76c 100644 --- a/crates/accelerators/tests/bls12_381.rs +++ b/crates/accelerators/tests/bls12_381.rs @@ -85,7 +85,7 @@ fn neg_g1_generator() -> ZkvmBls12381G1Point { #[test] fn bls12_g1_add_msm_vectors() { - let output = bls12_381_g1_add(bls_g1(BLS_G1_GEN), bls_g1(BLS_G1_GEN)).unwrap(); + let output = bls12_381_g1_add(&bls_g1(BLS_G1_GEN), &bls_g1(BLS_G1_GEN)).unwrap(); assert_eq!(output, BLS_G1_2GEN.data); let pairs = [Ok::<_, Infallible>((bls_g1(BLS_G1_GEN), scalar(2).data))]; @@ -99,7 +99,7 @@ fn bls12_g1_add_msm_vectors() { #[test] fn bls12_g2_add_msm_vectors() { - let output = bls12_381_g2_add(bls_g2(BLS_G2_GEN), bls_g2(BLS_G2_GEN)).unwrap(); + let output = bls12_381_g2_add(&bls_g2(BLS_G2_GEN), &bls_g2(BLS_G2_GEN)).unwrap(); assert_eq!(output, BLS_G2_2GEN.data); let pairs = [Ok::<_, Infallible>((bls_g2(BLS_G2_GEN), scalar(2).data))]; @@ -168,11 +168,11 @@ fn zkvm_bls12_pairing_smoke() { fn bls12_rejects_invalid_points() { let mut off_curve_g1 = BLS_G1_GEN; off_curve_g1.data[95] ^= 1; - assert!(bls12_381_g1_add(bls_g1(off_curve_g1), bls_g1(BLS_G1_GEN)).is_err()); + assert!(bls12_381_g1_add(&bls_g1(off_curve_g1), &bls_g1(BLS_G1_GEN)).is_err()); let mut off_curve_g2 = BLS_G2_GEN; off_curve_g2.data[191] ^= 1; - assert!(bls12_381_g2_add(bls_g2(off_curve_g2), bls_g2(BLS_G2_GEN)).is_err()); + assert!(bls12_381_g2_add(&bls_g2(off_curve_g2), &bls_g2(BLS_G2_GEN)).is_err()); let pairs = [(bls_g1(off_curve_g1), bls_g2(BLS_G2_GEN))]; assert_eq!(bls12_381_pairing_check(pairs), Err(Error::BlsG1PointNotOnCurve)); @@ -295,7 +295,7 @@ fn bls12_map_fp_to_g1_vectors() { #[test] fn bls12_map_fp2_to_g2_vectors() { for (input, expected) in MAP_FP2_TO_G2_VECTORS { - let output = bls12_381_map_fp2_to_g2(bls_fp2(input)).unwrap(); + let output = bls12_381_map_fp2_to_g2(&bls_fp2(input)).unwrap(); assert_eq!(output, expected, "input={input:?}"); } } @@ -314,7 +314,7 @@ fn bls12_map_lands_in_prime_order_subgroup() { bls12_381_g1_msm(pairs).expect("mapped G1 point must be in the prime-order subgroup"); assert_eq!(output, mapped); - let mapped = bls12_381_map_fp2_to_g2(bls_fp2(MAP_FP2_TO_G2_VECTORS[0].0)).unwrap(); + let mapped = bls12_381_map_fp2_to_g2(&bls_fp2(MAP_FP2_TO_G2_VECTORS[0].0)).unwrap(); let pairs = [Ok::<_, Infallible>((bls_g2(ZkvmBls12381G2Point { data: mapped }), scalar(1).data))]; let output = @@ -330,8 +330,14 @@ fn bls12_map_field_element_range() { assert_eq!(bls12_381_map_fp_to_g1(&[0xff; 48]), Err(Error::FieldElementInvalid)); // Either half of an Fp2 input is checked. - assert_eq!(bls12_381_map_fp2_to_g2((BLS_FP_MODULUS, [0; 48])), Err(Error::FieldElementInvalid)); - assert_eq!(bls12_381_map_fp2_to_g2(([0; 48], BLS_FP_MODULUS)), Err(Error::FieldElementInvalid)); + assert_eq!( + bls12_381_map_fp2_to_g2(&(BLS_FP_MODULUS, [0; 48])), + Err(Error::FieldElementInvalid) + ); + assert_eq!( + bls12_381_map_fp2_to_g2(&([0; 48], BLS_FP_MODULUS)), + Err(Error::FieldElementInvalid) + ); } #[test] diff --git a/crates/revm-crypto/src/lib.rs b/crates/revm-crypto/src/lib.rs index 51df00da9..fe33a80ab 100644 --- a/crates/revm-crypto/src/lib.rs +++ b/crates/revm-crypto/src/lib.rs @@ -142,7 +142,7 @@ impl Crypto for OpenVmCrypto { a: BlsG1Point, b: BlsG1Point, ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - bls12_381_g1_add(a, b).map_err(map_bls_g1_error) + bls12_381_g1_add(&a, &b).map_err(map_bls_g1_error) } fn bls12_381_g1_msm( @@ -157,7 +157,7 @@ impl Crypto for OpenVmCrypto { a: BlsG2Point, b: BlsG2Point, ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - bls12_381_g2_add(a, b).map_err(map_bls_g2_error) + bls12_381_g2_add(&a, &b).map_err(map_bls_g2_error) } fn bls12_381_g2_msm( @@ -185,7 +185,7 @@ impl Crypto for OpenVmCrypto { &self, fp2: ([u8; BLS_FP_LEN], [u8; BLS_FP_LEN]), ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - bls12_381_map_fp2_to_g2(fp2).map_err(map_bls_field_error) + bls12_381_map_fp2_to_g2(&fp2).map_err(map_bls_field_error) } } From b3650e55b7e91ef636bf94d67a449cd40039b488 Mon Sep 17 00:00:00 2001 From: Ayush Shukla Date: Wed, 12 Aug 2026 11:05:11 +0200 Subject: [PATCH 42/44] refactor: expose zkVM accelerator C interface --- crates/accelerators/Cargo.toml | 3 +- crates/accelerators/src/blake2f.rs | 166 ++++++++ crates/accelerators/src/bls12_381/codec.rs | 90 ++++ crates/accelerators/src/bls12_381/map.rs | 64 +++ crates/accelerators/src/bls12_381/mod.rs | 290 +++++++++++++ crates/accelerators/src/bn254/codec.rs | 54 +++ crates/accelerators/src/bn254/mod.rs | 136 ++++++ crates/accelerators/src/error.rs | 8 + crates/accelerators/src/ffi/blake2.rs | 54 --- crates/accelerators/src/ffi/bls12_381.rs | 249 ----------- crates/accelerators/src/ffi/bn254.rs | 105 ----- crates/accelerators/src/ffi/ecdsa.rs | 102 ----- crates/accelerators/src/ffi/hash.rs | 91 ---- crates/accelerators/src/ffi/kzg.rs | 38 -- crates/accelerators/src/ffi/mod.rs | 20 - crates/accelerators/src/ffi/modexp.rs | 47 --- crates/accelerators/src/keccak256.rs | 31 ++ crates/accelerators/src/kzg.rs | 48 +++ crates/accelerators/src/lib.rs | 51 ++- crates/accelerators/src/modexp.rs | 139 +++++++ crates/accelerators/src/ops/blake2/mod.rs | 39 -- .../accelerators/src/ops/blake2/portable.rs | 83 ---- .../accelerators/src/ops/bls12_381/codec.rs | 102 ----- crates/accelerators/src/ops/bls12_381/map.rs | 78 ---- crates/accelerators/src/ops/bls12_381/mod.rs | 131 ------ crates/accelerators/src/ops/bn254/codec.rs | 79 ---- crates/accelerators/src/ops/bn254/mod.rs | 55 --- crates/accelerators/src/ops/ecdsa/mod.rs | 10 - .../accelerators/src/ops/ecdsa/secp256k1.rs | 60 --- .../accelerators/src/ops/ecdsa/secp256r1.rs | 18 - crates/accelerators/src/ops/hash.rs | 30 -- crates/accelerators/src/ops/kzg.rs | 29 -- crates/accelerators/src/ops/mod.rs | 62 --- crates/accelerators/src/ops/modexp.rs | 143 ------- crates/accelerators/src/ripemd160.rs | 42 ++ crates/accelerators/src/secp256k1.rs | 95 +++++ crates/accelerators/src/secp256r1.rs | 46 +++ crates/accelerators/src/sha256.rs | 33 ++ crates/accelerators/src/types.rs | 152 +------ .../tests/{blake2.rs => blake2f.rs} | 65 +-- crates/accelerators/tests/bls12_381.rs | 298 ++++++------- crates/accelerators/tests/bn254.rs | 73 ++-- crates/accelerators/tests/ecdsa.rs | 148 ------- crates/accelerators/tests/hash.rs | 144 ------- crates/accelerators/tests/keccak256.rs | 36 ++ crates/accelerators/tests/kzg.rs | 70 ++-- crates/accelerators/tests/modexp.rs | 55 ++- crates/accelerators/tests/ripemd160.rs | 36 ++ crates/accelerators/tests/secp256k1.rs | 103 +++++ crates/accelerators/tests/secp256r1.rs | 81 ++++ crates/accelerators/tests/sha256.rs | 36 ++ crates/revm-crypto/src/alloy.rs | 67 +++ crates/revm-crypto/src/lib.rs | 366 +--------------- crates/revm-crypto/src/revm.rs | 391 ++++++++++++++++++ 54 files changed, 2267 insertions(+), 2775 deletions(-) create mode 100644 crates/accelerators/src/blake2f.rs create mode 100644 crates/accelerators/src/bls12_381/codec.rs create mode 100644 crates/accelerators/src/bls12_381/map.rs create mode 100644 crates/accelerators/src/bls12_381/mod.rs create mode 100644 crates/accelerators/src/bn254/codec.rs create mode 100644 crates/accelerators/src/bn254/mod.rs create mode 100644 crates/accelerators/src/error.rs delete mode 100644 crates/accelerators/src/ffi/blake2.rs delete mode 100644 crates/accelerators/src/ffi/bls12_381.rs delete mode 100644 crates/accelerators/src/ffi/bn254.rs delete mode 100644 crates/accelerators/src/ffi/ecdsa.rs delete mode 100644 crates/accelerators/src/ffi/hash.rs delete mode 100644 crates/accelerators/src/ffi/kzg.rs delete mode 100644 crates/accelerators/src/ffi/mod.rs delete mode 100644 crates/accelerators/src/ffi/modexp.rs create mode 100644 crates/accelerators/src/keccak256.rs create mode 100644 crates/accelerators/src/kzg.rs create mode 100644 crates/accelerators/src/modexp.rs delete mode 100644 crates/accelerators/src/ops/blake2/mod.rs delete mode 100644 crates/accelerators/src/ops/blake2/portable.rs delete mode 100644 crates/accelerators/src/ops/bls12_381/codec.rs delete mode 100644 crates/accelerators/src/ops/bls12_381/map.rs delete mode 100644 crates/accelerators/src/ops/bls12_381/mod.rs delete mode 100644 crates/accelerators/src/ops/bn254/codec.rs delete mode 100644 crates/accelerators/src/ops/bn254/mod.rs delete mode 100644 crates/accelerators/src/ops/ecdsa/mod.rs delete mode 100644 crates/accelerators/src/ops/ecdsa/secp256k1.rs delete mode 100644 crates/accelerators/src/ops/ecdsa/secp256r1.rs delete mode 100644 crates/accelerators/src/ops/hash.rs delete mode 100644 crates/accelerators/src/ops/kzg.rs delete mode 100644 crates/accelerators/src/ops/mod.rs delete mode 100644 crates/accelerators/src/ops/modexp.rs create mode 100644 crates/accelerators/src/ripemd160.rs create mode 100644 crates/accelerators/src/secp256k1.rs create mode 100644 crates/accelerators/src/secp256r1.rs create mode 100644 crates/accelerators/src/sha256.rs rename crates/accelerators/tests/{blake2.rs => blake2f.rs} (54%) delete mode 100644 crates/accelerators/tests/ecdsa.rs delete mode 100644 crates/accelerators/tests/hash.rs create mode 100644 crates/accelerators/tests/keccak256.rs create mode 100644 crates/accelerators/tests/ripemd160.rs create mode 100644 crates/accelerators/tests/secp256k1.rs create mode 100644 crates/accelerators/tests/secp256r1.rs create mode 100644 crates/accelerators/tests/sha256.rs create mode 100644 crates/revm-crypto/src/alloy.rs create mode 100644 crates/revm-crypto/src/revm.rs diff --git a/crates/accelerators/Cargo.toml b/crates/accelerators/Cargo.toml index d70690b93..8479efdc2 100644 --- a/crates/accelerators/Cargo.toml +++ b/crates/accelerators/Cargo.toml @@ -52,6 +52,5 @@ ignored = ["openvm-pairing-guest"] ignored = ["openvm-pairing-guest"] [features] -default = ["ffi"] -ffi = [] +default = [] std = [] diff --git a/crates/accelerators/src/blake2f.rs b/crates/accelerators/src/blake2f.rs new file mode 100644 index 000000000..76689a19a --- /dev/null +++ b/crates/accelerators/src/blake2f.rs @@ -0,0 +1,166 @@ +//! BLAKE2b compression function F (EIP-152). +//! +//! Operates on raw BLAKE2b state with an arbitrary round count. + +// Ported from revm-precompile 36.0.3's EIP-152 adaptation: +// https://docs.rs/crate/revm-precompile/36.0.3/source/src/blake2/portable.rs +// That implementation is adapted from blake2b_simd: +// https://github.com/oconnor663/blake2_simd +// +// Copyright (c) 2018 Jack O'Connor +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; + +pub type zkvm_blake2f_state = ZkvmBytes<64>; +pub type zkvm_blake2f_message = ZkvmBytes<128>; +pub type zkvm_blake2f_offset = ZkvmBytes<16>; + +/// Apply BLAKE2 compression function F to `h` in place. +/// +/// # Safety +/// +/// - `h` must be valid for reads and writes of one [`zkvm_blake2f_state`]. +/// - `m` must be valid for reads of one [`zkvm_blake2f_message`]. +/// - `t` must be valid for reads of one [`zkvm_blake2f_offset`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_blake2f( + rounds: u32, + h: *mut zkvm_blake2f_state, + m: *const zkvm_blake2f_message, + t: *const zkvm_blake2f_offset, + f: u8, +) -> zkvm_status { + if h.is_null() || m.is_null() || t.is_null() || f > 1 { + return ZKVM_EFAIL; + } + + // SAFETY: the non-null inputs satisfy the function's pointer requirements. + // Read every input before writing `h` so overlapping arguments are supported. + let (state, message, offset) = unsafe { (h.read(), m.read(), t.read()) }; + + let mut state_words = [0; 8]; + for (word, bytes) in state_words.iter_mut().zip(state.data.as_chunks::<8>().0) { + *word = Word::from_le_bytes(*bytes); + } + let mut message_words = [0; 16]; + for (word, bytes) in message_words.iter_mut().zip(message.data.as_chunks::<8>().0) { + *word = Word::from_le_bytes(*bytes); + } + let offset_words = [ + Word::from_le_bytes(offset.data[..8].try_into().unwrap()), + Word::from_le_bytes(offset.data[8..].try_into().unwrap()), + ]; + + compress(rounds, &mut state_words, &message_words, &offset_words, f == 1); + + let mut value = zkvm_blake2f_state { data: [0; 64] }; + for (bytes, word) in value.data.as_chunks_mut::<8>().0.iter_mut().zip(state_words) { + *bytes = word.to_le_bytes(); + } + + // SAFETY: `h` satisfies the function's write requirement; all reads are complete. + unsafe { h.write(value) }; + ZKVM_EOK +} + +type Word = u64; + +const IV: [Word; 8] = [ + 0x6A09E667F3BCC908, + 0xBB67AE8584CAA73B, + 0x3C6EF372FE94F82B, + 0xA54FF53A5F1D36F1, + 0x510E527FADE682D1, + 0x9B05688C2B3E6C1F, + 0x1F83D9ABFB41BD6B, + 0x5BE0CD19137E2179, +]; + +// The message schedule has period 10 (RFC 7693 section 2.7). EIP-152 permits +// arbitrary round counts, so rounds beyond the standard 12 must use `r % 10`. +const SIGMA: [[u8; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +#[inline(always)] +const fn g(v: &mut [Word; 16], a: usize, b: usize, c: usize, d: usize, x: Word, y: Word) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +#[inline(always)] +const fn round(round: usize, m: &[Word; 16], v: &mut [Word; 16]) { + let schedule = SIGMA[round % SIGMA.len()]; + + g(v, 0, 4, 8, 12, m[schedule[0] as usize], m[schedule[1] as usize]); + g(v, 1, 5, 9, 13, m[schedule[2] as usize], m[schedule[3] as usize]); + g(v, 2, 6, 10, 14, m[schedule[4] as usize], m[schedule[5] as usize]); + g(v, 3, 7, 11, 15, m[schedule[6] as usize], m[schedule[7] as usize]); + + g(v, 0, 5, 10, 15, m[schedule[8] as usize], m[schedule[9] as usize]); + g(v, 1, 6, 11, 12, m[schedule[10] as usize], m[schedule[11] as usize]); + g(v, 2, 7, 8, 13, m[schedule[12] as usize], m[schedule[13] as usize]); + g(v, 3, 4, 9, 14, m[schedule[14] as usize], m[schedule[15] as usize]); +} + +fn compress(rounds: u32, h: &mut [Word; 8], m: &[Word; 16], t: &[Word; 2], f: bool) { + let mut v = [ + h[0], + h[1], + h[2], + h[3], + h[4], + h[5], + h[6], + h[7], + IV[0], + IV[1], + IV[2], + IV[3], + IV[4] ^ t[0], + IV[5] ^ t[1], + IV[6] ^ if f { Word::MAX } else { 0 }, + IV[7], + ]; + + for round_index in 0..rounds as usize { + round(round_index, m, &mut v); + } + + for (index, word) in h.iter_mut().enumerate() { + *word ^= v[index] ^ v[index + 8]; + } +} diff --git a/crates/accelerators/src/bls12_381/codec.rs b/crates/accelerators/src/bls12_381/codec.rs new file mode 100644 index 000000000..205be15ba --- /dev/null +++ b/crates/accelerators/src/bls12_381/codec.rs @@ -0,0 +1,90 @@ +//! EIP-2537 BLS12-381 point and scalar codecs. + +use openvm_curve_utils::SubgroupCheck; +use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint, Group}; +use openvm_pairing::bls12_381 as bls; + +use crate::error::Error; + +const FP_LEN: usize = 48; + +#[inline] +fn read_fp(input: &[u8]) -> Result { + bls::Fp::from_be_bytes(input).ok_or(Error::FieldElementInvalid) +} + +#[inline] +fn read_fp2(c0: &[u8], c1: &[u8]) -> Result { + Ok(bls::Fp2::new(read_fp(c0)?, read_fp(c1)?)) +} + +#[inline] +pub(super) fn read_g1_no_subgroup_check(input: &[u8; 96]) -> Result { + let x = read_fp(&input[..FP_LEN])?; + let y = read_fp(&input[FP_LEN..])?; + // SAFETY: the coordinates are canonical; `from_xy` checks the curve equation. + unsafe { bls::G1Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve) +} + +#[inline] +pub(super) fn read_g1(input: &[u8; 96]) -> Result { + let point = read_g1_no_subgroup_check(input)?; + point.is_in_correct_subgroup().then_some(point).ok_or(Error::PointNotInSubgroup) +} + +#[inline] +pub(super) fn read_g2_no_subgroup_check(input: &[u8; 192]) -> Result { + let x = read_fp2(&input[..FP_LEN], &input[FP_LEN..2 * FP_LEN])?; + let y = read_fp2(&input[2 * FP_LEN..3 * FP_LEN], &input[3 * FP_LEN..])?; + // SAFETY: the coordinates are canonical; `from_xy` checks the twist equation. + unsafe { bls::G2Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve) +} + +#[inline] +pub(super) fn read_g2(input: &[u8; 192]) -> Result { + let point = read_g2_no_subgroup_check(input)?; + point.is_in_correct_subgroup().then_some(point).ok_or(Error::PointNotInSubgroup) +} + +#[inline] +pub(super) fn read_scalar(input: &[u8; 32]) -> bls::Scalar { + bls::Scalar::from_be_bytes_unchecked(input) +} + +#[inline] +pub(super) fn encode_g1(point: &bls::G1Affine) -> [u8; 96] { + let mut output = [0; 96]; + if point.is_identity() { + return output; + } + + let x: &[u8] = point.x().as_le_bytes(); + let y: &[u8] = point.y().as_le_bytes(); + for index in 0..FP_LEN { + output[index] = x[FP_LEN - 1 - index]; + output[index + FP_LEN] = y[FP_LEN - 1 - index]; + } + output +} + +#[inline] +pub(super) fn encode_g2(point: &bls::G2Affine) -> [u8; 192] { + let mut output = [0; 192]; + if point.is_identity() { + return output; + } + + 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 index in 0..FP_LEN { + output[index] = x_c0[FP_LEN - 1 - index]; + output[index + FP_LEN] = x_c1[FP_LEN - 1 - index]; + output[index + 2 * FP_LEN] = y_c0[FP_LEN - 1 - index]; + output[index + 3 * FP_LEN] = y_c1[FP_LEN - 1 - index]; + } + output +} diff --git a/crates/accelerators/src/bls12_381/map.rs b/crates/accelerators/src/bls12_381/map.rs new file mode 100644 index 000000000..51cd772e1 --- /dev/null +++ b/crates/accelerators/src/bls12_381/map.rs @@ -0,0 +1,64 @@ +//! BLS12-381 map-to-curve operations. + +use ark_bls12_381::{Fq, Fq2, G1Affine, G2Affine}; +use ark_ec::{ + hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve}, + AffineRepr, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use crate::error::Error; + +const FP_LEN: usize = 48; + +#[inline] +pub(super) fn fp_to_g1(input: &[u8; 48]) -> Result<[u8; 96], Error> { + let point = WBMap::map_to_curve(read_fq(input)?) + .expect("the arkworks WB map is defined for every field element") + .clear_cofactor(); + Ok(encode_g1(&point)) +} + +#[inline] +pub(super) fn fp2_to_g2(input: &[u8; 96]) -> Result<[u8; 192], Error> { + let c0 = read_fq(&input[..FP_LEN])?; + let c1 = read_fq(&input[FP_LEN..])?; + let point = WBMap::map_to_curve(Fq2::new(c0, c1)) + .expect("the arkworks WB map is defined for every field element") + .clear_cofactor(); + Ok(encode_g2(&point)) +} + +fn read_fq(input_be: &[u8]) -> Result { + let mut input_le = [0; FP_LEN]; + input_le.copy_from_slice(input_be); + input_le.reverse(); + Fq::deserialize_uncompressed(&input_le[..]).map_err(|_| Error::FieldElementInvalid) +} + +fn encode_fq(fq: &Fq, output: &mut [u8]) { + fq.serialize_uncompressed(&mut output[..]).expect("field element serialization is infallible"); + output.reverse(); +} + +fn encode_g1(point: &G1Affine) -> [u8; 96] { + let mut output = [0; 96]; + let Some((x, y)) = point.xy() else { + return output; + }; + encode_fq(&x, &mut output[..FP_LEN]); + encode_fq(&y, &mut output[FP_LEN..]); + output +} + +fn encode_g2(point: &G2Affine) -> [u8; 192] { + let mut output = [0; 192]; + let Some((x, y)) = point.xy() else { + return output; + }; + encode_fq(&x.c0, &mut output[..FP_LEN]); + encode_fq(&x.c1, &mut output[FP_LEN..2 * FP_LEN]); + encode_fq(&y.c0, &mut output[2 * FP_LEN..3 * FP_LEN]); + encode_fq(&y.c1, &mut output[3 * FP_LEN..]); + output +} diff --git a/crates/accelerators/src/bls12_381/mod.rs b/crates/accelerators/src/bls12_381/mod.rs new file mode 100644 index 000000000..95173f371 --- /dev/null +++ b/crates/accelerators/src/bls12_381/mod.rs @@ -0,0 +1,290 @@ +//! BLS12-381 accelerators (EIP-2537). + +mod codec; +mod map; + +use alloc::vec::Vec; + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +use codec::{ + encode_g1, encode_g2, read_g1, read_g1_no_subgroup_check, read_g2, read_g2_no_subgroup_check, + read_scalar, +}; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, +}; +use openvm_pairing::{bls12_381::Bls12_381, PairingCheck}; + +use crate::error::Error; + +pub type zkvm_bls12_381_g1_point = ZkvmBytes<96>; +pub type zkvm_bls12_381_g2_point = ZkvmBytes<192>; +pub type zkvm_bls12_381_scalar = ZkvmBytes<32>; +pub type zkvm_bls12_381_fp = ZkvmBytes<48>; +pub type zkvm_bls12_381_fp2 = ZkvmBytes<96>; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct zkvm_bls12_381_g1_msm_pair { + pub point: zkvm_bls12_381_g1_point, + pub scalar: zkvm_bls12_381_scalar, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct zkvm_bls12_381_g2_msm_pair { + pub point: zkvm_bls12_381_g2_point, + pub scalar: zkvm_bls12_381_scalar, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct zkvm_bls12_381_pairing_pair { + pub g1: zkvm_bls12_381_g1_point, + pub g2: zkvm_bls12_381_g2_point, +} + +/// BLS12-381 G1 point addition. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_g1_add( + p1: *const zkvm_bls12_381_g1_point, + p2: *const zkvm_bls12_381_g1_point, + result: *mut zkvm_bls12_381_g1_point, +) -> zkvm_status { + if p1.is_null() || p2.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees valid reads. The output is written only after these + // shared borrows are no longer used, so overlapping storage is supported. + let value = unsafe { g1_add(&(*p1).data, &(*p2).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bls12_381_g1_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// BLS12-381 G1 multi-scalar multiplication. +/// +/// # Safety +/// +/// `pairs` must be valid for `num_pairs` reads when non-empty, and `result` +/// must be non-NULL and valid for one write. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_g1_msm( + pairs: *const zkvm_bls12_381_g1_msm_pair, + num_pairs: usize, + result: *mut zkvm_bls12_381_g1_point, +) -> zkvm_status { + if result.is_null() || (pairs.is_null() && num_pairs != 0) { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that non-empty input is valid for `num_pairs` reads. + let pairs = + if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } }; + match g1_msm(pairs) { + Ok(data) => { + // SAFETY: all input reads are complete; `result` is valid for writes. + unsafe { result.write(zkvm_bls12_381_g1_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// BLS12-381 G2 point addition. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_g2_add( + p1: *const zkvm_bls12_381_g2_point, + p2: *const zkvm_bls12_381_g2_point, + result: *mut zkvm_bls12_381_g2_point, +) -> zkvm_status { + if p1.is_null() || p2.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees valid reads. The output is written only after these + // shared borrows are no longer used, so overlapping storage is supported. + let value = unsafe { g2_add(&(*p1).data, &(*p2).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bls12_381_g2_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// BLS12-381 G2 multi-scalar multiplication. +/// +/// # Safety +/// +/// `pairs` must be valid for `num_pairs` reads when non-empty, and `result` +/// must be non-NULL and valid for one write. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_g2_msm( + pairs: *const zkvm_bls12_381_g2_msm_pair, + num_pairs: usize, + result: *mut zkvm_bls12_381_g2_point, +) -> zkvm_status { + if result.is_null() || (pairs.is_null() && num_pairs != 0) { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that non-empty input is valid for `num_pairs` reads. + let pairs = + if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } }; + match g2_msm(pairs) { + Ok(data) => { + // SAFETY: all input reads are complete; `result` is valid for writes. + unsafe { result.write(zkvm_bls12_381_g2_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// BLS12-381 pairing check. +/// +/// # Safety +/// +/// `pairs` must be valid for `num_pairs` reads when non-empty, and `verified` +/// must be non-NULL and valid for one write. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_pairing( + pairs: *const zkvm_bls12_381_pairing_pair, + num_pairs: usize, + verified: *mut bool, +) -> zkvm_status { + if verified.is_null() || (pairs.is_null() && num_pairs != 0) { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that non-empty input is valid for `num_pairs` reads. + let pairs = + if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } }; + match pairing(pairs) { + Ok(value) => { + // SAFETY: all input reads are complete; `verified` is valid for writes. + unsafe { verified.write(value) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// Map a BLS12-381 base-field element to G1. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_map_fp_to_g1( + field_element: *const zkvm_bls12_381_fp, + result: *mut zkvm_bls12_381_g1_point, +) -> zkvm_status { + if field_element.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees a valid read. The output is written only after this + // shared borrow is no longer used, so overlapping storage is supported. + let value = unsafe { map::fp_to_g1(&(*field_element).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bls12_381_g1_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// Map a BLS12-381 quadratic-extension-field element to G2. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( + field_element: *const zkvm_bls12_381_fp2, + result: *mut zkvm_bls12_381_g2_point, +) -> zkvm_status { + if field_element.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees a valid read. The output is written only after this + // shared borrow is no longer used, so overlapping storage is supported. + let value = unsafe { map::fp2_to_g2(&(*field_element).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bls12_381_g2_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +#[inline] +fn g1_add(p1: &[u8; 96], p2: &[u8; 96]) -> Result<[u8; 96], Error> { + Ok(encode_g1(&(read_g1_no_subgroup_check(p1)? + read_g1_no_subgroup_check(p2)?))) +} + +fn g1_msm(pairs: &[zkvm_bls12_381_g1_msm_pair]) -> Result<[u8; 96], Error> { + let mut points = Vec::with_capacity(pairs.len()); + let mut scalars = Vec::with_capacity(pairs.len()); + for pair in pairs { + points.push(read_g1(&pair.point.data)?); + scalars.push(read_scalar(&pair.scalar.data)); + } + if points.is_empty() { + Ok([0; 96]) + } else { + Ok(encode_g1(&Bls12_381::msm(&scalars, &points))) + } +} + +#[inline] +fn g2_add(p1: &[u8; 192], p2: &[u8; 192]) -> Result<[u8; 192], Error> { + Ok(encode_g2(&(read_g2_no_subgroup_check(p1)? + read_g2_no_subgroup_check(p2)?))) +} + +fn g2_msm(pairs: &[zkvm_bls12_381_g2_msm_pair]) -> Result<[u8; 192], Error> { + let mut points = Vec::with_capacity(pairs.len()); + let mut scalars = Vec::with_capacity(pairs.len()); + for pair in pairs { + points.push(read_g2(&pair.point.data)?); + scalars.push(read_scalar(&pair.scalar.data)); + } + if points.is_empty() { + Ok([0; 192]) + } else { + Ok(encode_g2(&openvm_ecc_guest::msm(&scalars, &points))) + } +} + +fn pairing(pairs: &[zkvm_bls12_381_pairing_pair]) -> Result { + let mut g1_points = Vec::with_capacity(pairs.len()); + let mut g2_points = Vec::with_capacity(pairs.len()); + for pair in pairs { + let (g1_x, g1_y) = read_g1(&pair.g1.data)?.into_coords(); + let (g2_x, g2_y) = read_g2(&pair.g2.data)?.into_coords(); + g1_points.push(AffinePoint::new(g1_x, g1_y)); + g2_points.push(AffinePoint::new(g2_x, g2_y)); + } + if g1_points.is_empty() { + Ok(true) + } else { + Ok(Bls12_381::pairing_check(&g1_points, &g2_points).is_ok()) + } +} diff --git a/crates/accelerators/src/bn254/codec.rs b/crates/accelerators/src/bn254/codec.rs new file mode 100644 index 000000000..b766e5712 --- /dev/null +++ b/crates/accelerators/src/bn254/codec.rs @@ -0,0 +1,54 @@ +use openvm_curve_utils::SubgroupCheck; +use openvm_ecc_guest::{algebra::IntMod, weierstrass::WeierstrassPoint}; +use openvm_pairing::bn254 as bn; + +use crate::error::Error; + +const FQ_LEN: usize = 32; + +#[inline] +fn read_fq(input: &[u8]) -> Result { + bn::Fp::from_be_bytes(input).ok_or(Error::FieldElementInvalid) +} + +#[inline] +fn read_fq2(input: &[u8; 64]) -> Result { + let imag = read_fq(&input[..FQ_LEN])?; + let real = read_fq(&input[FQ_LEN..])?; + Ok(bn::Fp2::new(real, imag)) +} + +#[inline] +pub(super) fn read_g1(input: &[u8; 64]) -> Result { + let x = read_fq(&input[..FQ_LEN])?; + let y = read_fq(&input[FQ_LEN..])?; + // SAFETY: the coordinates are canonical; `from_xy` checks the curve equation. + let point = unsafe { bn::G1Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve)?; + point.is_in_correct_subgroup().then_some(point).ok_or(Error::PointNotInSubgroup) +} + +#[inline] +pub(super) fn read_g2(input: &[u8; 128]) -> Result { + let x = read_fq2(input[..64].try_into().unwrap())?; + let y = read_fq2(input[64..].try_into().unwrap())?; + // SAFETY: the coordinates are canonical; `from_xy` checks the curve equation. + let point = unsafe { bn::G2Affine::from_xy(x, y) }.ok_or(Error::PointNotOnCurve)?; + point.is_in_correct_subgroup().then_some(point).ok_or(Error::PointNotInSubgroup) +} + +#[inline] +pub(super) fn read_scalar(input: &[u8; 32]) -> bn::Scalar { + bn::Scalar::from_be_bytes_unchecked(input) +} + +#[inline] +pub(super) fn encode_g1(point: bn::G1Affine) -> [u8; 64] { + let mut output = [0; 64]; + let x: &[u8] = point.x().as_le_bytes(); + let y: &[u8] = point.y().as_le_bytes(); + for index in 0..FQ_LEN { + output[index] = x[FQ_LEN - 1 - index]; + output[index + FQ_LEN] = y[FQ_LEN - 1 - index]; + } + output +} diff --git a/crates/accelerators/src/bn254/mod.rs b/crates/accelerators/src/bn254/mod.rs new file mode 100644 index 000000000..e8549f2ce --- /dev/null +++ b/crates/accelerators/src/bn254/mod.rs @@ -0,0 +1,136 @@ +//! BN254 accelerators (EIP-196 and EIP-197). + +mod codec; + +use alloc::vec::Vec; + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +use codec::{encode_g1, read_g1, read_g2, read_scalar}; +use openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, +}; +use openvm_pairing::{bn254::Bn254, PairingCheck}; + +use crate::error::Error; + +pub type zkvm_bn254_g1_point = ZkvmBytes<64>; +pub type zkvm_bn254_g2_point = ZkvmBytes<128>; +pub type zkvm_bn254_scalar = ZkvmBytes<32>; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct zkvm_bn254_pairing_pair { + pub g1: zkvm_bn254_g1_point, + pub g2: zkvm_bn254_g2_point, +} + +/// Add two BN254 G1 points. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bn254_g1_add( + p1: *const zkvm_bn254_g1_point, + p2: *const zkvm_bn254_g1_point, + result: *mut zkvm_bn254_g1_point, +) -> zkvm_status { + if p1.is_null() || p2.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees valid reads. The output is written only after these + // shared borrows are no longer used, so overlapping storage is supported. + let value = unsafe { g1_add(&(*p1).data, &(*p2).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bn254_g1_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// Multiply a BN254 G1 point by a scalar. +/// +/// # Safety +/// +/// Each pointer must be non-NULL and valid for one value of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bn254_g1_mul( + point: *const zkvm_bn254_g1_point, + scalar: *const zkvm_bn254_scalar, + result: *mut zkvm_bn254_g1_point, +) -> zkvm_status { + if point.is_null() || scalar.is_null() || result.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees valid reads. The output is written only after these + // shared borrows are no longer used, so overlapping storage is supported. + let value = unsafe { g1_mul(&(*point).data, &(*scalar).data) }; + match value { + Ok(data) => { + // SAFETY: `result` is non-NULL and valid for writes. + unsafe { result.write(zkvm_bn254_g1_point { data }) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +/// Check a BN254 pairing equation. +/// +/// # Safety +/// +/// `pairs` must be valid for `num_pairs` reads when non-empty, and `verified` +/// must be non-NULL and valid for one write. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_bn254_pairing( + pairs: *const zkvm_bn254_pairing_pair, + num_pairs: usize, + verified: *mut bool, +) -> zkvm_status { + if verified.is_null() || (pairs.is_null() && num_pairs != 0) { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that non-empty input is valid for `num_pairs` reads. + let pairs = + if num_pairs == 0 { &[] } else { unsafe { core::slice::from_raw_parts(pairs, num_pairs) } }; + let value = pairing(pairs.iter().map(|pair| (&pair.g1.data, &pair.g2.data))); + match value { + Ok(value) => { + // SAFETY: `verified` is non-NULL and all input reads are complete. + unsafe { verified.write(value) }; + ZKVM_EOK + } + Err(_) => ZKVM_EFAIL, + } +} + +fn g1_add(p1: &[u8; 64], p2: &[u8; 64]) -> Result<[u8; 64], Error> { + Ok(encode_g1(read_g1(p1)? + read_g1(p2)?)) +} + +fn g1_mul(point: &[u8; 64], scalar: &[u8; 32]) -> Result<[u8; 64], Error> { + Ok(encode_g1(Bn254::msm(&[read_scalar(scalar)], &[read_g1(point)?]))) +} + +fn pairing<'a>( + pairs: impl IntoIterator, +) -> Result { + let pairs = pairs.into_iter(); + let mut g1_points = Vec::with_capacity(pairs.size_hint().0); + let mut g2_points = Vec::with_capacity(pairs.size_hint().0); + for (g1, g2) in pairs { + let (g1_x, g1_y) = read_g1(g1)?.into_coords(); + let (g2_x, g2_y) = read_g2(g2)?.into_coords(); + g1_points.push(AffinePoint::new(g1_x, g1_y)); + g2_points.push(AffinePoint::new(g2_x, g2_y)); + } + if g1_points.is_empty() { + Ok(true) + } else { + Ok(Bn254::pairing_check(&g1_points, &g2_points).is_ok()) + } +} diff --git a/crates/accelerators/src/error.rs b/crates/accelerators/src/error.rs new file mode 100644 index 000000000..3928c2744 --- /dev/null +++ b/crates/accelerators/src/error.rs @@ -0,0 +1,8 @@ +//! Shared curve-decoding errors. + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Error { + FieldElementInvalid, + PointNotOnCurve, + PointNotInSubgroup, +} diff --git a/crates/accelerators/src/ffi/blake2.rs b/crates/accelerators/src/ffi/blake2.rs deleted file mode 100644 index 3fda48d18..000000000 --- a/crates/accelerators/src/ffi/blake2.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! C ABI for the BLAKE2 compression function. - -use crate::{ - ops, - types::{ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus}, -}; - -/// Apply the BLAKE2 compression function F (EIP-152) to `h` in place. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL, or if `f` is neither -/// 0 nor 1. -/// -/// # Safety -/// -/// - `h`, if non-NULL, must be valid for reads and writes of 64 bytes. -/// - `m`, if non-NULL, must be valid for reads of 128 bytes. -/// - `t`, if non-NULL, must be valid for reads of 16 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_blake2f( - rounds: u32, - h: *mut ZkvmBlake2fState, - m: *const ZkvmBlake2fMessage, - t: *const ZkvmBlake2fOffset, - f: u8, -) -> ZkvmStatus { - if h.is_null() || m.is_null() || t.is_null() || f > 1 { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (state, message, offset) = unsafe { (h.read(), m.read(), t.read()) }; - - let mut state_words = [0; 8]; - for (word, bytes) in state_words.iter_mut().zip(state.data.as_chunks::<8>().0) { - *word = u64::from_le_bytes(*bytes); - } - let mut message_words = [0; 16]; - for (word, bytes) in message_words.iter_mut().zip(message.data.as_chunks::<8>().0) { - *word = u64::from_le_bytes(*bytes); - } - let offset_words = [ - u64::from_le_bytes(offset.data[..8].try_into().unwrap()), - u64::from_le_bytes(offset.data[8..].try_into().unwrap()), - ]; - - ops::blake2f(rounds, &mut state_words, &message_words, &offset_words, f == 1); - - let mut value = ZkvmBlake2fState { data: [0; 64] }; - for (bytes, word) in value.data.as_chunks_mut::<8>().0.iter_mut().zip(state_words) { - *bytes = word.to_le_bytes(); - } - // SAFETY: `h` is non-NULL and valid for writes. - unsafe { h.write(value) }; - ZkvmStatus::Ok -} diff --git a/crates/accelerators/src/ffi/bls12_381.rs b/crates/accelerators/src/ffi/bls12_381.rs deleted file mode 100644 index b7e78d1ad..000000000 --- a/crates/accelerators/src/ffi/bls12_381.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! C ABI for the BLS12-381 add/MSM/map accelerators (EIP-2537). - -use crate::{ - ops, - types::{ - ZkvmBls12381Fp, ZkvmBls12381Fp2, 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: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - match ops::bls12_381_g1_add(&bls_g1(p1.data), &bls_g1(p2.data)) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBls12381G1Point { data }) }; - 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) } }; - let pairs = pairs.iter().map(|pair| { - Ok::<_, core::convert::Infallible>((bls_g1(pair.point.data), pair.scalar.data)) - }); - match ops::bls12_381_g1_msm(pairs) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. - unsafe { result.write(ZkvmBls12381G1Point { data }) }; - 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: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - match ops::bls12_381_g2_add(&bls_g2(p1.data), &bls_g2(p2.data)) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBls12381G2Point { data }) }; - 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) } }; - let pairs = pairs.iter().map(|pair| { - Ok::<_, core::convert::Infallible>((bls_g2(pair.point.data), pair.scalar.data)) - }); - match ops::bls12_381_g2_msm(pairs) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes; input reads are complete. - unsafe { result.write(ZkvmBls12381G2Point { data }) }; - 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) } }; - let pairs = pairs.iter().map(|pair| (bls_g1(pair.g1.data), bls_g2(pair.g2.data))); - match ops::bls12_381_pairing_check(pairs) { - Ok(value) => { - // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. - unsafe { verified.write(value) }; - ZkvmStatus::Ok - } - Err(_) => { - // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. - unsafe { verified.write(false) }; - ZkvmStatus::Fail - } - } -} - -fn bls_g1(data: [u8; 96]) -> ([u8; 48], [u8; 48]) { - (data[..48].try_into().unwrap(), data[48..].try_into().unwrap()) -} - -fn bls_g2(data: [u8; 192]) -> ([u8; 48], [u8; 48], [u8; 48], [u8; 48]) { - ( - data[..48].try_into().unwrap(), - data[48..96].try_into().unwrap(), - data[96..144].try_into().unwrap(), - data[144..].try_into().unwrap(), - ) -} - -/// BLS12-381 map field element to G1 (precompile 0x10, EIP-2537). -/// -/// Returns [`ZkvmStatus::Fail`] if either pointer is NULL or the field element -/// is not canonical. -/// -/// # Safety -/// -/// - `field_element`, if non-NULL, must be valid for reads of 48 bytes. -/// - `result`, if non-NULL, must be valid for writes of 96 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_bls12_map_fp_to_g1( - field_element: *const ZkvmBls12381Fp, - result: *mut ZkvmBls12381G1Point, -) -> ZkvmStatus { - if field_element.is_null() || result.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. - let field_element = unsafe { field_element.read() }; - match ops::bls12_381_map_fp_to_g1(&field_element.data) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBls12381G1Point { data }) }; - ZkvmStatus::Ok - } - Err(_) => ZkvmStatus::Fail, - } -} - -/// BLS12-381 map field element to G2 (precompile 0x11, EIP-2537). -/// -/// Returns [`ZkvmStatus::Fail`] if either pointer is NULL or either half of -/// the field element is not canonical. -/// -/// # Safety -/// -/// - `field_element`, if non-NULL, must be valid for reads of 96 bytes. -/// - `result`, if non-NULL, must be valid for writes of 192 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_bls12_map_fp2_to_g2( - field_element: *const ZkvmBls12381Fp2, - result: *mut ZkvmBls12381G2Point, -) -> ZkvmStatus { - if field_element.is_null() || result.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL input is valid for reads. Copy before writing to support overlap. - let field_element = unsafe { field_element.read() }; - let fp2 = ( - field_element.data[..48].try_into().unwrap(), - field_element.data[48..].try_into().unwrap(), - ); - match ops::bls12_381_map_fp2_to_g2(&fp2) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBls12381G2Point { data }) }; - ZkvmStatus::Ok - } - Err(_) => ZkvmStatus::Fail, - } -} diff --git a/crates/accelerators/src/ffi/bn254.rs b/crates/accelerators/src/ffi/bn254.rs deleted file mode 100644 index d0d34b884..000000000 --- a/crates/accelerators/src/ffi/bn254.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! 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: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (p1, p2) = unsafe { (p1.read(), p2.read()) }; - match ops::bn254_g1_add(&p1.data, &p2.data) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBn254G1Point { data }) }; - 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: the non-NULL inputs are valid for reads. Copy before writing to support overlap. - let (point, scalar) = unsafe { (point.read(), scalar.read()) }; - match ops::bn254_g1_mul(&point.data, &scalar.data) { - Ok(data) => { - // SAFETY: `result` is non-NULL and valid for writes. - unsafe { result.write(ZkvmBn254G1Point { data }) }; - 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) } }; - let pairs = pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())); - match ops::bn254_pairing_check(pairs) { - Ok(value) => { - // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. - unsafe { verified.write(value) }; - ZkvmStatus::Ok - } - Err(_) => { - // SAFETY: `verified` is non-NULL and valid for writes; input reads are complete. - unsafe { verified.write(false) }; - ZkvmStatus::Fail - } - } -} diff --git a/crates/accelerators/src/ffi/ecdsa.rs b/crates/accelerators/src/ffi/ecdsa.rs deleted file mode 100644 index a262a360c..000000000 --- a/crates/accelerators/src/ffi/ecdsa.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! C ABI for the ECDSA accelerators. - -use crate::{ - ops, - types::{ - ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, - ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, ZkvmStatus, - }, -}; - -/// Recover the uncompressed secp256k1 public key from an ECDSA signature -/// over `msg` into `output`. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL, the signature cannot -/// be parsed, or the recovery id is invalid. -/// -/// # Safety -/// -/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. -/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. -/// - `output`, if non-NULL, must be valid for writes of 64 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( - msg: *const ZkvmSecp256k1Hash, - sig: *const ZkvmSecp256k1Signature, - recid: u8, - output: *mut ZkvmSecp256k1Pubkey, -) -> ZkvmStatus { - if msg.is_null() || sig.is_null() || output.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL inputs are valid for reads. Copying before writing supports overlap. - let (msg, sig) = unsafe { (msg.read(), sig.read()) }; - match ops::secp256k1_ecrecover(&msg.data, &sig.data, recid) { - Ok(data) => { - // SAFETY: `output` is non-NULL and valid for writes. - unsafe { output.write(ZkvmSecp256k1Pubkey { data }) }; - ZkvmStatus::Ok - } - Err(_) => ZkvmStatus::Fail, - } -} - -/// Verify an ECDSA signature over secp256k1 against an uncompressed public -/// key, writing the result to `verified`. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid -/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. -/// -/// # Safety -/// -/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. -/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. -/// - `pubkey`, if non-NULL, must be valid for reads of 64 bytes. -/// - `verified`, if non-NULL, must be valid for writes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_secp256k1_verify( - msg: *const ZkvmSecp256k1Hash, - sig: *const ZkvmSecp256k1Signature, - pubkey: *const ZkvmSecp256k1Pubkey, - verified: *mut bool, -) -> ZkvmStatus { - if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL inputs are valid for reads. - let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; - let value = ops::secp256k1_verify(&msg.data, &sig.data, &pubkey.data); - // SAFETY: `verified` is non-NULL and valid for writes. - unsafe { verified.write(value) }; - ZkvmStatus::Ok -} - -/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed -/// public key, writing the result to `verified`. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid -/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. -/// -/// # Safety -/// -/// - `msg`, if non-NULL, must be valid for reads of 32 bytes. -/// - `sig`, if non-NULL, must be valid for reads of 64 bytes. -/// - `pubkey`, if non-NULL, must be valid for reads of 64 bytes. -/// - `verified`, if non-NULL, must be valid for writes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_secp256r1_verify( - msg: *const ZkvmSecp256r1Hash, - sig: *const ZkvmSecp256r1Signature, - pubkey: *const ZkvmSecp256r1Pubkey, - verified: *mut bool, -) -> ZkvmStatus { - if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL inputs are valid for reads. - let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; - let value = ops::secp256r1_verify(&msg.data, &sig.data, &pubkey.data); - // SAFETY: `verified` is non-NULL and valid for writes. - unsafe { verified.write(value) }; - ZkvmStatus::Ok -} diff --git a/crates/accelerators/src/ffi/hash.rs b/crates/accelerators/src/ffi/hash.rs deleted file mode 100644 index 50978a19b..000000000 --- a/crates/accelerators/src/ffi/hash.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! C ABI for the hash accelerators. - -use crate::{ - ops, - types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus}, -}; - -/// Compute the Keccak-256 hash of `data[..len]` into `output`. -/// -/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL -/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty -/// input. -/// -/// # Safety -/// -/// - `data`, if non-NULL, must be valid for reads of `len` bytes. -/// - `output`, if non-NULL, must be valid for writes of 32 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_keccak256( - data: *const u8, - len: usize, - output: *mut ZkvmKeccak256Hash, -) -> ZkvmStatus { - if output.is_null() || (data.is_null() && len != 0) { - return ZkvmStatus::Fail; - } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let value = ZkvmKeccak256Hash { data: ops::keccak256(data) }; - // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so - // overlapping input/output storage is supported. - unsafe { output.write(value) }; - ZkvmStatus::Ok -} - -/// Compute the SHA-256 hash of `data[..len]` into `output`. -/// -/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL -/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty -/// input. -/// -/// # Safety -/// -/// - `data`, if non-NULL, must be valid for reads of `len` bytes. -/// - `output`, if non-NULL, must be valid for writes of 32 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_sha256( - data: *const u8, - len: usize, - output: *mut ZkvmSha256Hash, -) -> ZkvmStatus { - if output.is_null() || (data.is_null() && len != 0) { - return ZkvmStatus::Fail; - } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let value = ZkvmSha256Hash { data: ops::sha256(data) }; - // SAFETY: see `zkvm_keccak256`. - unsafe { output.write(value) }; - ZkvmStatus::Ok -} - -/// Compute the RIPEMD-160 hash of `data[..len]` into `output`. -/// -/// The 20-byte digest is written to `output.data[12..]`; the first 12 bytes -/// are zeroed. -/// -/// Returns [`ZkvmStatus::Fail`] if `output` is NULL, or if `data` is NULL -/// with a non-zero `len`; a NULL `data` with `len == 0` hashes the empty -/// input. -/// -/// # Safety -/// -/// - `data`, if non-NULL, must be valid for reads of `len` bytes. -/// - `output`, if non-NULL, must be valid for writes of 32 bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_ripemd160( - data: *const u8, - len: usize, - output: *mut ZkvmRipemd160Hash, -) -> ZkvmStatus { - if output.is_null() || (data.is_null() && len != 0) { - return ZkvmStatus::Fail; - } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; - let value = ZkvmRipemd160Hash { data: ops::ripemd160(data) }; - // SAFETY: see `zkvm_keccak256`. - unsafe { output.write(value) }; - ZkvmStatus::Ok -} diff --git a/crates/accelerators/src/ffi/kzg.rs b/crates/accelerators/src/ffi/kzg.rs deleted file mode 100644 index e2310f5df..000000000 --- a/crates/accelerators/src/ffi/kzg.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! C ABI for KZG point evaluation. - -use crate::{ - ops, - types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmStatus}, -}; - -/// Verify a KZG proof that the blob committed to by `commitment` evaluates -/// to `y` at point `z`, writing the result to `verified`. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL. Malformed or invalid -/// cryptographic inputs return [`ZkvmStatus::Ok`] with `verified == false`. -/// -/// # Safety -/// -/// - `commitment` and `proof`, if non-NULL, must be valid for reads of 48 bytes. -/// - `z` and `y`, if non-NULL, must be valid for reads of 32 bytes. -/// - `verified`, if non-NULL, must be valid for writes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_kzg_point_eval( - commitment: *const ZkvmKzgCommitment, - z: *const ZkvmKzgFieldElement, - y: *const ZkvmKzgFieldElement, - proof: *const ZkvmKzgProof, - verified: *mut bool, -) -> ZkvmStatus { - if commitment.is_null() || z.is_null() || y.is_null() || proof.is_null() || verified.is_null() { - return ZkvmStatus::Fail; - } - // SAFETY: the non-NULL inputs are valid for reads. - let (commitment, z, y, proof) = - unsafe { (commitment.read(), z.read(), y.read(), proof.read()) }; - let value = - ops::kzg_point_eval(&commitment.data, &z.data, &y.data, &proof.data).unwrap_or(false); - // SAFETY: `verified` is non-NULL and valid for writes. - unsafe { verified.write(value) }; - ZkvmStatus::Ok -} diff --git a/crates/accelerators/src/ffi/mod.rs b/crates/accelerators/src/ffi/mod.rs deleted file mode 100644 index 3ecddb08c..000000000 --- a/crates/accelerators/src/ffi/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! The `extern "C"` layer: `zkvm_*` symbols matching `zkvm_accelerators.h`. -//! -//! Each function validates the ABI inputs, converts their representation, -//! calls [`crate::ops`], and maps the result to [`crate::types::ZkvmStatus`]. - -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::*; -pub use modexp::*; diff --git a/crates/accelerators/src/ffi/modexp.rs b/crates/accelerators/src/ffi/modexp.rs deleted file mode 100644 index 652eaabba..000000000 --- a/crates/accelerators/src/ffi/modexp.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! C ABI for modular exponentiation. - -use crate::{ops, types::ZkvmStatus}; - -/// Compute `base[..base_len] ^ exp[..exp_len] % modulus[..mod_len]` into -/// `output`, which receives exactly `mod_len` bytes, left-padded with zeros. -/// -/// Returns [`ZkvmStatus::Fail`] if any pointer is NULL while its length is -/// non-zero; a NULL pointer with a zero length is the empty input. -/// -/// # Safety -/// -/// - `base`, `exp` and `modulus`, if non-NULL, must be valid for reads of their respective lengths. -/// - `output`, if non-NULL, must be valid for writes of `mod_len` bytes. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn zkvm_modexp( - base: *const u8, - base_len: usize, - exp: *const u8, - exp_len: usize, - modulus: *const u8, - mod_len: usize, - output: *mut u8, -) -> ZkvmStatus { - if (base.is_null() && base_len != 0) || - (exp.is_null() && exp_len != 0) || - (modulus.is_null() && mod_len != 0) || - (output.is_null() && mod_len != 0) - { - return ZkvmStatus::Fail; - } - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let base = - if base_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(base, base_len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let exp = if exp_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(exp, exp_len) } }; - // SAFETY: non-NULL checked above; validity is guaranteed by the caller. - let modulus = - if mod_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(modulus, mod_len) } }; - let value = ops::modexp(base, exp, modulus); - if mod_len != 0 { - // SAFETY: `output` is non-NULL and valid for `mod_len` writes. `value` cannot overlap it, - // and all caller-provided input reads are complete. - unsafe { core::ptr::copy_nonoverlapping(value.as_ptr(), output, mod_len) }; - } - ZkvmStatus::Ok -} diff --git a/crates/accelerators/src/keccak256.rs b/crates/accelerators/src/keccak256.rs new file mode 100644 index 000000000..bc111693c --- /dev/null +++ b/crates/accelerators/src/keccak256.rs @@ -0,0 +1,31 @@ +//! Keccak-256 accelerator. + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; + +pub type zkvm_keccak256_hash = ZkvmBytes<32>; + +/// Compute the Keccak-256 hash of `data[..len]` into `output`. +/// +/// A NULL `data` pointer is accepted only when `len == 0`. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of one [`zkvm_keccak256_hash`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_keccak256( + data: *const u8, + len: usize, + output: *mut zkvm_keccak256_hash, +) -> zkvm_status { + if output.is_null() || (data.is_null() && len != 0) { + return ZKVM_EFAIL; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + let value = zkvm_keccak256_hash { data: openvm_keccak256::keccak256(data) }; + // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so + // overlapping input/output storage is supported. + unsafe { output.write(value) }; + ZKVM_EOK +} diff --git a/crates/accelerators/src/kzg.rs b/crates/accelerators/src/kzg.rs new file mode 100644 index 000000000..83982e247 --- /dev/null +++ b/crates/accelerators/src/kzg.rs @@ -0,0 +1,48 @@ +//! KZG point-evaluation accelerator (EIP-4844). + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +use openvm_kzg::{Bytes32, Bytes48, KzgProof}; + +pub type zkvm_kzg_commitment = ZkvmBytes<48>; +pub type zkvm_kzg_proof = ZkvmBytes<48>; +pub type zkvm_kzg_field_element = ZkvmBytes<32>; + +/// Verify a KZG point-evaluation proof. +/// +/// # Safety +/// +/// Every pointer must be non-NULL and valid for one read or write of its pointee type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_kzg_point_eval( + commitment: *const zkvm_kzg_commitment, + z: *const zkvm_kzg_field_element, + y: *const zkvm_kzg_field_element, + proof: *const zkvm_kzg_proof, + verified: *mut bool, +) -> zkvm_status { + if commitment.is_null() || z.is_null() || y.is_null() || proof.is_null() || verified.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees valid reads. `verified` is written only after these + // shared borrows are no longer used, so overlapping storage is supported. + let value = unsafe { verify(&(*commitment).data, &(*z).data, &(*y).data, &(*proof).data) } + .unwrap_or(false); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZKVM_EOK +} + +fn verify(commitment: &[u8; 48], z: &[u8; 32], y: &[u8; 32], proof: &[u8; 48]) -> Result { + let commitment = Bytes48::from_slice(commitment).map_err(|_| ())?; + let z = Bytes32::from_slice(z).map_err(|_| ())?; + let y = Bytes32::from_slice(y).map_err(|_| ())?; + let proof = Bytes48::from_slice(proof).map_err(|_| ())?; + KzgProof::verify_kzg_proof( + &commitment, + &z, + &y, + &proof, + openvm_kzg::EnvKzgSettings::default().get(), + ) + .map_err(|_| ()) +} diff --git a/crates/accelerators/src/lib.rs b/crates/accelerators/src/lib.rs index affa9e773..1ae378709 100644 --- a/crates/accelerators/src/lib.rs +++ b/crates/accelerators/src/lib.rs @@ -1,21 +1,44 @@ -//! OpenVM implementation of the zkVM cryptographic accelerator interface. -//! -//! Points and scalars use fixed-size big-endian encodings. BLS12-381 G2 uses -//! `x_c0 || x_c1 || y_c0 || y_c1`; BN254 G2 uses the EIP-197 -//! `x_c1 || x_c0 || y_c1 || y_c0` order. +//! OpenVM implementation of the standard zkVM accelerator C interface. #![cfg_attr(not(feature = "std"), no_std)] +#![allow(non_camel_case_types)] extern crate alloc; -#[cfg(feature = "ffi")] -mod ffi; -mod ops; -#[cfg(feature = "ffi")] +mod blake2f; +mod bls12_381; +mod bn254; +mod error; +mod keccak256; +mod kzg; +mod modexp; +mod ripemd160; +mod secp256k1; +mod secp256r1; +mod sha256; mod types; -#[cfg(feature = "ffi")] -pub use ffi::*; -pub use ops::*; -#[cfg(feature = "ffi")] -pub use types::*; +pub use blake2f::{zkvm_blake2f, zkvm_blake2f_message, zkvm_blake2f_offset, zkvm_blake2f_state}; +pub use bls12_381::{ + zkvm_bls12_381_fp, zkvm_bls12_381_fp2, zkvm_bls12_381_g1_msm_pair, zkvm_bls12_381_g1_point, + zkvm_bls12_381_g2_msm_pair, zkvm_bls12_381_g2_point, zkvm_bls12_381_pairing_pair, + zkvm_bls12_381_scalar, zkvm_bls12_g1_add, zkvm_bls12_g1_msm, zkvm_bls12_g2_add, + zkvm_bls12_g2_msm, zkvm_bls12_map_fp2_to_g2, zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, +}; +pub use bn254::{ + zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_g1_point, zkvm_bn254_g2_point, + zkvm_bn254_pairing, zkvm_bn254_pairing_pair, zkvm_bn254_scalar, +}; +pub use keccak256::{zkvm_keccak256, zkvm_keccak256_hash}; +pub use kzg::{zkvm_kzg_commitment, zkvm_kzg_field_element, zkvm_kzg_point_eval, zkvm_kzg_proof}; +pub use modexp::zkvm_modexp; +pub use ripemd160::{zkvm_ripemd160, zkvm_ripemd160_hash}; +pub use secp256k1::{ + zkvm_secp256k1_ecrecover, zkvm_secp256k1_hash, zkvm_secp256k1_pubkey, zkvm_secp256k1_signature, + zkvm_secp256k1_verify, +}; +pub use secp256r1::{ + zkvm_secp256r1_hash, zkvm_secp256r1_pubkey, zkvm_secp256r1_signature, zkvm_secp256r1_verify, +}; +pub use sha256::{zkvm_sha256, zkvm_sha256_hash}; +pub use types::{zkvm_status, ZKVM_EFAIL, ZKVM_EOK}; diff --git a/crates/accelerators/src/modexp.rs b/crates/accelerators/src/modexp.rs new file mode 100644 index 000000000..8319314e4 --- /dev/null +++ b/crates/accelerators/src/modexp.rs @@ -0,0 +1,139 @@ +//! Modular exponentiation accelerator. + +use alloc::{vec, vec::Vec}; + +use crate::types::{zkvm_status, ZKVM_EFAIL, ZKVM_EOK}; +use openvm_ecc_guest::algebra::{ExpBytes, IntMod, Reduce}; +use openvm_pairing::bn254 as bn; + +const BN_SCALAR_LEN: usize = 32; + +/// Compute `base^exp mod modulus`. +/// +/// # Safety +/// +/// Each non-empty input must be valid for its corresponding length, and `output` +/// must be valid for `mod_len` writes when `mod_len != 0`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_modexp( + base: *const u8, + base_len: usize, + exp: *const u8, + exp_len: usize, + modulus: *const u8, + mod_len: usize, + output: *mut u8, +) -> zkvm_status { + if (base.is_null() && base_len != 0) || + (exp.is_null() && exp_len != 0) || + (modulus.is_null() && mod_len != 0) || + (output.is_null() && mod_len != 0) + { + return ZKVM_EFAIL; + } + + // SAFETY: non-NULL pointers and lengths were checked above; the caller guarantees validity. + let base = + if base_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(base, base_len) } }; + // SAFETY: see above. + let exp = if exp_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(exp, exp_len) } }; + // SAFETY: see above. + let modulus = + if mod_len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(modulus, mod_len) } }; + let value = modexp(base, exp, modulus); + if mod_len != 0 { + // SAFETY: all input reads are complete and `output` is valid for `mod_len` writes. + unsafe { core::ptr::copy_nonoverlapping(value.as_ptr(), output, mod_len) }; + } + ZKVM_EOK +} + +fn modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { + let mut result = if is_bn254_fr(modulus) { + accelerated_modexp_bn254_fr(base, exp) + } else { + aurora_engine_modexp::modexp(base, exp, modulus) + }; + + let output_len = modulus.len(); + match result.len().cmp(&output_len) { + core::cmp::Ordering::Greater => { + let start = result.len() - output_len; + result.copy_within(start.., 0); + result.truncate(output_len); + } + core::cmp::Ordering::Less => { + let value_len = result.len(); + let padding = output_len - value_len; + result.resize(output_len, 0); + result.copy_within(0..value_len, padding); + result[..padding].fill(0); + } + core::cmp::Ordering::Equal => {} + } + result +} + +fn is_bn254_fr(modulus: &[u8]) -> bool { + let stripped = match modulus.iter().position(|&byte| byte != 0) { + Some(index) => &modulus[index..], + None => return false, + }; + stripped.len() == BN_SCALAR_LEN && stripped.iter().rev().eq(bn::Scalar::MODULUS.as_ref().iter()) +} + +fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { + let padded_len = base.len().next_multiple_of(BN_SCALAR_LEN).max(BN_SCALAR_LEN); + let mut padded = vec![0u8; padded_len]; + padded[padded_len - base.len()..].copy_from_slice(base); + let base_fr = bn::Scalar::reduce_be_bytes(&padded); + base_fr.exp_bytes(true, exp).to_be_bytes().as_ref().to_vec() +} + +#[cfg(test)] +mod tests { + use super::*; + + const BN254_FR: [u8; 32] = [ + 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, + 0x5d, 0x28, 0x33, 0xe8, 0x48, 0x79, 0xb9, 0x70, 0x91, 0x43, 0xe1, 0xf5, 0x93, 0xf0, 0x00, + 0x00, 0x01, + ]; + + fn check(base: &[u8], exp: &[u8]) { + let expected = aurora_engine_modexp::modexp(base, exp, &BN254_FR); + let actual = accelerated_modexp_bn254_fr(base, exp); + let mut expected_padded = vec![0u8; BN_SCALAR_LEN]; + let offset = BN_SCALAR_LEN - expected.len(); + expected_padded[offset..].copy_from_slice(&expected); + assert_eq!(actual, expected_padded, "base={base:?}, exp={exp:?}"); + } + + #[test] + fn recognizes_bn254_fr() { + assert!(is_bn254_fr(&BN254_FR)); + let mut padded = vec![0u8; 10]; + padded.extend_from_slice(&BN254_FR); + assert!(is_bn254_fr(&padded)); + assert!(!is_bn254_fr(&[0u8; 32])); + let mut wrong = BN254_FR; + *wrong.last_mut().unwrap() ^= 1; + assert!(!is_bn254_fr(&wrong)); + } + + #[test] + fn accelerated_bn254_fr_matches_software() { + for (base, exp) in [ + (&[3][..], &[5][..]), + (&[0], &[5]), + (&[3], &[0]), + (&[][..], &[][..]), + (&[0xff; 32], &[1]), + (&[0xab; 64], &[3]), + (&[0x42; 100], &[2]), + (&[2], &[0xff; 32]), + ] { + check(base, exp); + } + } +} diff --git a/crates/accelerators/src/ops/blake2/mod.rs b/crates/accelerators/src/ops/blake2/mod.rs deleted file mode 100644 index 800b08e69..000000000 --- a/crates/accelerators/src/ops/blake2/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! BLAKE2b compression function F (EIP-152). -//! -//! Operates on raw BLAKE2b state with an arbitrary round count. - -mod portable; - -type Word = u64; - -const IV: [Word; 8] = [ - 0x6A09E667F3BCC908, - 0xBB67AE8584CAA73B, - 0x3C6EF372FE94F82B, - 0xA54FF53A5F1D36F1, - 0x510E527FADE682D1, - 0x9B05688C2B3E6C1F, - 0x1F83D9ABFB41BD6B, - 0x5BE0CD19137E2179, -]; - -// The message schedule has period 10 (RFC 7693 section 2.7). EIP-152 permits -// arbitrary round counts, so rounds beyond the standard 12 must use `r % 10`. -const SIGMA: [[u8; 16]; 10] = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], - [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], - [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], - [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], - [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], - [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], - [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], - [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], - [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], -]; - -/// Apply BLAKE2 compression to word-oriented state without byte conversion. -#[inline] -pub fn blake2f(rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { - portable::compress(rounds, h, m, t, f); -} diff --git a/crates/accelerators/src/ops/blake2/portable.rs b/crates/accelerators/src/ops/blake2/portable.rs deleted file mode 100644 index 9bc4cc1db..000000000 --- a/crates/accelerators/src/ops/blake2/portable.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Ported from revm-precompile 36.0.3's EIP-152 adaptation: -// https://docs.rs/crate/revm-precompile/36.0.3/source/src/blake2/portable.rs -// That implementation is adapted from blake2b_simd: -// https://github.com/oconnor663/blake2_simd -// -// Copyright (c) 2018 Jack O'Connor -// Copyright (c) 2021-2026 draganrakita -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use super::{Word, IV, SIGMA}; - -#[inline(always)] -const fn g(v: &mut [Word; 16], a: usize, b: usize, c: usize, d: usize, x: Word, y: Word) { - v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); - v[d] = (v[d] ^ v[a]).rotate_right(32); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(24); - v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); - v[d] = (v[d] ^ v[a]).rotate_right(16); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(63); -} - -#[inline(always)] -const fn round(round: usize, m: &[Word; 16], v: &mut [Word; 16]) { - let schedule = SIGMA[round % SIGMA.len()]; - - g(v, 0, 4, 8, 12, m[schedule[0] as usize], m[schedule[1] as usize]); - g(v, 1, 5, 9, 13, m[schedule[2] as usize], m[schedule[3] as usize]); - g(v, 2, 6, 10, 14, m[schedule[4] as usize], m[schedule[5] as usize]); - g(v, 3, 7, 11, 15, m[schedule[6] as usize], m[schedule[7] as usize]); - - g(v, 0, 5, 10, 15, m[schedule[8] as usize], m[schedule[9] as usize]); - g(v, 1, 6, 11, 12, m[schedule[10] as usize], m[schedule[11] as usize]); - g(v, 2, 7, 8, 13, m[schedule[12] as usize], m[schedule[13] as usize]); - g(v, 3, 4, 9, 14, m[schedule[14] as usize], m[schedule[15] as usize]); -} - -pub(super) fn compress(rounds: u32, h: &mut [Word; 8], m: &[Word; 16], t: &[Word; 2], f: bool) { - let mut v = [ - h[0], - h[1], - h[2], - h[3], - h[4], - h[5], - h[6], - h[7], - IV[0], - IV[1], - IV[2], - IV[3], - IV[4] ^ t[0], - IV[5] ^ t[1], - IV[6] ^ if f { Word::MAX } else { 0 }, - IV[7], - ]; - - for round_index in 0..rounds as usize { - round(round_index, m, &mut v); - } - - for (index, word) in h.iter_mut().enumerate() { - *word ^= v[index] ^ v[index + 8]; - } -} diff --git a/crates/accelerators/src/ops/bls12_381/codec.rs b/crates/accelerators/src/ops/bls12_381/codec.rs deleted file mode 100644 index 7f72feeaf..000000000 --- a/crates/accelerators/src/ops/bls12_381/codec.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! 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 super::BLS_FP_LEN; -use crate::ops::{BlsG1, BlsG2, Error}; - -#[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: &BlsG1) -> Result { - let px = read_bls_fp(&point.0)?; - let py = read_bls_fp(&point.1)?; - // 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: &BlsG1) -> 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: &BlsG2) -> Result { - let x = read_bls_fp2(&point.0, &point.1)?; - let y = read_bls_fp2(&point.2, &point.3)?; - // 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: &BlsG2) -> 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: &[u8; 32]) -> bls::Scalar { - bls::Scalar::from_be_bytes_unchecked(input) -} - -#[inline] -pub(super) fn encode_bls_g1_point(point: &bls::G1Affine) -> [u8; 96] { - let mut output = [0; 96]; - if point.is_identity() { - return output; - } - - 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[i] = x_bytes[BLS_FP_LEN - 1 - i]; - output[i + BLS_FP_LEN] = y_bytes[BLS_FP_LEN - 1 - i]; - } - output -} - -#[inline] -pub(super) fn encode_bls_g2_point(point: &bls::G2Affine) -> [u8; 192] { - let mut output = [0; 192]; - if point.is_identity() { - return output; - } - - 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[i] = x_c0[BLS_FP_LEN - 1 - i]; - output[i + BLS_FP_LEN] = x_c1[BLS_FP_LEN - 1 - i]; - output[i + (2 * BLS_FP_LEN)] = y_c0[BLS_FP_LEN - 1 - i]; - output[i + (3 * BLS_FP_LEN)] = y_c1[BLS_FP_LEN - 1 - i]; - } - output -} diff --git a/crates/accelerators/src/ops/bls12_381/map.rs b/crates/accelerators/src/ops/bls12_381/map.rs deleted file mode 100644 index 5fba2b2dd..000000000 --- a/crates/accelerators/src/ops/bls12_381/map.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! BLS12-381 map-to-curve (EIP-2537). -//! -//! Implemented using arkworks. - -use ark_bls12_381::{Fq, Fq2, G1Affine, G2Affine}; -use ark_ec::{ - hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve}, - AffineRepr, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; - -use super::BLS_FP_LEN; -use crate::ops::Error; - -/// BLS12-381 map field element to G1 (precompile 0x10). -#[inline] -pub fn bls12_381_map_fp_to_g1(fp: &[u8; 48]) -> Result<[u8; 96], Error> { - let fp = read_fq(fp)?; - let point = WBMap::map_to_curve(fp) - .expect("the arkworks WB map is defined for every field element") - .clear_cofactor(); - - Ok(encode_g1_point(&point)) -} - -/// BLS12-381 map field element to G2 (precompile 0x11). -#[inline] -pub fn bls12_381_map_fp2_to_g2(fp2: &([u8; 48], [u8; 48])) -> Result<[u8; 192], Error> { - let c0 = read_fq(&fp2.0)?; - let c1 = read_fq(&fp2.1)?; - let point = WBMap::map_to_curve(Fq2::new(c0, c1)) - .expect("the arkworks WB map is defined for every field element") - .clear_cofactor(); - - Ok(encode_g2_point(&point)) -} - -/// Reads a big-endian field element, rejecting non-canonical encodings. -fn read_fq(input_be: &[u8]) -> Result { - let mut input_le = [0u8; BLS_FP_LEN]; - input_le.copy_from_slice(input_be); - input_le.reverse(); - - Fq::deserialize_uncompressed(&input_le[..]).map_err(|_| Error::FieldElementInvalid) -} - -/// Writes a field element as big-endian bytes. -fn encode_fq(fq: &Fq, output: &mut [u8]) { - fq.serialize_uncompressed(&mut output[..]).expect("field element serialization is infallible"); - output.reverse(); -} - -/// Writes a G1 point as `x || y`; the point at infinity encodes as zeros. -fn encode_g1_point(point: &G1Affine) -> [u8; 96] { - let mut output = [0; 96]; - let Some((x, y)) = point.xy() else { - return output; - }; - - encode_fq(&x, &mut output[..BLS_FP_LEN]); - encode_fq(&y, &mut output[BLS_FP_LEN..]); - output -} - -/// Writes a G2 point as `x_c0 || x_c1 || y_c0 || y_c1`; the point at infinity -/// encodes as zeros. -fn encode_g2_point(point: &G2Affine) -> [u8; 192] { - let mut output = [0; 192]; - let Some((x, y)) = point.xy() else { - return output; - }; - - encode_fq(&x.c0, &mut output[..BLS_FP_LEN]); - encode_fq(&x.c1, &mut output[BLS_FP_LEN..2 * BLS_FP_LEN]); - encode_fq(&y.c0, &mut output[2 * BLS_FP_LEN..3 * BLS_FP_LEN]); - encode_fq(&y.c1, &mut output[3 * BLS_FP_LEN..]); - output -} diff --git a/crates/accelerators/src/ops/bls12_381/mod.rs b/crates/accelerators/src/ops/bls12_381/mod.rs deleted file mode 100644 index dcd2cd905..000000000 --- a/crates/accelerators/src/ops/bls12_381/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! BLS12-381 group operations (EIP-2537). - -mod codec; -mod map; - -pub use map::{bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1}; - -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::{BlsG1, BlsG2, Error, StreamError}; - -/// The number of bytes needed to represent an element of the base field Fp. -const BLS_FP_LEN: usize = 48; - -/// 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. -#[inline] -pub fn bls12_381_g1_add(p1: &BlsG1, p2: &BlsG1) -> Result<[u8; 96], Error> { - let p1 = read_bls_g1_point_no_subgroup_check(p1)?; - let p2 = read_bls_g1_point_no_subgroup_check(p2)?; - Ok(encode_bls_g1_point(&(p1 + p2))) -} - -/// 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: impl IntoIterator>, -) -> Result<[u8; 96], StreamError> { - let pairs = pairs.into_iter(); - let capacity = pairs.size_hint().0; - - let mut points = Vec::with_capacity(capacity); - let mut scalars = Vec::with_capacity(capacity); - for pair in pairs { - let (point, scalar) = pair.map_err(StreamError::Source)?; - points.push(read_bls_g1_point(&point).map_err(StreamError::Operation)?); - scalars.push(read_bls_scalar(&scalar)); - } - if points.is_empty() { - Ok([0; 96]) - } else { - Ok(encode_bls_g1_point(&Bls12_381::msm(&scalars, &points))) - } -} - -/// BLS12-381 G2 point addition (precompile 0x0d). -/// -/// Per EIP-2537 G2ADD, inputs are validated on-curve only, not for subgroup -/// membership. -#[inline] -pub fn bls12_381_g2_add(p1: &BlsG2, p2: &BlsG2) -> Result<[u8; 192], Error> { - let p1 = read_bls_g2_point_no_subgroup_check(p1)?; - let p2 = read_bls_g2_point_no_subgroup_check(p2)?; - Ok(encode_bls_g2_point(&(p1 + p2))) -} - -/// 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: impl IntoIterator>, -) -> Result<[u8; 192], StreamError> { - let pairs = pairs.into_iter(); - let capacity = pairs.size_hint().0; - - let mut points = Vec::with_capacity(capacity); - let mut scalars = Vec::with_capacity(capacity); - for pair in pairs { - let (point, scalar) = pair.map_err(StreamError::Source)?; - points.push(read_bls_g2_point(&point).map_err(StreamError::Operation)?); - scalars.push(read_bls_scalar(&scalar)); - } - if points.is_empty() { - Ok([0; 192]) - } else { - Ok(encode_bls_g2_point(&openvm_ecc_guest::msm(&scalars, &points))) - } -} - -/// BLS12-381 pairing check (precompile 0x0f). -/// -/// Points must be in the prime-order subgroup. -pub fn bls12_381_pairing_check( - pairs: impl IntoIterator, -) -> Result { - let pairs = pairs.into_iter(); - let capacity = pairs.size_hint().0; - - let mut g1_points = Vec::with_capacity(capacity); - let mut g2_points = Vec::with_capacity(capacity); - - for (g1, g2) in pairs { - let g1 = read_bls_g1_point(&g1).map_err(|error| match error { - Error::PointNotOnCurve => Error::BlsG1PointNotOnCurve, - Error::PointNotInSubgroup => Error::BlsG1PointNotInSubgroup, - error => error, - })?; - let g2 = read_bls_g2_point(&g2).map_err(|error| match error { - Error::PointNotOnCurve => Error::BlsG2PointNotOnCurve, - Error::PointNotInSubgroup => Error::BlsG2PointNotInSubgroup, - error => error, - })?; - - 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)); - } - - if g1_points.is_empty() { - return Ok(true); - } - Ok(Bls12_381::pairing_check(&g1_points, &g2_points).is_ok()) -} diff --git a/crates/accelerators/src/ops/bn254/codec.rs b/crates/accelerators/src/ops/bn254/codec.rs deleted file mode 100644 index 4ac9b7439..000000000 --- a/crates/accelerators/src/ops/bn254/codec.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! 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; - -const BN_FQ_LEN: usize = 32; -const BN_G1_LEN: usize = BN_FQ_LEN * 2; -const BN_G2_LEN: usize = BN_G1_LEN * 2; - -#[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: &[u8]) -> Result { - if input.len() != BN_G1_LEN { - return Err(Error::InvalidLength); - } - let px = read_bn_fq(&input[..BN_FQ_LEN])?; - let py = read_bn_fq(&input[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: &[u8]) -> Result { - if input.len() != BN_G2_LEN { - return Err(Error::InvalidLength); - } - let x = read_bn_fq2(&input[..BN_G1_LEN])?; - let y = read_bn_fq2(&input[BN_G1_LEN..])?; - // 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: &[u8]) -> Result { - if input.len() != BN_FQ_LEN { - return Err(Error::InvalidLength); - } - Ok(bn::Scalar::from_be_bytes_unchecked(input)) -} - -#[inline] -pub(super) fn encode_bn_g1_point(point: bn::G1Affine) -> [u8; BN_G1_LEN] { - let mut output = [0; BN_G1_LEN]; - 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[i] = x_bytes[BN_FQ_LEN - 1 - i]; - output[i + BN_FQ_LEN] = y_bytes[BN_FQ_LEN - 1 - i]; - } - output -} diff --git a/crates/accelerators/src/ops/bn254/mod.rs b/crates/accelerators/src/ops/bn254/mod.rs deleted file mode 100644 index 394752f6d..000000000 --- a/crates/accelerators/src/ops/bn254/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! 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; - -/// BN254 G1 point addition (precompile 0x06). -pub fn bn254_g1_add(p1: &[u8], p2: &[u8]) -> Result<[u8; 64], Error> { - let p1 = read_bn_g1_point(p1)?; - let p2 = read_bn_g1_point(p2)?; - Ok(encode_bn_g1_point(p1 + p2)) -} - -/// BN254 G1 scalar multiplication (precompile 0x07). -pub fn bn254_g1_mul(point: &[u8], scalar: &[u8]) -> Result<[u8; 64], Error> { - let p = read_bn_g1_point(point)?; - let s = read_bn_scalar(scalar)?; - Ok(encode_bn_g1_point(Bn254::msm(&[s], &[p]))) -} - -/// BN254 pairing check (precompile 0x08). -pub fn bn254_pairing_check<'a>( - pairs: impl IntoIterator, -) -> Result { - let pairs = pairs.into_iter(); - let capacity = pairs.size_hint().0; - - let mut g1_points = Vec::with_capacity(capacity); - let mut g2_points = Vec::with_capacity(capacity); - - for (g1, g2) in pairs { - let g1 = read_bn_g1_point(g1)?; - let g2 = read_bn_g2_point(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)); - } - - if g1_points.is_empty() { - return Ok(true); - } - Ok(Bn254::pairing_check(&g1_points, &g2_points).is_ok()) -} diff --git a/crates/accelerators/src/ops/ecdsa/mod.rs b/crates/accelerators/src/ops/ecdsa/mod.rs deleted file mode 100644 index e25e45ee3..000000000 --- a/crates/accelerators/src/ops/ecdsa/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! ECDSA operations. -//! -//! Split by curve: the two crates providing them use the same type names, and -//! secp256k1 additionally needs a guest/host split that secp256r1 does not. - -mod secp256k1; -mod secp256r1; - -pub use secp256k1::{secp256k1_ecrecover, secp256k1_verify}; -pub use secp256r1::secp256r1_verify; diff --git a/crates/accelerators/src/ops/ecdsa/secp256k1.rs b/crates/accelerators/src/ops/ecdsa/secp256k1.rs deleted file mode 100644 index 8ee8ca338..000000000 --- a/crates/accelerators/src/ops/ecdsa/secp256k1.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! ECDSA over the secp256k1 curve. - -// In the guest, secp256k1 operations use the OpenVM-accelerated k256; on -// the host they use upstream RustCrypto k256 (the ECDSA recovery -// relies on zkVM hints and is unimplemented outside the guest). -#[cfg(any(target_os = "none", target_os = "openvm"))] -use openvm_k256 as k256; - -use k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; - -use crate::ops::Error; - -/// Recover the uncompressed secp256k1 public key from an ECDSA signature -/// over `msg`. -/// -/// Both low-s and high-s signatures are accepted. -pub fn secp256k1_ecrecover( - msg: &[u8; 32], - sig: &[u8; 64], - mut recid: u8, -) -> Result<[u8; 64], Error> { - let mut signature = Signature::from_slice(sig).map_err(|_| Error::InvalidSignature)?; - // k256 requires a low-s signature for recovery; normalizing flips the - // recovery id parity but recovers the same key. - if let Some(normalized) = signature.normalize_s() { - signature = normalized; - recid ^= 1; - } - let recovery_id = RecoveryId::from_byte(recid).ok_or(Error::InvalidSignature)?; - - #[cfg(any(target_os = "none", target_os = "openvm"))] - let key = VerifyingKey::recover_from_prehash_noverify(msg, &signature.to_bytes(), recovery_id) - .map_err(|_| Error::InvalidSignature)?; - #[cfg(not(any(target_os = "none", target_os = "openvm")))] - let key = VerifyingKey::recover_from_prehash(msg, &signature, recovery_id) - .map_err(|_| Error::InvalidSignature)?; - - let point = key.to_encoded_point(false); - Ok(point.as_bytes()[1..65].try_into().unwrap()) -} - -/// Verify an ECDSA signature over secp256k1 against an uncompressed public key. -/// -/// Both low-s and high-s signatures are accepted. -pub fn secp256k1_verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { - let mut sec1 = [0u8; 65]; - sec1[0] = 0x04; - sec1[1..].copy_from_slice(pubkey); - let Ok(key) = VerifyingKey::from_sec1_bytes(&sec1) else { - return false; - }; - let Ok(mut signature) = Signature::from_slice(sig) else { - return false; - }; - // k256 rejects high-s signatures in verification. Normalize to accept both forms. - if let Some(normalized) = signature.normalize_s() { - signature = normalized; - } - key.verify_prehash(msg, &signature).is_ok() -} diff --git a/crates/accelerators/src/ops/ecdsa/secp256r1.rs b/crates/accelerators/src/ops/ecdsa/secp256r1.rs deleted file mode 100644 index 3a355a36a..000000000 --- a/crates/accelerators/src/ops/ecdsa/secp256r1.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! ECDSA over the secp256r1 (P-256) curve. - -use openvm_p256::{ - ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}, - EncodedPoint, -}; - -/// Verify an ECDSA signature over secp256r1 (P-256) against an uncompressed public key. -pub fn secp256r1_verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { - let encoded_point = EncodedPoint::from_untagged_bytes(&(*pubkey).into()); - let Ok(key) = VerifyingKey::from_encoded_point(&encoded_point) else { - return false; - }; - let Ok(signature) = Signature::from_slice(sig) else { - return false; - }; - key.verify_prehash(msg, &signature).is_ok() -} diff --git a/crates/accelerators/src/ops/hash.rs b/crates/accelerators/src/ops/hash.rs deleted file mode 100644 index 847186648..000000000 --- a/crates/accelerators/src/ops/hash.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Hash operations. - -/// Compute the Keccak-256 hash of `data`. -#[inline] -pub fn keccak256(data: &[u8]) -> [u8; 32] { - openvm_keccak256::keccak256(data) -} - -/// Compute the SHA-256 hash of `data`. -#[inline] -pub fn sha256(data: &[u8]) -> [u8; 32] { - #[cfg(not(openvm_intrinsics))] - use openvm_sha2::Digest; - openvm_sha2::Sha256::digest(data).into() -} - -/// Compute the RIPEMD-160 hash of `data`. -/// -/// The 20-byte digest is written to the final 20 bytes; the first 12 bytes are -/// zeroed, matching the EVM word layout. -#[inline] -pub fn ripemd160(data: &[u8]) -> [u8; 32] { - use ripemd::Digest; - let mut hasher = ripemd::Ripemd160::new(); - hasher.update(data); - - let mut output = [0; 32]; - hasher.finalize_into((&mut output[12..]).into()); - output -} diff --git a/crates/accelerators/src/ops/kzg.rs b/crates/accelerators/src/ops/kzg.rs deleted file mode 100644 index d7df493fb..000000000 --- a/crates/accelerators/src/ops/kzg.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! KZG point-evaluation proof verification (EIP-4844). - -use openvm_kzg::{Bytes32, Bytes48, KzgProof}; - -use crate::ops::Error; - -/// Verify a KZG proof that the blob committed to by `commitment` evaluates -/// to `y` at point `z`. -/// -/// Errors mean the check could not run — a commitment or proof that is not -/// a valid compressed G1 point, or an out-of-range field element. `Ok(false)` -/// means a well-formed proof did not verify. -pub fn kzg_point_eval( - commitment: &[u8; 48], - z: &[u8; 32], - y: &[u8; 32], - proof: &[u8; 48], -) -> Result { - let env = openvm_kzg::EnvKzgSettings::default(); - let kzg_settings = env.get(); - - let commitment = Bytes48::from_slice(commitment).map_err(|_| Error::KzgInvalidInput)?; - let z = Bytes32::from_slice(z).map_err(|_| Error::KzgInvalidInput)?; - let y = Bytes32::from_slice(y).map_err(|_| Error::KzgInvalidInput)?; - let proof = Bytes48::from_slice(proof).map_err(|_| Error::KzgInvalidInput)?; - - KzgProof::verify_kzg_proof(&commitment, &z, &y, &proof, kzg_settings) - .map_err(|_| Error::KzgInvalidInput) -} diff --git a/crates/accelerators/src/ops/mod.rs b/crates/accelerators/src/ops/mod.rs deleted file mode 100644 index b43fff455..000000000 --- a/crates/accelerators/src/ops/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! OpenVM-accelerated implementations of the zkVM accelerator operations. -//! -//! Functions use ordinary Rust arrays, slices and tuples. BLS12-381 G2 is -//! `(x_c0, x_c1, y_c0, y_c1)`; BN254 G2 byte slices use the EIP-197 -//! `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_map_fp2_to_g2, bls12_381_map_fp_to_g1, 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; -pub use modexp::modexp; - -/// Uncompressed BLS12-381 G1 coordinates `(x, y)`, big-endian. -pub type BlsG1 = ([u8; 48], [u8; 48]); - -/// Uncompressed BLS12-381 G2 coordinates `(x_c0, x_c1, y_c0, y_c1)`, big-endian. -pub type BlsG2 = ([u8; 48], [u8; 48], [u8; 48], [u8; 48]); - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum StreamError { - /// The input iterator produced an error. - Source(E), - /// An accelerator operation rejected an input. - Operation(Error), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Error { - /// An input does not have the length required by the operation. - InvalidLength, - /// A field element is out of range or otherwise not a field member. - FieldElementInvalid, - /// A point encoding does not satisfy the curve equation. - PointNotOnCurve, - /// A point is on the curve but not in the prime-order subgroup. - PointNotInSubgroup, - /// A BLS12-381 pairing G1 point does not satisfy the curve equation. - BlsG1PointNotOnCurve, - /// A BLS12-381 pairing G1 point is not in the prime-order subgroup. - BlsG1PointNotInSubgroup, - /// A BLS12-381 pairing G2 point does not satisfy the curve equation. - BlsG2PointNotOnCurve, - /// A BLS12-381 pairing G2 point is not in the prime-order subgroup. - BlsG2PointNotInSubgroup, - /// A signature could not be parsed or key recovery failed. - InvalidSignature, - /// KZG commitment/proof/field-element inputs are malformed. - KzgInvalidInput, -} diff --git a/crates/accelerators/src/ops/modexp.rs b/crates/accelerators/src/ops/modexp.rs deleted file mode 100644 index 223d87aff..000000000 --- a/crates/accelerators/src/ops/modexp.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Modular exponentiation with a BN254-Fr fast path. - -use alloc::{vec, vec::Vec}; - -use openvm_ecc_guest::algebra::{ExpBytes, IntMod, Reduce}; -use openvm_pairing::bn254 as bn; - -/// The number of bytes needed to represent an element of BN254's scalar -/// field Fr. -const BN_SCALAR_LEN: usize = 32; - -/// Compute `base^exp % modulus`, returning exactly `modulus.len()` bytes. -pub fn modexp(base: &[u8], exp: &[u8], modulus: &[u8]) -> Vec { - let mut result = if is_bn254_fr(modulus) { - accelerated_modexp_bn254_fr(base, exp) - } else { - aurora_engine_modexp::modexp(base, exp, modulus) - }; - - // The result is numerically reduced, but its byte representation may not - // be modulus-sized. Reuse its allocation while right-aligning it. - let output_len = modulus.len(); - match result.len().cmp(&output_len) { - core::cmp::Ordering::Greater => { - let start = result.len() - output_len; - result.copy_within(start.., 0); - result.truncate(output_len); - } - core::cmp::Ordering::Less => { - let value_len = result.len(); - let padding = output_len - value_len; - result.resize(output_len, 0); - result.copy_within(0..value_len, padding); - result[..padding].fill(0); - } - core::cmp::Ordering::Equal => {} - } - result -} - -/// Returns true if the modulus (big-endian, possibly with leading zeros) equals BN254 Fr. -fn is_bn254_fr(modulus: &[u8]) -> bool { - // Strip leading zeros - let stripped = match modulus.iter().position(|&b| b != 0) { - Some(i) => &modulus[i..], - None => return false, // all zeros - }; - // bn::Scalar::MODULUS is little-endian; compare against reversed input - stripped.len() == BN_SCALAR_LEN && stripped.iter().rev().eq(bn::Scalar::MODULUS.as_ref().iter()) -} - -/// Accelerated modexp for BN254 Fr using field arithmetic intrinsics. -fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec { - // OpenVM's field reduction requires inputs to be aligned to the field byte size. - let padded_len = base.len().next_multiple_of(BN_SCALAR_LEN).max(BN_SCALAR_LEN); - let mut padded = vec![0u8; padded_len]; - padded[padded_len - base.len()..].copy_from_slice(base); - let base_fr = bn::Scalar::reduce_be_bytes(&padded); - - base_fr.exp_bytes(true, exp).to_be_bytes().as_ref().to_vec() -} - -#[cfg(test)] -mod tests { - use super::*; - - /// EIP-197 BN254 scalar-field modulus, independently specified in big-endian order. - const BN254_FR: [u8; 32] = [ - 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, - 0x5d, 0x28, 0x33, 0xe8, 0x48, 0x79, 0xb9, 0x70, 0x91, 0x43, 0xe1, 0xf5, 0x93, 0xf0, 0x00, - 0x00, 0x01, - ]; - - /// Helper: run the accelerated path and compare against the aurora - /// reference. The accelerated path always returns BN_SCALAR_LEN bytes, - /// so the reference output is left-padded to match. - fn check(base: &[u8], exp: &[u8]) { - let expected = aurora_engine_modexp::modexp(base, exp, &BN254_FR); - let actual = accelerated_modexp_bn254_fr(base, exp); - let mut expected_padded = vec![0u8; BN_SCALAR_LEN]; - let offset = BN_SCALAR_LEN - expected.len(); - expected_padded[offset..].copy_from_slice(&expected); - assert_eq!(actual, expected_padded, "base={base:?}, exp={exp:?}"); - } - - #[test] - fn test_is_bn254_fr() { - // Exact modulus - assert!(is_bn254_fr(&BN254_FR)); - - // With leading zeros - let mut padded = vec![0u8; 10]; - padded.extend_from_slice(&BN254_FR); - assert!(is_bn254_fr(&padded)); - - // All zeros → false - assert!(!is_bn254_fr(&[0u8; 32])); - - // Wrong modulus (flip last bit) - let mut m = BN254_FR; - *m.last_mut().unwrap() ^= 1; - assert!(!is_bn254_fr(&m)); - } - - #[test] - fn test_accelerated_modexp_bn254_fr() { - // --- short base (<=32 bytes), value < modulus --- - check(&[3], &[5]); // 3^5 mod Fr - check(&[0], &[5]); // 0^5 = 0 - check(&[3], &[0]); // 3^0 = 1 - check(&[0], &[0]); // 0^0 = 1 by convention - check(&[], &[]); // empty inputs - check(&[0, 0, 0, 3], &[5]); // leading zeros in base - - // --- short base, value >= modulus (triggers the reduce fallback) --- - check(&BN254_FR, &[1]); // Fr mod Fr = 0, so 0^1 = 0 - let mut m_plus_1 = BN254_FR; - *m_plus_1.last_mut().unwrap() += 1; - check(&m_plus_1, &[2]); // (Fr+1)^2 mod Fr = 1 - check(&[0xff; 32], &[1]); // max 256-bit value, >= modulus - - // --- large base (> 32 bytes, reduce_be_bytes path) --- - check(&[0xab; 64], &[3]); // aligned (multiple of 32) - check(&[0x42; 100], &[2]); // unaligned (tests the padding) - check(&[0xab; 64], &[0xff; 32]); // large base + large exponent - - // --- larger exponents --- - check(&[2], &[0xff; 32]); // 2^(2^256-1) mod Fr - check(&[2], &[0, 0, 0, 5]); // leading zeros in exponent - check(&[3], &[0xab; 64]); // exponent > 32 bytes - - // --- same value through both base-parsing code paths --- - let base_32 = [0xab; 32]; - let mut base_33 = vec![0u8]; - base_33.extend_from_slice(&base_32); - let exp = &[7]; - assert_eq!( - accelerated_modexp_bn254_fr(&base_32, exp), - accelerated_modexp_bn254_fr(&base_33, exp), - "33-byte base with leading zero must match 32-byte base" - ); - } -} diff --git a/crates/accelerators/src/ripemd160.rs b/crates/accelerators/src/ripemd160.rs new file mode 100644 index 000000000..339f7ded2 --- /dev/null +++ b/crates/accelerators/src/ripemd160.rs @@ -0,0 +1,42 @@ +//! RIPEMD-160 accelerator. + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; + +pub type zkvm_ripemd160_hash = ZkvmBytes<32>; + +/// Compute the RIPEMD-160 hash of `data[..len]` into `output`. +/// +/// A NULL `data` pointer is accepted only when `len == 0`. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of one [`zkvm_ripemd160_hash`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_ripemd160( + data: *const u8, + len: usize, + output: *mut zkvm_ripemd160_hash, +) -> zkvm_status { + if output.is_null() || (data.is_null() && len != 0) { + return ZKVM_EFAIL; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + let value = zkvm_ripemd160_hash { data: ripemd160(data) }; + // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so + // overlapping input/output storage is supported. + unsafe { output.write(value) }; + ZKVM_EOK +} + +#[inline] +fn ripemd160(data: &[u8]) -> [u8; 32] { + use ripemd::Digest; + let mut hasher = ripemd::Ripemd160::new(); + hasher.update(data); + + let mut output = [0; 32]; + hasher.finalize_into((&mut output[12..]).into()); + output +} diff --git a/crates/accelerators/src/secp256k1.rs b/crates/accelerators/src/secp256k1.rs new file mode 100644 index 000000000..bed812a20 --- /dev/null +++ b/crates/accelerators/src/secp256k1.rs @@ -0,0 +1,95 @@ +//! secp256k1 ECDSA accelerator. + +#[cfg(any(target_os = "none", target_os = "openvm"))] +use openvm_k256 as k256; + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +use k256::ecdsa::{signature::hazmat::PrehashVerifier, RecoveryId, Signature, VerifyingKey}; + +pub type zkvm_secp256k1_hash = ZkvmBytes<32>; +pub type zkvm_secp256k1_signature = ZkvmBytes<64>; +pub type zkvm_secp256k1_pubkey = ZkvmBytes<64>; + +/// Recover an uncompressed public key from a signature and recovery ID. +/// +/// # Safety +/// +/// Every non-NULL pointer must be valid for a read or write of its pointed-to type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256k1_ecrecover( + msg: *const zkvm_secp256k1_hash, + sig: *const zkvm_secp256k1_signature, + recid: u8, + output: *mut zkvm_secp256k1_pubkey, +) -> zkvm_status { + if msg.is_null() || sig.is_null() || output.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that the non-NULL inputs are valid for reads. Copying both + // inputs before writing supports overlap with `output`. + let (msg, sig) = unsafe { (msg.read(), sig.read()) }; + let Some(data) = recover(&msg.data, &sig.data, recid) else { + return ZKVM_EFAIL; + }; + // SAFETY: `output` is non-NULL and valid for writes. + unsafe { output.write(zkvm_secp256k1_pubkey { data }) }; + ZKVM_EOK +} + +/// Verify a signature, writing `false` for malformed or invalid cryptographic inputs. +/// +/// # Safety +/// +/// Every non-NULL pointer must be valid for a read or write of its pointed-to type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256k1_verify( + msg: *const zkvm_secp256k1_hash, + sig: *const zkvm_secp256k1_signature, + pubkey: *const zkvm_secp256k1_pubkey, + verified: *mut bool, +) -> zkvm_status { + if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that the non-NULL inputs are valid for reads. Copying every + // input before writing supports overlap with `verified`. + let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; + let value = verify(&msg.data, &sig.data, &pubkey.data); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZKVM_EOK +} + +fn recover(msg: &[u8; 32], sig: &[u8; 64], mut recid: u8) -> Option<[u8; 64]> { + let mut signature = Signature::from_slice(sig).ok()?; + // k256 recovery requires low-s; changing s to -s also flips the recovery-ID parity. + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + recid ^= 1; + } + let recovery_id = RecoveryId::from_byte(recid)?; + + #[cfg(any(target_os = "none", target_os = "openvm"))] + let key = VerifyingKey::recover_from_prehash_noverify(msg, &signature.to_bytes(), recovery_id) + .ok()?; + #[cfg(not(any(target_os = "none", target_os = "openvm")))] + let key = VerifyingKey::recover_from_prehash(msg, &signature, recovery_id).ok()?; + + key.to_encoded_point(false).as_bytes().get(1..65)?.try_into().ok() +} + +fn verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { + let mut sec1 = [0u8; 65]; + sec1[0] = 0x04; + sec1[1..].copy_from_slice(pubkey); + let Ok(key) = VerifyingKey::from_sec1_bytes(&sec1) else { + return false; + }; + let Ok(mut signature) = Signature::from_slice(sig) else { + return false; + }; + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + } + key.verify_prehash(msg, &signature).is_ok() +} diff --git a/crates/accelerators/src/secp256r1.rs b/crates/accelerators/src/secp256r1.rs new file mode 100644 index 000000000..d8ae7608f --- /dev/null +++ b/crates/accelerators/src/secp256r1.rs @@ -0,0 +1,46 @@ +//! secp256r1 ECDSA accelerator. + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +use openvm_p256::{ + ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey}, + EncodedPoint, +}; + +pub type zkvm_secp256r1_hash = ZkvmBytes<32>; +pub type zkvm_secp256r1_signature = ZkvmBytes<64>; +pub type zkvm_secp256r1_pubkey = ZkvmBytes<64>; + +/// Verify a signature, writing `false` for malformed or invalid cryptographic inputs. +/// +/// # Safety +/// +/// Every non-NULL pointer must be valid for a read or write of its pointed-to type. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_secp256r1_verify( + msg: *const zkvm_secp256r1_hash, + sig: *const zkvm_secp256r1_signature, + pubkey: *const zkvm_secp256r1_pubkey, + verified: *mut bool, +) -> zkvm_status { + if msg.is_null() || sig.is_null() || pubkey.is_null() || verified.is_null() { + return ZKVM_EFAIL; + } + // SAFETY: the caller guarantees that the non-NULL inputs are valid for reads. Copying every + // input before writing supports overlap with `verified`. + let (msg, sig, pubkey) = unsafe { (msg.read(), sig.read(), pubkey.read()) }; + let value = verify(&msg.data, &sig.data, &pubkey.data); + // SAFETY: `verified` is non-NULL and valid for writes. + unsafe { verified.write(value) }; + ZKVM_EOK +} + +fn verify(msg: &[u8; 32], sig: &[u8; 64], pubkey: &[u8; 64]) -> bool { + let encoded_point = EncodedPoint::from_untagged_bytes(&(*pubkey).into()); + let Ok(key) = VerifyingKey::from_encoded_point(&encoded_point) else { + return false; + }; + let Ok(signature) = Signature::from_slice(sig) else { + return false; + }; + key.verify_prehash(msg, &signature).is_ok() +} diff --git a/crates/accelerators/src/sha256.rs b/crates/accelerators/src/sha256.rs new file mode 100644 index 000000000..4bb824bf2 --- /dev/null +++ b/crates/accelerators/src/sha256.rs @@ -0,0 +1,33 @@ +//! SHA-256 accelerator. + +use crate::types::{zkvm_status, ZkvmBytes, ZKVM_EFAIL, ZKVM_EOK}; +#[cfg(not(openvm_intrinsics))] +use openvm_sha2::Digest; + +pub type zkvm_sha256_hash = ZkvmBytes<32>; + +/// Compute the SHA-256 hash of `data[..len]` into `output`. +/// +/// A NULL `data` pointer is accepted only when `len == 0`. +/// +/// # Safety +/// +/// - `data`, if non-NULL, must be valid for reads of `len` bytes. +/// - `output`, if non-NULL, must be valid for writes of one [`zkvm_sha256_hash`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn zkvm_sha256( + data: *const u8, + len: usize, + output: *mut zkvm_sha256_hash, +) -> zkvm_status { + if output.is_null() || (data.is_null() && len != 0) { + return ZKVM_EFAIL; + } + // SAFETY: non-NULL checked above; validity is guaranteed by the caller. + let data = if len == 0 { &[] } else { unsafe { core::slice::from_raw_parts(data, len) } }; + let value = zkvm_sha256_hash { data: openvm_sha2::Sha256::digest(data).into() }; + // SAFETY: `output` is non-NULL and valid for writes. All input reads are complete, so + // overlapping input/output storage is supported. + unsafe { output.write(value) }; + ZKVM_EOK +} diff --git a/crates/accelerators/src/types.rs b/crates/accelerators/src/types.rs index b76b7da11..47cd563fc 100644 --- a/crates/accelerators/src/types.rs +++ b/crates/accelerators/src/types.rs @@ -1,150 +1,12 @@ -//! Types mirroring the complete interface standard header. +//! Types for the standard zkVM accelerator C interface. -/// Status code returned by every accelerator function (`zkvm_status`). -/// -/// Pinned to `i32` so the layout does not depend on target conventions. -#[repr(i32)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ZkvmStatus { - /// Success (`ZKVM_EOK`). - Ok = 0, - /// Failure (`ZKVM_EFAIL`). - Fail = -1, -} - -/// 16-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes16 { - pub data: [u8; 16], -} - -/// 32-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes32 { - pub data: [u8; 32], -} - -/// 48-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes48 { - pub data: [u8; 48], -} +pub type zkvm_status = core::ffi::c_int; -/// 64-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes64 { - pub data: [u8; 64], -} - -/// 96-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes96 { - pub data: [u8; 96], -} - -/// 128-byte buffer. -#[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes128 { - pub data: [u8; 128], -} +pub const ZKVM_EOK: zkvm_status = 0; +pub const ZKVM_EFAIL: zkvm_status = -1; -/// 192-byte buffer. #[repr(C, align(8))] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBytes192 { - pub data: [u8; 192], +#[derive(Clone, Copy, Debug)] +pub struct ZkvmBytes { + pub data: [u8; N], } - -/* Hash types */ -pub type ZkvmKeccak256Hash = ZkvmBytes32; -pub type ZkvmSha256Hash = ZkvmBytes32; -/// 20-byte hash padded to 32 bytes, first 12 bytes zero. -pub type ZkvmRipemd160Hash = ZkvmBytes32; - -/* secp256k1 types */ -pub type ZkvmSecp256k1Hash = ZkvmBytes32; -/// `r || s`, 32 bytes each, big-endian. -pub type ZkvmSecp256k1Signature = ZkvmBytes64; -/// uncompressed `x || y`, 32 bytes each, big-endian. -pub type ZkvmSecp256k1Pubkey = ZkvmBytes64; - -/* secp256r1 (P-256) types */ -pub type ZkvmSecp256r1Hash = ZkvmBytes32; -/// `r || s`, 32 bytes each, big-endian. -pub type ZkvmSecp256r1Signature = ZkvmBytes64; -/// uncompressed `x || y`, 32 bytes each, big-endian. -pub type ZkvmSecp256r1Pubkey = ZkvmBytes64; - -/* BN254 types */ -/// `x || y`, 32 bytes each, big-endian. -pub type ZkvmBn254G1Point = ZkvmBytes64; -/// `x_c1 || x_c0 || y_c1 || y_c0` (EIP-197 order). -pub type ZkvmBn254G2Point = ZkvmBytes128; -pub type ZkvmBn254Scalar = ZkvmBytes32; - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBn254PairingPair { - pub g1: ZkvmBn254G1Point, - pub g2: ZkvmBn254G2Point, -} - -/* BLS12-381 types */ -/// `x || y`, 48 bytes each, big-endian. -pub type ZkvmBls12381G1Point = ZkvmBytes96; -/// `x_c0 || x_c1 || y_c0 || y_c1` (EIP-2537 order). -pub type ZkvmBls12381G2Point = ZkvmBytes192; -pub type ZkvmBls12381Scalar = ZkvmBytes32; -pub type ZkvmBls12381Fp = ZkvmBytes48; -/// `c0 || c1`, 48 bytes each, big-endian. -pub type ZkvmBls12381Fp2 = ZkvmBytes96; - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBls12381G1MsmPair { - pub point: ZkvmBls12381G1Point, - pub scalar: ZkvmBls12381Scalar, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBls12381G2MsmPair { - pub point: ZkvmBls12381G2Point, - pub scalar: ZkvmBls12381Scalar, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ZkvmBls12381PairingPair { - pub g1: ZkvmBls12381G1Point, - pub g2: ZkvmBls12381G2Point, -} - -/* BLAKE2f types */ -/// 8 × u64 little-endian. -pub type ZkvmBlake2fState = ZkvmBytes64; -/// 16 × u64 little-endian. -pub type ZkvmBlake2fMessage = ZkvmBytes128; -/// 2 × u64 little-endian. -pub type ZkvmBlake2fOffset = ZkvmBytes16; - -/* KZG types */ -pub type ZkvmKzgCommitment = ZkvmBytes48; -pub type ZkvmKzgProof = ZkvmBytes48; -pub type ZkvmKzgFieldElement = ZkvmBytes32; - -// Pin the non-trivial aggregate layouts exposed by the canonical C header. -const _: () = { - use core::mem::size_of; - - assert!(size_of::() == 192); - assert!(size_of::() == 128); - assert!(size_of::() == 224); - assert!(size_of::() == 288); -}; diff --git a/crates/accelerators/tests/blake2.rs b/crates/accelerators/tests/blake2f.rs similarity index 54% rename from crates/accelerators/tests/blake2.rs rename to crates/accelerators/tests/blake2f.rs index 2c0c03a52..aeb287b3c 100644 --- a/crates/accelerators/tests/blake2.rs +++ b/crates/accelerators/tests/blake2f.rs @@ -1,10 +1,9 @@ -//! BLAKE2f conformance: the official EIP-152 test vectors 4-7. - -#![cfg(feature = "ffi")] +//! BLAKE2f conformance using the official EIP-152 test vectors 4-7. use hex_literal::hex; use openvm_accelerators::{ - blake2f, zkvm_blake2f, ZkvmBlake2fMessage, ZkvmBlake2fOffset, ZkvmBlake2fState, ZkvmStatus, + zkvm_blake2f, zkvm_blake2f_message, zkvm_blake2f_offset, zkvm_blake2f_state, ZKVM_EFAIL, + ZKVM_EOK, }; /// EIP-152 vectors 4-7 share the same h, m and t inputs. @@ -14,28 +13,20 @@ const H: [u8; 64] = hex!( ); const T: [u8; 16] = hex!("03000000000000000000000000000000"); -fn m() -> ZkvmBlake2fMessage { - let mut m = ZkvmBlake2fMessage { data: [0; 128] }; +fn m() -> zkvm_blake2f_message { + let mut m = zkvm_blake2f_message { data: [0; 128] }; m.data[..3].copy_from_slice(b"abc"); m } -fn state_words(bytes: &[u8; 64]) -> [u64; 8] { - core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) -} - -fn message_words(bytes: &[u8; 128]) -> [u64; 16] { - core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) -} - -fn offset_words(bytes: &[u8; 16]) -> [u64; 2] { - core::array::from_fn(|i| u64::from_le_bytes(bytes[i * 8..(i + 1) * 8].try_into().unwrap())) -} - fn check(rounds: u32, f: u8, expected: [u8; 64]) { - let mut h = state_words(&H); - blake2f(rounds, &mut h, &message_words(&m().data), &offset_words(&T), f == 1); - assert_eq!(h, state_words(&expected), "rounds={rounds}, f={f}"); + let mut h = zkvm_blake2f_state { data: H }; + let m = m(); + let t = zkvm_blake2f_offset { data: T }; + + let status = unsafe { zkvm_blake2f(rounds, &mut h, &m, &t, f) }; + assert_eq!(status, ZKVM_EOK, "rounds={rounds}, f={f}"); + assert_eq!(h.data, expected, "rounds={rounds}, f={f}"); } #[test] @@ -87,42 +78,30 @@ fn blake2f_eip152_vector_7_one_round() { } #[test] -fn zkvm_blake2f_smoke() { - let mut h = ZkvmBlake2fState { data: H }; +fn zkvm_blake2f_invalid_final_flag_preserves_state() { + let mut h = zkvm_blake2f_state { data: H }; let m = m(); - let t = ZkvmBlake2fOffset { data: T }; + let t = zkvm_blake2f_offset { data: T }; - let status = unsafe { zkvm_blake2f(12, &mut h, &m, &t, 1) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - h.data, - hex!( - "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" - "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923" - ) - ); - - // An invalid final flag maps to the failure status. - let valid_output = h; let status = unsafe { zkvm_blake2f(12, &mut h, &m, &t, 2) }; - assert_eq!(status, ZkvmStatus::Fail); - assert_eq!(h, valid_output); + assert_eq!(status, ZKVM_EFAIL); + assert_eq!(h.data, H); } #[test] fn zkvm_blake2f_null_pointers() { - let mut h = ZkvmBlake2fState { data: H }; + let mut h = zkvm_blake2f_state { data: H }; let m = m(); - let t = ZkvmBlake2fOffset { data: T }; + let t = zkvm_blake2f_offset { data: T }; let status = unsafe { zkvm_blake2f(12, core::ptr::null_mut(), &m, &t, 1) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_blake2f(12, &mut h, core::ptr::null(), &t, 1) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_blake2f(12, &mut h, &m, core::ptr::null(), 1) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); // The state must be untouched when a pointer is NULL. assert_eq!(h.data, H); } diff --git a/crates/accelerators/tests/bls12_381.rs b/crates/accelerators/tests/bls12_381.rs index f78efe76c..906a90c5a 100644 --- a/crates/accelerators/tests/bls12_381.rs +++ b/crates/accelerators/tests/bls12_381.rs @@ -1,44 +1,22 @@ //! BLS12-381 add/MSM/pairing/map conformance vectors. -#![cfg(feature = "ffi")] - -use core::convert::Infallible; - use hex_literal::hex; use openvm_accelerators::{ - bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, zkvm_bls12_g1_add, - zkvm_bls12_g1_msm, zkvm_bls12_g2_add, zkvm_bls12_g2_msm, zkvm_bls12_map_fp2_to_g2, - zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, BlsG1, BlsG2, Error, ZkvmBls12381Fp, - ZkvmBls12381Fp2, ZkvmBls12381G1MsmPair, ZkvmBls12381G1Point, ZkvmBls12381G2MsmPair, - ZkvmBls12381G2Point, ZkvmBls12381PairingPair, ZkvmBls12381Scalar, ZkvmStatus, + zkvm_bls12_381_fp, zkvm_bls12_381_fp2, zkvm_bls12_381_g1_msm_pair, zkvm_bls12_381_g1_point, + zkvm_bls12_381_g2_msm_pair, zkvm_bls12_381_g2_point, zkvm_bls12_381_pairing_pair, + zkvm_bls12_381_scalar, zkvm_bls12_g1_add, zkvm_bls12_g1_msm, zkvm_bls12_g2_add, + zkvm_bls12_g2_msm, zkvm_bls12_map_fp2_to_g2, zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, + ZKVM_EFAIL, ZKVM_EOK, }; -fn scalar(value: u8) -> ZkvmBls12381Scalar { - let mut scalar = ZkvmBls12381Scalar { data: [0; 32] }; +fn scalar(value: u8) -> zkvm_bls12_381_scalar { + let mut scalar = zkvm_bls12_381_scalar { data: [0; 32] }; scalar.data[31] = value; scalar } -fn bls_g1(point: ZkvmBls12381G1Point) -> BlsG1 { - (point.data[..48].try_into().unwrap(), point.data[48..].try_into().unwrap()) -} - -fn bls_g2(point: ZkvmBls12381G2Point) -> BlsG2 { - ( - point.data[..48].try_into().unwrap(), - point.data[48..96].try_into().unwrap(), - point.data[96..144].try_into().unwrap(), - point.data[144..].try_into().unwrap(), - ) -} - -fn bls_fp2(input: [u8; 96]) -> ([u8; 48], [u8; 48]) { - (input[..48].try_into().unwrap(), input[48..].try_into().unwrap()) -} - /// BLS12-381 G1 generator (`x || y`). -const BLS_G1_GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { +const BLS_G1_GEN: zkvm_bls12_381_g1_point = zkvm_bls12_381_g1_point { data: hex!( "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" "08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" @@ -46,7 +24,7 @@ const BLS_G1_GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { }; /// Doubled BLS12-381 G1 generator, stripped from the EIP-2537 test-vector padding. -const BLS_G1_2GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { +const BLS_G1_2GEN: zkvm_bls12_381_g1_point = zkvm_bls12_381_g1_point { data: hex!( "0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e" "166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28" @@ -54,7 +32,7 @@ const BLS_G1_2GEN: ZkvmBls12381G1Point = ZkvmBls12381G1Point { }; /// BLS12-381 G2 generator in EIP-2537 order (`x_c0 || x_c1 || y_c0 || y_c1`). -const BLS_G2_GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { +const BLS_G2_GEN: zkvm_bls12_381_g2_point = zkvm_bls12_381_g2_point { data: hex!( "024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8" "13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e" @@ -64,7 +42,7 @@ const BLS_G2_GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { }; /// Doubled BLS12-381 G2 generator, stripped from the EIP-2537 test-vector padding. -const BLS_G2_2GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { +const BLS_G2_2GEN: zkvm_bls12_381_g2_point = zkvm_bls12_381_g2_point { data: hex!( "1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053" "0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577" @@ -74,93 +52,61 @@ const BLS_G2_2GEN: ZkvmBls12381G2Point = ZkvmBls12381G2Point { }; /// BLS12-381 scalar field order minus one; multiplying by it negates a point. -const BLS_R_MINUS_1: ZkvmBls12381Scalar = ZkvmBls12381Scalar { +const BLS_R_MINUS_1: zkvm_bls12_381_scalar = zkvm_bls12_381_scalar { data: hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"), }; -fn neg_g1_generator() -> ZkvmBls12381G1Point { - let pairs = [Ok::<_, Infallible>((bls_g1(BLS_G1_GEN), BLS_R_MINUS_1.data))]; - ZkvmBls12381G1Point { data: bls12_381_g1_msm(pairs).unwrap() } +fn neg_g1_generator() -> zkvm_bls12_381_g1_point { + let pairs = [zkvm_bls12_381_g1_msm_pair { point: BLS_G1_GEN, scalar: BLS_R_MINUS_1 }]; + let mut output = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_msm(pairs.as_ptr(), pairs.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + output } #[test] fn bls12_g1_add_msm_vectors() { - let output = bls12_381_g1_add(&bls_g1(BLS_G1_GEN), &bls_g1(BLS_G1_GEN)).unwrap(); - assert_eq!(output, BLS_G1_2GEN.data); - - let pairs = [Ok::<_, Infallible>((bls_g1(BLS_G1_GEN), scalar(2).data))]; - let output = bls12_381_g1_msm(pairs).unwrap(); - assert_eq!(output, BLS_G1_2GEN.data); - - let output = - bls12_381_g1_msm(core::iter::empty::>()).unwrap(); - assert_eq!(output, [0u8; 96]); + let mut output = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_add(&BLS_G1_GEN, &BLS_G1_GEN, &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BLS_G1_2GEN.data); + + let pairs = [zkvm_bls12_381_g1_msm_pair { point: BLS_G1_GEN, scalar: scalar(2) }]; + output.data.fill(0); + let status = unsafe { zkvm_bls12_g1_msm(pairs.as_ptr(), pairs.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BLS_G1_2GEN.data); } #[test] fn bls12_g2_add_msm_vectors() { - let output = bls12_381_g2_add(&bls_g2(BLS_G2_GEN), &bls_g2(BLS_G2_GEN)).unwrap(); - assert_eq!(output, BLS_G2_2GEN.data); - - let pairs = [Ok::<_, Infallible>((bls_g2(BLS_G2_GEN), scalar(2).data))]; - let output = bls12_381_g2_msm(pairs).unwrap(); - assert_eq!(output, BLS_G2_2GEN.data); - - let output = - bls12_381_g2_msm(core::iter::empty::>()).unwrap(); - assert_eq!(output, [0u8; 192]); + let mut output = zkvm_bls12_381_g2_point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_g2_add(&BLS_G2_GEN, &BLS_G2_GEN, &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BLS_G2_2GEN.data); + + let pairs = [zkvm_bls12_381_g2_msm_pair { point: BLS_G2_GEN, scalar: scalar(2) }]; + output.data.fill(0); + let status = unsafe { zkvm_bls12_g2_msm(pairs.as_ptr(), pairs.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BLS_G2_2GEN.data); } #[test] fn bls12_pairing_vectors() { - let neg_g1 = neg_g1_generator(); - let pairs = [(bls_g1(BLS_G1_GEN), bls_g2(BLS_G2_GEN)), (bls_g1(neg_g1), bls_g2(BLS_G2_GEN))]; - - assert!(bls12_381_pairing_check(pairs).unwrap()); - assert!(!bls12_381_pairing_check(pairs[..1].iter().copied()).unwrap()); - assert!(bls12_381_pairing_check(core::iter::empty::<(BlsG1, BlsG2)>()).unwrap()); -} - -#[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 }, + zkvm_bls12_381_pairing_pair { g1: BLS_G1_GEN, g2: BLS_G2_GEN }, + zkvm_bls12_381_pairing_pair { 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_eq!(status, ZKVM_EOK); assert!(verified); let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), 1, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert!(!verified); } @@ -168,58 +114,67 @@ fn zkvm_bls12_pairing_smoke() { fn bls12_rejects_invalid_points() { let mut off_curve_g1 = BLS_G1_GEN; off_curve_g1.data[95] ^= 1; - assert!(bls12_381_g1_add(&bls_g1(off_curve_g1), &bls_g1(BLS_G1_GEN)).is_err()); + let mut g1_output = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_add(&off_curve_g1, &BLS_G1_GEN, &mut g1_output) }; + assert_eq!(status, ZKVM_EFAIL); let mut off_curve_g2 = BLS_G2_GEN; off_curve_g2.data[191] ^= 1; - assert!(bls12_381_g2_add(&bls_g2(off_curve_g2), &bls_g2(BLS_G2_GEN)).is_err()); + let mut g2_output = zkvm_bls12_381_g2_point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_g2_add(&off_curve_g2, &BLS_G2_GEN, &mut g2_output) }; + assert_eq!(status, ZKVM_EFAIL); - let pairs = [(bls_g1(off_curve_g1), bls_g2(BLS_G2_GEN))]; - assert_eq!(bls12_381_pairing_check(pairs), Err(Error::BlsG1PointNotOnCurve)); + let pairs = [zkvm_bls12_381_pairing_pair { g1: off_curve_g1, g2: BLS_G2_GEN }]; + let mut verified = true; + let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; + assert_eq!(status, ZKVM_EFAIL); + assert!(verified); } #[test] fn zkvm_bls12_null_pointers() { - let mut g1_output = ZkvmBls12381G1Point { data: [0; 96] }; + let mut g1_output = zkvm_bls12_381_g1_point { data: [0; 96] }; let status = unsafe { zkvm_bls12_g1_add(core::ptr::null(), &BLS_G1_GEN, &mut g1_output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bls12_g1_add(&BLS_G1_GEN, &BLS_G1_GEN, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); + g1_output.data.fill(0xff); let status = unsafe { zkvm_bls12_g1_msm(core::ptr::null(), 0, &mut g1_output) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); 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); + assert_eq!(status, ZKVM_EFAIL); - let mut g2_output = ZkvmBls12381G2Point { data: [0; 192] }; + let mut g2_output = zkvm_bls12_381_g2_point { data: [0; 192] }; let status = unsafe { zkvm_bls12_g2_add(core::ptr::null(), &BLS_G2_GEN, &mut g2_output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bls12_g2_add(&BLS_G2_GEN, &BLS_G2_GEN, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); + g2_output.data.fill(0xff); let status = unsafe { zkvm_bls12_g2_msm(core::ptr::null(), 0, &mut g2_output) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); 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); + assert_eq!(status, ZKVM_EFAIL); - let pairs = [ZkvmBls12381PairingPair { g1: BLS_G1_GEN, g2: BLS_G2_GEN }]; + let pairs = [zkvm_bls12_381_pairing_pair { 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_eq!(status, ZKVM_EOK); assert!(verified); let status = unsafe { zkvm_bls12_pairing(core::ptr::null(), 1, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); } /* ============================================================================ @@ -287,16 +242,22 @@ const BLS_FP_MODULUS: [u8; 48] = #[test] fn bls12_map_fp_to_g1_vectors() { for (input, expected) in MAP_FP_TO_G1_VECTORS { - let output = bls12_381_map_fp_to_g1(&input).unwrap(); - assert_eq!(output, expected, "input={input:?}"); + let field_element = zkvm_bls12_381_fp { data: input }; + let mut output = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, &mut output) }; + assert_eq!(status, ZKVM_EOK, "input={input:?}"); + assert_eq!(output.data, expected, "input={input:?}"); } } #[test] fn bls12_map_fp2_to_g2_vectors() { for (input, expected) in MAP_FP2_TO_G2_VECTORS { - let output = bls12_381_map_fp2_to_g2(&bls_fp2(input)).unwrap(); - assert_eq!(output, expected, "input={input:?}"); + let field_element = zkvm_bls12_381_fp2 { data: input }; + let mut output = zkvm_bls12_381_g2_point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, &mut output) }; + assert_eq!(status, ZKVM_EOK, "input={input:?}"); + assert_eq!(output.data, expected, "input={input:?}"); } } @@ -307,74 +268,67 @@ fn bls12_map_fp2_to_g2_vectors() { /// return the point itself. #[test] fn bls12_map_lands_in_prime_order_subgroup() { - let mapped = bls12_381_map_fp_to_g1(&MAP_FP_TO_G1_VECTORS[0].0).unwrap(); - let pairs = - [Ok::<_, Infallible>((bls_g1(ZkvmBls12381G1Point { data: mapped }), scalar(1).data))]; - let output = - bls12_381_g1_msm(pairs).expect("mapped G1 point must be in the prime-order subgroup"); - assert_eq!(output, mapped); - - let mapped = bls12_381_map_fp2_to_g2(&bls_fp2(MAP_FP2_TO_G2_VECTORS[0].0)).unwrap(); - let pairs = - [Ok::<_, Infallible>((bls_g2(ZkvmBls12381G2Point { data: mapped }), scalar(1).data))]; - let output = - bls12_381_g2_msm(pairs).expect("mapped G2 point must be in the prime-order subgroup"); - assert_eq!(output, mapped); + let fp = zkvm_bls12_381_fp { data: MAP_FP_TO_G1_VECTORS[0].0 }; + let mut mapped_g1 = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(&fp, &mut mapped_g1) }; + assert_eq!(status, ZKVM_EOK); + + let pairs = [zkvm_bls12_381_g1_msm_pair { point: mapped_g1, scalar: scalar(1) }]; + let mut output_g1 = zkvm_bls12_381_g1_point { data: [0; 96] }; + let status = unsafe { zkvm_bls12_g1_msm(pairs.as_ptr(), pairs.len(), &mut output_g1) }; + assert_eq!(status, ZKVM_EOK, "mapped G1 point must pass the subgroup check"); + assert_eq!(output_g1.data, mapped_g1.data); + + let fp2 = zkvm_bls12_381_fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }; + let mut mapped_g2 = zkvm_bls12_381_g2_point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&fp2, &mut mapped_g2) }; + assert_eq!(status, ZKVM_EOK); + + let pairs = [zkvm_bls12_381_g2_msm_pair { point: mapped_g2, scalar: scalar(1) }]; + let mut output_g2 = zkvm_bls12_381_g2_point { data: [0; 192] }; + let status = unsafe { zkvm_bls12_g2_msm(pairs.as_ptr(), pairs.len(), &mut output_g2) }; + assert_eq!(status, ZKVM_EOK, "mapped G2 point must pass the subgroup check"); + assert_eq!(output_g2.data, mapped_g2.data); } #[test] fn bls12_map_field_element_range() { // The largest canonical element is accepted, the modulus itself is not. - assert!(bls12_381_map_fp_to_g1(&BLS_FP_MAX).is_ok()); - assert_eq!(bls12_381_map_fp_to_g1(&BLS_FP_MODULUS), Err(Error::FieldElementInvalid)); - assert_eq!(bls12_381_map_fp_to_g1(&[0xff; 48]), Err(Error::FieldElementInvalid)); + let mut output = zkvm_bls12_381_g1_point { data: [0; 96] }; + let field_element = zkvm_bls12_381_fp { data: BLS_FP_MAX }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, &mut output) }; + assert_eq!(status, ZKVM_EOK); + + for input in [BLS_FP_MODULUS, [0xff; 48]] { + let field_element = zkvm_bls12_381_fp { data: input }; + let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, &mut output) }; + assert_eq!(status, ZKVM_EFAIL); + } // Either half of an Fp2 input is checked. - assert_eq!( - bls12_381_map_fp2_to_g2(&(BLS_FP_MODULUS, [0; 48])), - Err(Error::FieldElementInvalid) - ); - assert_eq!( - bls12_381_map_fp2_to_g2(&([0; 48], BLS_FP_MODULUS)), - Err(Error::FieldElementInvalid) - ); -} - -#[test] -fn zkvm_bls12_map_smoke() { - let (fp, expected) = MAP_FP_TO_G1_VECTORS[0]; - let field_element = ZkvmBls12381Fp { data: fp }; - let mut g1 = ZkvmBls12381G1Point { data: [0xff; 96] }; - let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, &mut g1) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!(g1.data, expected); - - let (fp2, expected) = MAP_FP2_TO_G2_VECTORS[0]; - let field_element = ZkvmBls12381Fp2 { data: fp2 }; - let mut g2 = ZkvmBls12381G2Point { data: [0xff; 192] }; - let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, &mut g2) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!(g2.data, expected); - - // A non-canonical field element maps to the failure status. - let not_canonical = ZkvmBls12381Fp { data: BLS_FP_MODULUS }; - let status = unsafe { zkvm_bls12_map_fp_to_g1(¬_canonical, &mut g1) }; - assert_eq!(status, ZkvmStatus::Fail); + let mut g2 = zkvm_bls12_381_g2_point { data: [0; 192] }; + for modulus_offset in [0, 48] { + let mut input = [0; 96]; + input[modulus_offset..modulus_offset + 48].copy_from_slice(&BLS_FP_MODULUS); + let field_element = zkvm_bls12_381_fp2 { data: input }; + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, &mut g2) }; + assert_eq!(status, ZKVM_EFAIL); + } } #[test] fn zkvm_bls12_map_null_pointers() { - let field_element = ZkvmBls12381Fp { data: MAP_FP_TO_G1_VECTORS[0].0 }; - let mut g1 = ZkvmBls12381G1Point { data: [0; 96] }; + let field_element = zkvm_bls12_381_fp { data: MAP_FP_TO_G1_VECTORS[0].0 }; + let mut g1 = zkvm_bls12_381_g1_point { data: [0; 96] }; let status = unsafe { zkvm_bls12_map_fp_to_g1(core::ptr::null(), &mut g1) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bls12_map_fp_to_g1(&field_element, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); - let field_element = ZkvmBls12381Fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }; - let mut g2 = ZkvmBls12381G2Point { data: [0; 192] }; + let field_element = zkvm_bls12_381_fp2 { data: MAP_FP2_TO_G2_VECTORS[0].0 }; + let mut g2 = zkvm_bls12_381_g2_point { data: [0; 192] }; let status = unsafe { zkvm_bls12_map_fp2_to_g2(core::ptr::null(), &mut g2) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bls12_map_fp2_to_g2(&field_element, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); } diff --git a/crates/accelerators/tests/bn254.rs b/crates/accelerators/tests/bn254.rs index c5a77666d..1d9d95dba 100644 --- a/crates/accelerators/tests/bn254.rs +++ b/crates/accelerators/tests/bn254.rs @@ -1,12 +1,11 @@ //! BN254 add/mul/pairing conformance vectors. -#![cfg(feature = "ffi")] - use hex_literal::hex; use openvm_accelerators::{ - bn254_g1_add, bn254_g1_mul, bn254_pairing_check, zkvm_bn254_g1_add, zkvm_bn254_g1_mul, - zkvm_bn254_pairing, Error, ZkvmBn254G1Point, ZkvmBn254G2Point, ZkvmBn254PairingPair, - ZkvmBn254Scalar, ZkvmStatus, + zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_g1_point as ZkvmBn254G1Point, + zkvm_bn254_g2_point as ZkvmBn254G2Point, zkvm_bn254_pairing, + zkvm_bn254_pairing_pair as ZkvmBn254PairingPair, zkvm_bn254_scalar as ZkvmBn254Scalar, + ZKVM_EFAIL, ZKVM_EOK, }; fn scalar(value: u8) -> ZkvmBn254Scalar { @@ -49,39 +48,19 @@ const BN254_G2_GEN: ZkvmBn254G2Point = ZkvmBn254G2Point { ), }; -#[test] -fn bn254_add_mul_vectors() { - let point = generator(); - assert_eq!(bn254_g1_add(&point.data, &point.data).unwrap(), BN254_2GEN.data); - assert_eq!(bn254_g1_mul(&point.data, &scalar(2).data).unwrap(), BN254_2GEN.data); -} - -#[test] -fn bn254_pairing_vectors() { - let pairs = [ - ZkvmBn254PairingPair { g1: generator(), g2: BN254_G2_GEN }, - ZkvmBn254PairingPair { g1: BN254_NEG_GEN, g2: BN254_G2_GEN }, - ]; - let raw = pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())); - assert!(bn254_pairing_check(raw).unwrap()); - assert!(!bn254_pairing_check([(pairs[0].g1.data.as_slice(), pairs[0].g2.data.as_slice(),)]) - .unwrap()); - assert!(bn254_pairing_check(core::iter::empty()).unwrap()); -} - #[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); + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BN254_2GEN.data); 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); + assert_eq!(status, ZKVM_EOK); + assert_eq!(output.data, BN254_2GEN.data); } #[test] @@ -93,11 +72,11 @@ fn zkvm_bn254_pairing_smoke() { let mut verified = false; let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert!(verified); let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), 1, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert!(!verified); } @@ -107,18 +86,14 @@ fn bn254_rejects_invalid_point() { not_on_curve.data[63] = 3; let mut output = ZkvmBn254G1Point { data: [0; 64] }; - assert_eq!(bn254_g1_add(¬_on_curve.data, &generator().data), Err(Error::PointNotOnCurve)); - let status = unsafe { zkvm_bn254_g1_mul(¬_on_curve, &scalar(2), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let pairs = [ZkvmBn254PairingPair { g1: not_on_curve, g2: BN254_G2_GEN }]; - assert_eq!( - bn254_pairing_check( - pairs.iter().map(|pair| (pair.g1.data.as_slice(), pair.g2.data.as_slice())) - ), - Err(Error::PointNotOnCurve) - ); + let mut verified = true; + let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; + assert_eq!(status, ZKVM_EFAIL); + assert!(verified); } #[test] @@ -128,33 +103,33 @@ fn zkvm_bn254_null_pointers() { 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); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_g1_add(&point, core::ptr::null(), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_g1_add(&point, &point, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_g1_mul(core::ptr::null(), &scalar, &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_g1_mul(&point, core::ptr::null(), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_g1_mul(&point, &scalar, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); 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_eq!(status, ZKVM_EOK); assert!(verified); let status = unsafe { zkvm_bn254_pairing(core::ptr::null(), 1, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); } diff --git a/crates/accelerators/tests/ecdsa.rs b/crates/accelerators/tests/ecdsa.rs deleted file mode 100644 index 3ba682cd8..000000000 --- a/crates/accelerators/tests/ecdsa.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! ECDSA conformance vectors. -//! -//! Tested with vectors from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors. - -#![cfg(feature = "ffi")] - -use hex_literal::hex; -use openvm_accelerators::{ - keccak256, secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, zkvm_secp256k1_ecrecover, - zkvm_secp256k1_verify, zkvm_secp256r1_verify, Error, ZkvmSecp256k1Hash, ZkvmSecp256k1Pubkey, - ZkvmSecp256k1Signature, ZkvmSecp256r1Hash, ZkvmSecp256r1Pubkey, ZkvmSecp256r1Signature, - ZkvmStatus, -}; - -/// Splits a 160-byte P256VERIFY input (msg || sig || pk) into its parts. -fn parts(input: &[u8; 160]) -> (ZkvmSecp256r1Hash, ZkvmSecp256r1Signature, ZkvmSecp256r1Pubkey) { - ( - ZkvmSecp256r1Hash { data: input[..32].try_into().unwrap() }, - ZkvmSecp256r1Signature { data: input[32..96].try_into().unwrap() }, - ZkvmSecp256r1Pubkey { data: input[96..].try_into().unwrap() }, - ) -} - -const VALID: [u8; 160] = hex!( - "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d" - "a73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac" - "36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60" - "4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3" - "7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e" -); - -#[test] -fn secp256r1_verify_vectors() { - let (msg, sig, pubkey) = parts(&VALID); - assert!(secp256r1_verify(&msg.data, &sig.data, &pubkey.data)); - - let mut wrong_msg = msg; - wrong_msg.data[0] = 0x3c; - assert!(!secp256r1_verify(&wrong_msg.data, &sig.data, &pubkey.data)); -} - -#[test] -fn secp256r1_verify_malformed_inputs() { - let (msg, sig, _) = parts(&VALID); - let bad_sig = ZkvmSecp256r1Signature { data: [0xff; 64] }; - assert!(!secp256r1_verify(&msg.data, &bad_sig.data, &parts(&VALID).2.data)); - - let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; - assert!(!secp256r1_verify(&msg.data, &sig.data, &bad_pubkey.data)); -} - -#[test] -fn zkvm_secp256r1_verify_smoke() { - let (msg, sig, pubkey) = parts(&VALID); - let mut verified = false; - - let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); - assert!(verified); - - // Malformed cryptographic inputs are a completed verification with a false result. - let bad_pubkey = ZkvmSecp256r1Pubkey { data: [0; 64] }; - let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); - assert!(!verified); -} - -#[test] -fn zkvm_secp256r1_verify_null_pointers() { - let (msg, sig, pubkey) = parts(&VALID); - let mut verified = false; - - let status = unsafe { zkvm_secp256r1_verify(core::ptr::null(), &sig, &pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_secp256r1_verify(&msg, core::ptr::null(), &pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, core::ptr::null(), &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); -} - -const K1_MSG: ZkvmSecp256k1Hash = ZkvmSecp256k1Hash { - data: hex!("456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3"), -}; -const K1_SIG: ZkvmSecp256k1Signature = ZkvmSecp256k1Signature { - data: hex!( - "9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608" - "4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada" - ), -}; -const K1_ADDRESS: [u8; 20] = hex!("7156526fbd7a3c72969b54f64e42c10fbb768c8a"); - -#[test] -fn secp256k1_ecrecover_vector() { - let pubkey = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 1).unwrap(); - - // The Ethereum address is keccak(pubkey)[12..], derived here exactly as - // a caller of the interface would. - assert_eq!(keccak256(&pubkey)[12..], K1_ADDRESS); -} - -#[test] -fn secp256k1_ecrecover_invalid_inputs() { - // Recovery ids above 3 are invalid. - let result = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 4); - assert_eq!(result, Err(Error::InvalidSignature)); - - // The zero signature cannot be parsed. - let zero_sig = ZkvmSecp256k1Signature { data: [0; 64] }; - let result = secp256k1_ecrecover(&K1_MSG.data, &zero_sig.data, 0); - assert_eq!(result, Err(Error::InvalidSignature)); -} - -#[test] -fn secp256k1_verify_roundtrip() { - let pubkey = secp256k1_ecrecover(&K1_MSG.data, &K1_SIG.data, 1).unwrap(); - assert!(secp256k1_verify(&K1_MSG.data, &K1_SIG.data, &pubkey)); - - let mut wrong_msg = K1_MSG; - wrong_msg.data[0] ^= 1; - assert!(!secp256k1_verify(&wrong_msg.data, &K1_SIG.data, &pubkey)); - - let bad_pubkey = ZkvmSecp256k1Pubkey { data: [0xff; 64] }; - assert!(!secp256k1_verify(&K1_MSG.data, &K1_SIG.data, &bad_pubkey.data)); -} - -#[test] -fn zkvm_secp256k1_recover_and_verify() { - let mut pubkey = core::mem::MaybeUninit::::uninit(); - let status = unsafe { zkvm_secp256k1_ecrecover(&K1_MSG, &K1_SIG, 1, pubkey.as_mut_ptr()) }; - assert_eq!(status, ZkvmStatus::Ok); - let pubkey = unsafe { pubkey.assume_init() }; - - let mut verified = core::mem::MaybeUninit::::uninit(); - let status = unsafe { zkvm_secp256k1_verify(&K1_MSG, &K1_SIG, &pubkey, verified.as_mut_ptr()) }; - assert_eq!(status, ZkvmStatus::Ok); - let mut verified = unsafe { verified.assume_init() }; - assert!(verified); - - let bad_pubkey = ZkvmSecp256k1Pubkey { data: [0xff; 64] }; - let status = unsafe { zkvm_secp256k1_verify(&K1_MSG, &K1_SIG, &bad_pubkey, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); - assert!(!verified); -} diff --git a/crates/accelerators/tests/hash.rs b/crates/accelerators/tests/hash.rs deleted file mode 100644 index 7c909bd4a..000000000 --- a/crates/accelerators/tests/hash.rs +++ /dev/null @@ -1,144 +0,0 @@ -#![cfg(feature = "ffi")] - -//! Hash conformance vectors. - -use hex_literal::hex; -use openvm_accelerators::{ - keccak256, ripemd160, sha256, zkvm_keccak256, zkvm_ripemd160, zkvm_sha256, ZkvmKeccak256Hash, - ZkvmRipemd160Hash, ZkvmSha256Hash, ZkvmStatus, -}; - -#[test] -fn keccak256_vectors() { - assert_eq!( - keccak256(b""), - hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") - ); - - assert_eq!( - keccak256(b"abc"), - hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") - ); -} - -#[test] -fn zkvm_keccak256_smoke() { - let data = *b"abc"; - let mut output = ZkvmKeccak256Hash { data: [0; 32] }; - let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") - ); -} - -#[test] -fn zkvm_keccak256_null_pointers() { - let data = *b"abc"; - let mut output = ZkvmKeccak256Hash { data: [0; 32] }; - - // A NULL `data` with `len == 0` is the empty input. - let status = unsafe { zkvm_keccak256(core::ptr::null(), 0, &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") - ); - - let status = unsafe { zkvm_keccak256(core::ptr::null(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); -} - -#[test] -fn sha256_vectors() { - assert_eq!( - sha256(b""), - hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - ); - - assert_eq!( - sha256(b"abc"), - hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") - ); -} - -#[test] -fn zkvm_sha256_smoke() { - let data = *b"abc"; - let mut output = ZkvmSha256Hash { data: [0; 32] }; - let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") - ); -} - -#[test] -fn zkvm_sha256_null_pointers() { - let data = *b"abc"; - let mut output = ZkvmSha256Hash { data: [0; 32] }; - - // A NULL `data` with `len == 0` is the empty input. - let status = unsafe { zkvm_sha256(core::ptr::null(), 0, &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - ); - - let status = unsafe { zkvm_sha256(core::ptr::null(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); -} - -#[test] -fn ripemd160_vectors() { - assert_eq!( - ripemd160(b""), - hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") - ); - - assert_eq!( - ripemd160(b"abc"), - hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") - ); -} - -#[test] -fn zkvm_ripemd160_smoke() { - let data = *b"abc"; - let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; - let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") - ); -} - -#[test] -fn zkvm_ripemd160_null_pointers() { - let data = *b"abc"; - let mut output = ZkvmRipemd160Hash { data: [0xff; 32] }; - - // A NULL `data` with `len == 0` is the empty input. - let status = unsafe { zkvm_ripemd160(core::ptr::null(), 0, &mut output) }; - assert_eq!(status, ZkvmStatus::Ok); - assert_eq!( - output.data, - hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") - ); - - let status = unsafe { zkvm_ripemd160(core::ptr::null(), data.len(), &mut output) }; - assert_eq!(status, ZkvmStatus::Fail); - - let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); -} diff --git a/crates/accelerators/tests/keccak256.rs b/crates/accelerators/tests/keccak256.rs new file mode 100644 index 000000000..db11e8a4e --- /dev/null +++ b/crates/accelerators/tests/keccak256.rs @@ -0,0 +1,36 @@ +//! Keccak-256 C-interface conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{zkvm_keccak256, zkvm_keccak256_hash, ZKVM_EFAIL, ZKVM_EOK}; + +#[test] +fn keccak256_abc() { + let data = *b"abc"; + let mut output = zkvm_keccak256_hash { data: [0; 32] }; + let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + ); +} + +#[test] +fn keccak256_null_pointers() { + let data = *b"abc"; + let mut output = zkvm_keccak256_hash { data: [0; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_keccak256(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + ); + + let status = unsafe { zkvm_keccak256(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EFAIL); + + let status = unsafe { zkvm_keccak256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZKVM_EFAIL); +} diff --git a/crates/accelerators/tests/kzg.rs b/crates/accelerators/tests/kzg.rs index 9505446c6..de0471148 100644 --- a/crates/accelerators/tests/kzg.rs +++ b/crates/accelerators/tests/kzg.rs @@ -1,13 +1,20 @@ -//! KZG point-evaluation conformance using the point-at-infinity commitment, -//! which commits to the zero polynomial (p(z) = 0 for every z). - -#![cfg(feature = "ffi")] +//! KZG point-evaluation conformance. +use hex_literal::hex; use openvm_accelerators::{ - kzg_point_eval, zkvm_kzg_point_eval, Error, ZkvmKzgCommitment, ZkvmKzgFieldElement, - ZkvmKzgProof, ZkvmStatus, + zkvm_kzg_commitment as ZkvmKzgCommitment, zkvm_kzg_field_element as ZkvmKzgFieldElement, + zkvm_kzg_point_eval, zkvm_kzg_proof as ZkvmKzgProof, ZKVM_EFAIL, ZKVM_EOK, }; +// ethereum/consensus-spec-tests: +// verify_kzg_proof_case_correct_proof_1ce8e4f69d5df899. +const COMMITMENT: [u8; 48] = + hex!("93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556"); +const Z: [u8; 32] = hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"); +const Y: [u8; 32] = [0; 32]; +const PROOF: [u8; 48] = + hex!("92c51ff81dd71dab71cefecd79e8274b4b7ba36a0f40e2dc086bc4061c7f63249877db23297212991fd63e07b7ebc348"); + /// The compressed point at infinity: 0xc0 followed by zeros. fn infinity() -> ZkvmKzgCommitment { let mut point = ZkvmKzgCommitment { data: [0; 48] }; @@ -21,52 +28,23 @@ fn scalar(value: u8) -> ZkvmKzgFieldElement { s } -#[test] -fn kzg_point_eval_infinity_commitment() { - let commitment = infinity(); - let proof: ZkvmKzgProof = infinity(); - let z = scalar(2); - // The zero polynomial evaluates to 0 at every z; the infinity proof - // attests it. - assert!(kzg_point_eval(&commitment.data, &z.data, &scalar(0).data, &proof.data).unwrap()); - - // Claiming y = 1 for the zero polynomial must not verify. - assert!(!kzg_point_eval(&commitment.data, &z.data, &scalar(1).data, &proof.data).unwrap()); -} - -#[test] -fn kzg_point_eval_malformed_inputs() { - let z = scalar(2); - let y = scalar(0); - // Not a valid compressed-point prefix. - let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; - garbage.data[0] = 0x01; - let result = kzg_point_eval(&garbage.data, &z.data, &y.data, &infinity().data); - assert_eq!(result, Err(Error::KzgInvalidInput)); - - // An out-of-range evaluation point (>= the BLS scalar field order). - let big_z = ZkvmKzgFieldElement { data: [0xff; 32] }; - let result = kzg_point_eval(&infinity().data, &big_z.data, &y.data, &infinity().data); - assert_eq!(result, Err(Error::KzgInvalidInput)); -} - #[test] fn zkvm_kzg_point_eval_smoke() { - let commitment = infinity(); - let proof: ZkvmKzgProof = infinity(); - let z = scalar(2); - let y = scalar(0); + let commitment = ZkvmKzgCommitment { data: COMMITMENT }; + let proof = ZkvmKzgProof { data: PROOF }; + let z = ZkvmKzgFieldElement { data: Z }; + let y = ZkvmKzgFieldElement { data: Y }; let mut verified = false; let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert!(verified); // Malformed cryptographic inputs are a completed verification with a false result. let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; garbage.data[0] = 0x01; let status = unsafe { zkvm_kzg_point_eval(&garbage, &z, &y, &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert!(!verified); } @@ -79,20 +57,20 @@ fn zkvm_kzg_point_eval_null_pointers() { let mut verified = false; let status = unsafe { zkvm_kzg_point_eval(core::ptr::null(), &z, &y, &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_kzg_point_eval(&commitment, core::ptr::null(), &y, &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, core::ptr::null(), &proof, &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, core::ptr::null(), &mut verified) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); } diff --git a/crates/accelerators/tests/modexp.rs b/crates/accelerators/tests/modexp.rs index b97baa3f1..d47f4acfb 100644 --- a/crates/accelerators/tests/modexp.rs +++ b/crates/accelerators/tests/modexp.rs @@ -1,42 +1,33 @@ //! Modexp conformance vectors. -#![cfg(feature = "ffi")] - use hex_literal::hex; -use openvm_accelerators::{modexp, zkvm_modexp, ZkvmStatus}; +use openvm_accelerators::{zkvm_modexp, ZKVM_EFAIL, ZKVM_EOK}; /// BN254 Fr (the scalar field) modulus, big-endian. Not to be confused with /// the base field prime, which shares the leading bytes. const BN254_FR: [u8; 32] = hex!("30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); #[test] -fn modexp_small() { - // 3^5 mod 7 = 5 - assert_eq!(modexp(&[3], &[5], &[7]), [5]); - - // Output is left-padded to the modulus length. - assert_eq!(modexp(&[3], &[5], &[0, 7]), [0, 5]); - - assert!(modexp(&[3], &[5], &[]).is_empty()); -} - -#[test] -fn modexp_matches_reference() { - // The BN254-Fr accelerated path, compared right-aligned against the - // aurora reference. - let output = modexp(&[0xab; 32], &[0x07], &BN254_FR); +fn bn254_fr_accelerated_path_matches_reference() { + let base = [0xab; 32]; + let exp = [0x07]; + let mut output = [0; 32]; + let status = unsafe { + zkvm_modexp( + base.as_ptr(), + base.len(), + exp.as_ptr(), + exp.len(), + BN254_FR.as_ptr(), + BN254_FR.len(), + output.as_mut_ptr(), + ) + }; + assert_eq!(status, ZKVM_EOK); let reference = aurora_engine_modexp::modexp(&[0xab; 32], &[0x07], &BN254_FR); let mut expected = [0; 32]; expected[32 - reference.len()..].copy_from_slice(&reference); assert_eq!(output, expected); - - // The generic path with a non-special modulus. - let modulus = [0xef; 24]; - let output = modexp(&[0x12; 40], &[0x34; 3], &modulus); - let reference = aurora_engine_modexp::modexp(&[0x12; 40], &[0x34; 3], &modulus); - let mut expected = [0; 24]; - expected[24 - reference.len()..].copy_from_slice(&reference); - assert_eq!(output, expected); } #[test] @@ -49,7 +40,7 @@ fn zkvm_modexp_smoke() { let status = unsafe { zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, output.as_mut_ptr()) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert_eq!(output, [5]); } @@ -72,14 +63,14 @@ fn zkvm_modexp_null_pointers() { output.as_mut_ptr(), ) }; - assert_eq!(status, ZkvmStatus::Ok); + assert_eq!(status, ZKVM_EOK); assert_eq!(output, [1]); // A NULL pointer with a non-zero length fails. let status = unsafe { zkvm_modexp(core::ptr::null(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, output.as_mut_ptr()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_modexp( @@ -92,15 +83,15 @@ fn zkvm_modexp_null_pointers() { output.as_mut_ptr(), ) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, core::ptr::null(), 1, output.as_mut_ptr()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); let status = unsafe { zkvm_modexp(base.as_ptr(), 1, exp.as_ptr(), 1, modulus.as_ptr(), 1, core::ptr::null_mut()) }; - assert_eq!(status, ZkvmStatus::Fail); + assert_eq!(status, ZKVM_EFAIL); } diff --git a/crates/accelerators/tests/ripemd160.rs b/crates/accelerators/tests/ripemd160.rs new file mode 100644 index 000000000..502eb8aa1 --- /dev/null +++ b/crates/accelerators/tests/ripemd160.rs @@ -0,0 +1,36 @@ +//! RIPEMD-160 C-interface conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{zkvm_ripemd160, zkvm_ripemd160_hash, ZKVM_EFAIL, ZKVM_EOK}; + +#[test] +fn ripemd160_abc() { + let data = *b"abc"; + let mut output = zkvm_ripemd160_hash { data: [0xff; 32] }; + let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc") + ); +} + +#[test] +fn ripemd160_null_pointers() { + let data = *b"abc"; + let mut output = zkvm_ripemd160_hash { data: [0xff; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_ripemd160(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("0000000000000000000000009c1185a5c5e9fc54612808977ee8f548b2258d31") + ); + + let status = unsafe { zkvm_ripemd160(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EFAIL); + + let status = unsafe { zkvm_ripemd160(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZKVM_EFAIL); +} diff --git a/crates/accelerators/tests/secp256k1.rs b/crates/accelerators/tests/secp256k1.rs new file mode 100644 index 000000000..6c8573fc6 --- /dev/null +++ b/crates/accelerators/tests/secp256k1.rs @@ -0,0 +1,103 @@ +//! secp256k1 C-interface conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{ + zkvm_keccak256, zkvm_keccak256_hash, zkvm_secp256k1_ecrecover, zkvm_secp256k1_hash, + zkvm_secp256k1_pubkey, zkvm_secp256k1_signature, zkvm_secp256k1_verify, ZKVM_EFAIL, ZKVM_EOK, +}; + +const MSG: zkvm_secp256k1_hash = zkvm_secp256k1_hash { + data: hex!("456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3"), +}; +const SIG: zkvm_secp256k1_signature = zkvm_secp256k1_signature { + data: hex!( + "9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608" + "4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada" + ), +}; +const ADDRESS: [u8; 20] = hex!("7156526fbd7a3c72969b54f64e42c10fbb768c8a"); + +#[test] +fn secp256k1_ecrecover_vector() { + let mut pubkey = zkvm_secp256k1_pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256k1_ecrecover(&MSG, &SIG, 1, &mut pubkey) }; + assert_eq!(status, ZKVM_EOK); + + let mut digest = zkvm_keccak256_hash { data: [0; 32] }; + let status = unsafe { zkvm_keccak256(pubkey.data.as_ptr(), pubkey.data.len(), &mut digest) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!(digest.data[12..], ADDRESS); +} + +#[test] +fn secp256k1_ecrecover_invalid_inputs() { + let unchanged = [0x55; 64]; + let mut pubkey = zkvm_secp256k1_pubkey { data: unchanged }; + + let status = unsafe { zkvm_secp256k1_ecrecover(&MSG, &SIG, 4, &mut pubkey) }; + assert_eq!(status, ZKVM_EFAIL); + assert_eq!(pubkey.data, unchanged); + + let zero_sig = zkvm_secp256k1_signature { data: [0; 64] }; + let status = unsafe { zkvm_secp256k1_ecrecover(&MSG, &zero_sig, 0, &mut pubkey) }; + assert_eq!(status, ZKVM_EFAIL); + assert_eq!(pubkey.data, unchanged); +} + +#[test] +fn secp256k1_recover_and_verify() { + let mut pubkey = zkvm_secp256k1_pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256k1_ecrecover(&MSG, &SIG, 1, &mut pubkey) }; + assert_eq!(status, ZKVM_EOK); + + let mut verified = false; + let status = unsafe { zkvm_secp256k1_verify(&MSG, &SIG, &pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(verified); + + let mut wrong_msg = MSG; + wrong_msg.data[0] ^= 1; + let status = unsafe { zkvm_secp256k1_verify(&wrong_msg, &SIG, &pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(!verified); + + let bad_pubkey = zkvm_secp256k1_pubkey { data: [0xff; 64] }; + let status = unsafe { zkvm_secp256k1_verify(&MSG, &SIG, &bad_pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(!verified); +} + +#[test] +fn secp256k1_null_pointers() { + let mut pubkey = zkvm_secp256k1_pubkey { data: [0; 64] }; + assert_eq!( + unsafe { zkvm_secp256k1_ecrecover(core::ptr::null(), &SIG, 1, &mut pubkey) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256k1_ecrecover(&MSG, core::ptr::null(), 1, &mut pubkey) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256k1_ecrecover(&MSG, &SIG, 1, core::ptr::null_mut()) }, + ZKVM_EFAIL + ); + + let mut verified = false; + assert_eq!( + unsafe { zkvm_secp256k1_verify(core::ptr::null(), &SIG, &pubkey, &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256k1_verify(&MSG, core::ptr::null(), &pubkey, &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256k1_verify(&MSG, &SIG, core::ptr::null(), &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256k1_verify(&MSG, &SIG, &pubkey, core::ptr::null_mut()) }, + ZKVM_EFAIL + ); +} diff --git a/crates/accelerators/tests/secp256r1.rs b/crates/accelerators/tests/secp256r1.rs new file mode 100644 index 000000000..f8356b7bf --- /dev/null +++ b/crates/accelerators/tests/secp256r1.rs @@ -0,0 +1,81 @@ +//! secp256r1 C-interface conformance vectors. +//! +//! Vectors are from https://github.com/daimo-eth/p256-verifier/tree/master/test-vectors. + +use hex_literal::hex; +use openvm_accelerators::{ + zkvm_secp256r1_hash, zkvm_secp256r1_pubkey, zkvm_secp256r1_signature, zkvm_secp256r1_verify, + ZKVM_EFAIL, ZKVM_EOK, +}; + +fn parts( + input: &[u8; 160], +) -> (zkvm_secp256r1_hash, zkvm_secp256r1_signature, zkvm_secp256r1_pubkey) { + ( + zkvm_secp256r1_hash { data: input[..32].try_into().unwrap() }, + zkvm_secp256r1_signature { data: input[32..96].try_into().unwrap() }, + zkvm_secp256r1_pubkey { data: input[96..].try_into().unwrap() }, + ) +} + +const VALID: [u8; 160] = hex!( + "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4d" + "a73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac" + "36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d60" + "4aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff3" + "7618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e" +); + +#[test] +fn secp256r1_verify_vectors() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = false; + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(verified); + + let mut wrong_msg = msg; + wrong_msg.data[0] = 0x3c; + let status = unsafe { zkvm_secp256r1_verify(&wrong_msg, &sig, &pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(!verified); +} + +#[test] +fn secp256r1_verify_malformed_inputs() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = true; + + let bad_sig = zkvm_secp256r1_signature { data: [0xff; 64] }; + let status = unsafe { zkvm_secp256r1_verify(&msg, &bad_sig, &pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(!verified); + + let bad_pubkey = zkvm_secp256r1_pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &bad_pubkey, &mut verified) }; + assert_eq!(status, ZKVM_EOK); + assert!(!verified); +} + +#[test] +fn secp256r1_verify_null_pointers() { + let (msg, sig, pubkey) = parts(&VALID); + let mut verified = false; + + assert_eq!( + unsafe { zkvm_secp256r1_verify(core::ptr::null(), &sig, &pubkey, &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256r1_verify(&msg, core::ptr::null(), &pubkey, &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256r1_verify(&msg, &sig, core::ptr::null(), &mut verified) }, + ZKVM_EFAIL + ); + assert_eq!( + unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, core::ptr::null_mut()) }, + ZKVM_EFAIL + ); +} diff --git a/crates/accelerators/tests/sha256.rs b/crates/accelerators/tests/sha256.rs new file mode 100644 index 000000000..a4bf78cfc --- /dev/null +++ b/crates/accelerators/tests/sha256.rs @@ -0,0 +1,36 @@ +//! SHA-256 C-interface conformance vectors. + +use hex_literal::hex; +use openvm_accelerators::{zkvm_sha256, zkvm_sha256_hash, ZKVM_EFAIL, ZKVM_EOK}; + +#[test] +fn sha256_abc() { + let data = *b"abc"; + let mut output = zkvm_sha256_hash { data: [0; 32] }; + let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") + ); +} + +#[test] +fn sha256_null_pointers() { + let data = *b"abc"; + let mut output = zkvm_sha256_hash { data: [0; 32] }; + + // A NULL `data` with `len == 0` is the empty input. + let status = unsafe { zkvm_sha256(core::ptr::null(), 0, &mut output) }; + assert_eq!(status, ZKVM_EOK); + assert_eq!( + output.data, + hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + ); + + let status = unsafe { zkvm_sha256(core::ptr::null(), data.len(), &mut output) }; + assert_eq!(status, ZKVM_EFAIL); + + let status = unsafe { zkvm_sha256(data.as_ptr(), data.len(), core::ptr::null_mut()) }; + assert_eq!(status, ZKVM_EFAIL); +} diff --git a/crates/revm-crypto/src/alloy.rs b/crates/revm-crypto/src/alloy.rs new file mode 100644 index 000000000..2c0980d24 --- /dev/null +++ b/crates/revm-crypto/src/alloy.rs @@ -0,0 +1,67 @@ +//! Alloy signer adapter for the standard zkVM accelerator C interface. + +use alloc::{boxed::Box, sync::Arc}; +use core::error::Error; + +use crate::status_ok; +use alloy_consensus::crypto::{ + backend::{install_default_provider, CryptoProvider}, + RecoveryError, +}; +use alloy_primitives::Address; +use openvm_accelerators::{ + zkvm_keccak256, zkvm_keccak256_hash, zkvm_secp256k1_ecrecover, zkvm_secp256k1_hash, + zkvm_secp256k1_pubkey, zkvm_secp256k1_signature, zkvm_secp256k1_verify, +}; + +#[derive(Debug, Default)] +struct OpenVmK256Provider; + +impl CryptoProvider for OpenVmK256Provider { + fn recover_signer_unchecked( + &self, + signature: &[u8; 65], + msg: &[u8; 32], + ) -> Result { + let msg = zkvm_secp256k1_hash { data: *msg }; + let sig = zkvm_secp256k1_signature { data: signature[..64].try_into().unwrap() }; + let mut pubkey = zkvm_secp256k1_pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256k1_ecrecover(&msg, &sig, signature[64], &mut pubkey) }; + if !status_ok(status) { + return Err(RecoveryError::new()); + } + Ok(address_from_pubkey(&pubkey.data)) + } + + fn verify_and_compute_signer_unchecked( + &self, + pubkey: &[u8; 65], + sig: &[u8; 64], + msg: &[u8; 32], + ) -> Result { + if pubkey[0] != 0x04 { + return Err(RecoveryError::new()); + } + let msg = zkvm_secp256k1_hash { data: *msg }; + let sig = zkvm_secp256k1_signature { data: *sig }; + let pubkey = zkvm_secp256k1_pubkey { data: pubkey[1..].try_into().unwrap() }; + let mut verified = false; + let status = unsafe { zkvm_secp256k1_verify(&msg, &sig, &pubkey, &mut verified) }; + if !status_ok(status) || !verified { + return Err(RecoveryError::new()); + } + Ok(address_from_pubkey(&pubkey.data)) + } +} + +fn address_from_pubkey(pubkey: &[u8; 64]) -> Address { + let mut hash = zkvm_keccak256_hash { data: [0; 32] }; + let status = unsafe { zkvm_keccak256(pubkey.as_ptr(), pubkey.len(), &mut hash) }; + assert!(status_ok(status), "zkVM accelerator call failed"); + Address::from_slice(&hash.data[12..]) +} + +pub(super) fn install() -> Result<(), Box> { + install_default_provider(Arc::new(OpenVmK256Provider))?; + Ok(()) +} diff --git a/crates/revm-crypto/src/lib.rs b/crates/revm-crypto/src/lib.rs index fe33a80ab..c3fe83c15 100644 --- a/crates/revm-crypto/src/lib.rs +++ b/crates/revm-crypto/src/lib.rs @@ -1,368 +1,24 @@ //! OpenVM crypto providers for REVM and Alloy. //! -//! Cryptographic operations live in `openvm-accelerators`; this crate only -//! adapts their byte-oriented API to REVM and Alloy's provider traits. +//! These adapters use the standard zkVM accelerator C interface. #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +mod alloy; +mod revm; -use alloy_consensus::crypto::{ - backend::{install_default_provider, CryptoProvider}, - RecoveryError, -}; -use alloy_primitives::Address; -use openvm_accelerators::{ - blake2f, bls12_381_g1_add, bls12_381_g1_msm, bls12_381_g2_add, bls12_381_g2_msm, - bls12_381_map_fp2_to_g2, bls12_381_map_fp_to_g1, bls12_381_pairing_check, bn254_g1_add, - bn254_g1_mul, bn254_pairing_check, keccak256, kzg_point_eval, modexp, ripemd160, - secp256k1_ecrecover, secp256k1_verify, secp256r1_verify, sha256, Error, StreamError, -}; -use revm::{ - install_crypto, - precompile::{ - bls12_381::{ - G1Point as BlsG1Point, G1PointScalar as BlsG1PointScalar, G2Point as BlsG2Point, - G2PointScalar as BlsG2PointScalar, - }, - bls12_381_const::{ - FP_LENGTH as BLS_FP_LEN, G1_LENGTH as BLS_G1_LEN, G2_LENGTH as BLS_G2_LEN, - }, - Crypto, PrecompileHalt, - }, -}; +use alloc::boxed::Box; +use core::error::Error; +use openvm_accelerators::{zkvm_status, ZKVM_EOK}; -#[derive(Debug, Default)] -struct OpenVmK256Provider; - -impl CryptoProvider for OpenVmK256Provider { - fn recover_signer_unchecked( - &self, - sig: &[u8; 65], - msg: &[u8; 32], - ) -> Result { - let pubkey = secp256k1_ecrecover(msg, sig[..64].try_into().unwrap(), sig[64]) - .map_err(|_| RecoveryError::new())?; - Ok(address_from_pubkey(&pubkey)) - } - - fn verify_and_compute_signer_unchecked( - &self, - pubkey: &[u8; 65], - sig: &[u8; 64], - msg: &[u8; 32], - ) -> Result { - if pubkey[0] != 0x04 { - return Err(RecoveryError::new()); - } - - let pubkey: &[u8; 64] = pubkey[1..].try_into().unwrap(); - if !secp256k1_verify(msg, sig, pubkey) { - return Err(RecoveryError::new()); - } - - Ok(address_from_pubkey(pubkey)) - } -} - -fn address_from_pubkey(pubkey: &[u8; 64]) -> Address { - Address::from_slice(&keccak256(pubkey)[12..]) -} - -#[derive(Debug, Default)] -struct OpenVmCrypto; - -impl Crypto for OpenVmCrypto { - fn sha256(&self, input: &[u8]) -> [u8; 32] { - sha256(input) - } - - fn ripemd160(&self, input: &[u8]) -> [u8; 32] { - ripemd160(input) - } - - fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> { - bn254_g1_add(p1, p2).map_err(map_bn_error) - } - - fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> { - bn254_g1_mul(point, scalar).map_err(map_bn_error) - } - - fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result { - bn254_pairing_check(pairs.iter().copied()).map_err(map_bn_error) - } - - fn secp256k1_ecrecover( - &self, - sig: &[u8; 64], - recid: u8, - msg: &[u8; 32], - ) -> Result<[u8; 32], PrecompileHalt> { - let pubkey = secp256k1_ecrecover(msg, sig, recid) - .map_err(|_| PrecompileHalt::Secp256k1RecoverFailed)?; - - let mut hash = keccak256(&pubkey); - hash[..12].fill(0); - Ok(hash) - } - - fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileHalt> { - Ok(modexp(base, exp, modulus)) - } - - fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { - blake2f(rounds, h, m, t, f); - } - - fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool { - secp256r1_verify(msg, sig, pk) - } - - fn verify_kzg_proof( - &self, - z: &[u8; 32], - y: &[u8; 32], - commitment: &[u8; 48], - proof: &[u8; 48], - ) -> Result<(), PrecompileHalt> { - let verified = kzg_point_eval(commitment, z, y, proof) - .map_err(|_| PrecompileHalt::BlobVerifyKzgProofFailed)?; - if verified { - Ok(()) - } else { - Err(PrecompileHalt::BlobVerifyKzgProofFailed) - } - } - - fn bls12_381_g1_add( - &self, - a: BlsG1Point, - b: BlsG1Point, - ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - bls12_381_g1_add(&a, &b).map_err(map_bls_g1_error) - } - - fn bls12_381_g1_msm( - &self, - pairs: &mut dyn Iterator>, - ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - bls12_381_g1_msm(pairs).map_err(|error| map_stream_error(error, map_bls_g1_error)) - } - - fn bls12_381_g2_add( - &self, - a: BlsG2Point, - b: BlsG2Point, - ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - bls12_381_g2_add(&a, &b).map_err(map_bls_g2_error) - } - - fn bls12_381_g2_msm( - &self, - pairs: &mut dyn Iterator>, - ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - bls12_381_g2_msm(pairs).map_err(|error| map_stream_error(error, map_bls_g2_error)) - } - - fn bls12_381_pairing_check( - &self, - pairs: &[(BlsG1Point, BlsG2Point)], - ) -> Result { - bls12_381_pairing_check(pairs.iter().copied()).map_err(map_bls_pairing_error) - } - - fn bls12_381_fp_to_g1( - &self, - fp: &[u8; BLS_FP_LEN], - ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { - bls12_381_map_fp_to_g1(fp).map_err(map_bls_field_error) - } - - fn bls12_381_fp2_to_g2( - &self, - fp2: ([u8; BLS_FP_LEN], [u8; BLS_FP_LEN]), - ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { - bls12_381_map_fp2_to_g2(&fp2).map_err(map_bls_field_error) - } -} - -fn map_stream_error( - error: StreamError, - map_operation: fn(Error) -> PrecompileHalt, -) -> PrecompileHalt { - match error { - StreamError::Source(error) => error, - StreamError::Operation(error) => map_operation(error), - } -} - -fn map_bn_error(error: Error) -> PrecompileHalt { - match error { - Error::InvalidLength => PrecompileHalt::Bn254PairLength, - Error::FieldElementInvalid => PrecompileHalt::Bn254FieldPointNotAMember, - Error::PointNotOnCurve | Error::PointNotInSubgroup => { - PrecompileHalt::Bn254AffineGFailedToCreate - } - _ => PrecompileHalt::other("unexpected BN254 accelerator error"), - } -} - -fn map_bls_g1_error(error: Error) -> PrecompileHalt { - match error { - Error::PointNotInSubgroup => PrecompileHalt::Bls12381G1NotInSubgroup, - Error::PointNotOnCurve => PrecompileHalt::Bls12381G1NotOnCurve, - Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, - _ => PrecompileHalt::other("unexpected BLS12-381 G1 accelerator error"), - } -} - -fn map_bls_g2_error(error: Error) -> PrecompileHalt { - match error { - Error::PointNotInSubgroup => PrecompileHalt::Bls12381G2NotInSubgroup, - Error::PointNotOnCurve => PrecompileHalt::Bls12381G2NotOnCurve, - Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, - _ => PrecompileHalt::other("unexpected BLS12-381 G2 accelerator error"), - } -} - -fn map_bls_pairing_error(error: Error) -> PrecompileHalt { - match error { - Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, - Error::BlsG1PointNotOnCurve => PrecompileHalt::Bls12381G1NotOnCurve, - Error::BlsG1PointNotInSubgroup => PrecompileHalt::Bls12381G1NotInSubgroup, - Error::BlsG2PointNotOnCurve => PrecompileHalt::Bls12381G2NotOnCurve, - Error::BlsG2PointNotInSubgroup => PrecompileHalt::Bls12381G2NotInSubgroup, - _ => PrecompileHalt::other("unexpected BLS12-381 pairing accelerator error"), - } -} - -fn map_bls_field_error(error: Error) -> PrecompileHalt { - match error { - Error::FieldElementInvalid => PrecompileHalt::NonCanonicalFp, - _ => PrecompileHalt::other("unexpected BLS12-381 map accelerator error"), - } +fn status_ok(status: zkvm_status) -> bool { + status == ZKVM_EOK } /// Install the OpenVM implementations globally. -pub fn install_openvm_crypto() -> Result> { - install_default_provider(Arc::new(OpenVmK256Provider))?; - Ok(install_crypto(OpenVmCrypto)) -} - -#[cfg(test)] -mod tests { - use super::*; - use revm::precompile::DefaultCrypto; - - fn p256_verify_input(input_hex: &str) -> bool { - let input = alloy_primitives::hex::decode(input_hex).unwrap(); - assert_eq!(input.len(), 160); - OpenVmCrypto.secp256r1_verify_signature( - input[..32].try_into().unwrap(), - input[32..96].try_into().unwrap(), - input[96..160].try_into().unwrap(), - ) - } - - // Vectors from daimo-eth/p256-verifier, also used by revm-precompile. - #[test] - fn secp256r1_verify_signature() { - assert!(p256_verify_input("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e")); - assert!(!p256_verify_input("3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e")); - } - - #[test] - fn modexp_dispatch_and_padding() { - let modulus = alloy_primitives::hex::decode( - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", - ) - .unwrap(); - let accelerated = OpenVmCrypto.modexp(&[3], &[5], &modulus).unwrap(); - assert_eq!(accelerated.len(), 32); - assert!(accelerated[..31].iter().all(|byte| *byte == 0)); - assert_eq!(accelerated[31], 243); - - assert_eq!(OpenVmCrypto.modexp(&[3], &[4], &[7]).unwrap(), [4]); - } - - #[test] - fn ripemd160_adapter_uses_evm_padding() { - assert_eq!( - OpenVmCrypto.ripemd160(b"abc"), - alloy_primitives::hex!( - "0000000000000000000000008eb208f7e05d987a9b044a8e98c6b087f15a0bfc" - ) - ); - } - - #[test] - fn adapters_match_revm_for_portable_primitives() { - let input = b"OpenVM accelerator provider"; - assert_eq!(OpenVmCrypto.sha256(input), DefaultCrypto.sha256(input)); - assert_eq!(OpenVmCrypto.ripemd160(input), DefaultCrypto.ripemd160(input)); - assert_eq!( - OpenVmCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]), - DefaultCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]) - ); - - let mut actual = [ - 0x6a09e667f3bcc908, - 0xbb67ae8584caa73b, - 0x3c6ef372fe94f82b, - 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, - 0x9b05688c2b3e6c1f, - 0x1f83d9abfb41bd6b, - 0x5be0cd19137e2179, - ]; - let mut expected = actual; - let message = [0x0123_4567_89ab_cdef; 16]; - let offset = [0x1020_3040_5060_7080, 0x90a0_b0c0_d0e0_f000]; - OpenVmCrypto.blake2_compress(12, &mut actual, &message, &offset, true); - DefaultCrypto.blake2_compress(12, &mut expected, &message, &offset, true); - assert_eq!(actual, expected); - } - - #[test] - fn adapters_preserve_revm_error_variants() { - let invalid_bn_point = [0xff; 64]; - let actual = OpenVmCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64]); - assert_eq!(actual, Err(PrecompileHalt::Bn254FieldPointNotAMember)); - assert_eq!(actual, DefaultCrypto.bn254_g1_add(&invalid_bn_point, &[0; 64])); - - let noncanonical_fp = [0xff; BLS_FP_LEN]; - let actual = OpenVmCrypto.bls12_381_fp_to_g1(&noncanonical_fp); - assert_eq!(actual, Err(PrecompileHalt::NonCanonicalFp)); - assert_eq!(actual, DefaultCrypto.bls12_381_fp_to_g1(&noncanonical_fp)); - - assert_eq!( - OpenVmCrypto.secp256k1_ecrecover(&[0; 64], 0, &[0; 32]), - Err(PrecompileHalt::Secp256k1RecoverFailed) - ); - } - - #[test] - fn streaming_adapters_report_the_first_invalid_pair() { - let invalid_bn_g1 = [0xff; 64]; - let identity_bn_g2 = [0; 128]; - let identity_bn_g1 = [0; 64]; - let short_bn_g2 = [0; 127]; - let bn_pairs = - [(&invalid_bn_g1[..], &identity_bn_g2[..]), (&identity_bn_g1[..], &short_bn_g2[..])]; - assert_eq!( - OpenVmCrypto.bn254_pairing_check(&bn_pairs), - Err(PrecompileHalt::Bn254FieldPointNotAMember) - ); - - let invalid_g1 = ([0xff; BLS_FP_LEN], [0xff; BLS_FP_LEN]); - let mut g1_pairs = - [Ok((invalid_g1, [0; 32])), Err(PrecompileHalt::Bls12381ScalarInputLength)].into_iter(); - assert_eq!( - OpenVmCrypto.bls12_381_g1_msm(&mut g1_pairs), - Err(PrecompileHalt::NonCanonicalFp) - ); - } +pub fn install_openvm_crypto() -> Result> { + alloy::install()?; + Ok(revm::install()) } diff --git a/crates/revm-crypto/src/revm.rs b/crates/revm-crypto/src/revm.rs new file mode 100644 index 000000000..ba7468a8a --- /dev/null +++ b/crates/revm-crypto/src/revm.rs @@ -0,0 +1,391 @@ +//! REVM adapter for the standard zkVM accelerator C interface. + +use alloc::vec::Vec; +use core::mem::MaybeUninit; + +use crate::status_ok; +use openvm_accelerators::{ + zkvm_blake2f, zkvm_blake2f_message, zkvm_blake2f_offset, zkvm_blake2f_state, zkvm_bls12_381_fp, + zkvm_bls12_381_fp2, zkvm_bls12_381_g1_msm_pair, zkvm_bls12_381_g1_point, + zkvm_bls12_381_g2_msm_pair, zkvm_bls12_381_g2_point, zkvm_bls12_381_pairing_pair, + zkvm_bls12_381_scalar, zkvm_bls12_g1_add, zkvm_bls12_g1_msm, zkvm_bls12_g2_add, + zkvm_bls12_g2_msm, zkvm_bls12_map_fp2_to_g2, zkvm_bls12_map_fp_to_g1, zkvm_bls12_pairing, + zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_g1_point, zkvm_bn254_g2_point, + zkvm_bn254_pairing, zkvm_bn254_pairing_pair, zkvm_bn254_scalar, zkvm_keccak256, + zkvm_keccak256_hash, zkvm_kzg_commitment, zkvm_kzg_field_element, zkvm_kzg_point_eval, + zkvm_kzg_proof, zkvm_modexp, zkvm_ripemd160, zkvm_ripemd160_hash, zkvm_secp256k1_ecrecover, + zkvm_secp256k1_hash, zkvm_secp256k1_pubkey, zkvm_secp256k1_signature, zkvm_secp256r1_hash, + zkvm_secp256r1_pubkey, zkvm_secp256r1_signature, zkvm_secp256r1_verify, zkvm_sha256, + zkvm_sha256_hash, +}; +use revm::precompile::{ + bls12_381::{ + G1Point as BlsG1Point, G1PointScalar as BlsG1PointScalar, G2Point as BlsG2Point, + G2PointScalar as BlsG2PointScalar, + }, + bls12_381_const::{FP_LENGTH as BLS_FP_LEN, G1_LENGTH as BLS_G1_LEN, G2_LENGTH as BLS_G2_LEN}, + Crypto, PrecompileHalt, +}; + +fn bls_g1((x, y): BlsG1Point) -> zkvm_bls12_381_g1_point { + let mut data = [0; BLS_G1_LEN]; + data[..BLS_FP_LEN].copy_from_slice(&x); + data[BLS_FP_LEN..].copy_from_slice(&y); + zkvm_bls12_381_g1_point { data } +} + +fn bls_g2((x0, x1, y0, y1): BlsG2Point) -> zkvm_bls12_381_g2_point { + let mut data = [0; BLS_G2_LEN]; + for (output, coordinate) in data.chunks_exact_mut(BLS_FP_LEN).zip([x0, x1, y0, y1]) { + output.copy_from_slice(&coordinate); + } + zkvm_bls12_381_g2_point { data } +} + +fn write_words(words: &[u64; N], bytes: &mut [u8]) { + for (output, word) in bytes.chunks_exact_mut(8).zip(words) { + output.copy_from_slice(&word.to_le_bytes()); + } +} + +#[derive(Debug, Default)] +struct OpenVmCrypto; + +impl Crypto for OpenVmCrypto { + fn sha256(&self, input: &[u8]) -> [u8; 32] { + let mut output = zkvm_sha256_hash { data: [0; 32] }; + let status = unsafe { zkvm_sha256(input.as_ptr(), input.len(), &mut output) }; + assert!(status_ok(status), "zkVM accelerator call failed"); + output.data + } + + fn ripemd160(&self, input: &[u8]) -> [u8; 32] { + let mut output = zkvm_ripemd160_hash { data: [0; 32] }; + let status = unsafe { zkvm_ripemd160(input.as_ptr(), input.len(), &mut output) }; + assert!(status_ok(status), "zkVM accelerator call failed"); + output.data + } + + fn bn254_g1_add(&self, p1: &[u8], p2: &[u8]) -> Result<[u8; 64], PrecompileHalt> { + let p1 = zkvm_bn254_g1_point { + data: p1.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let p2 = zkvm_bn254_g1_point { + data: p2.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let mut output = zkvm_bn254_g1_point { data: [0; 64] }; + let status = unsafe { zkvm_bn254_g1_add(&p1, &p2, &mut output) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bn254AffineGFailedToCreate); + } + Ok(output.data) + } + + fn bn254_g1_mul(&self, point: &[u8], scalar: &[u8]) -> Result<[u8; 64], PrecompileHalt> { + let point = zkvm_bn254_g1_point { + data: point.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let scalar = zkvm_bn254_scalar { + data: scalar.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }; + let mut output = zkvm_bn254_g1_point { data: [0; 64] }; + let status = unsafe { zkvm_bn254_g1_mul(&point, &scalar, &mut output) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bn254AffineGFailedToCreate); + } + Ok(output.data) + } + + fn bn254_pairing_check(&self, pairs: &[(&[u8], &[u8])]) -> Result { + let pairs: Result, _> = pairs + .iter() + .map(|&(g1, g2)| { + Ok(zkvm_bn254_pairing_pair { + g1: zkvm_bn254_g1_point { + data: g1.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }, + g2: zkvm_bn254_g2_point { + data: g2.try_into().map_err(|_| PrecompileHalt::Bn254PairLength)?, + }, + }) + }) + .collect(); + let pairs = pairs?; + let mut verified = false; + let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bn254AffineGFailedToCreate); + } + Ok(verified) + } + + fn secp256k1_ecrecover( + &self, + sig: &[u8; 64], + recid: u8, + msg: &[u8; 32], + ) -> Result<[u8; 32], PrecompileHalt> { + let msg = zkvm_secp256k1_hash { data: *msg }; + let sig = zkvm_secp256k1_signature { data: *sig }; + let mut pubkey = zkvm_secp256k1_pubkey { data: [0; 64] }; + let status = unsafe { zkvm_secp256k1_ecrecover(&msg, &sig, recid, &mut pubkey) }; + if !status_ok(status) { + return Err(PrecompileHalt::Secp256k1RecoverFailed); + } + let mut hash = zkvm_keccak256_hash { data: [0; 32] }; + let status = unsafe { zkvm_keccak256(pubkey.data.as_ptr(), pubkey.data.len(), &mut hash) }; + assert!(status_ok(status), "zkVM accelerator call failed"); + hash.data[..12].fill(0); + Ok(hash.data) + } + + fn modexp(&self, base: &[u8], exp: &[u8], modulus: &[u8]) -> Result, PrecompileHalt> { + let mut output = alloc::vec![0; modulus.len()]; + let status = unsafe { + zkvm_modexp( + base.as_ptr(), + base.len(), + exp.as_ptr(), + exp.len(), + modulus.as_ptr(), + modulus.len(), + output.as_mut_ptr(), + ) + }; + assert!(status_ok(status), "zkVM accelerator call failed"); + Ok(output) + } + + fn blake2_compress(&self, rounds: u32, h: &mut [u64; 8], m: &[u64; 16], t: &[u64; 2], f: bool) { + let mut state = zkvm_blake2f_state { data: [0; 64] }; + let mut message = zkvm_blake2f_message { data: [0; 128] }; + let mut offset = zkvm_blake2f_offset { data: [0; 16] }; + write_words(h, &mut state.data); + write_words(m, &mut message.data); + write_words(t, &mut offset.data); + let status = unsafe { zkvm_blake2f(rounds, &mut state, &message, &offset, u8::from(f)) }; + assert!(status_ok(status), "zkVM accelerator call failed"); + for (word, bytes) in h.iter_mut().zip(state.data.as_chunks::<8>().0) { + *word = u64::from_le_bytes(*bytes); + } + } + + fn secp256r1_verify_signature(&self, msg: &[u8; 32], sig: &[u8; 64], pk: &[u8; 64]) -> bool { + let msg = zkvm_secp256r1_hash { data: *msg }; + let sig = zkvm_secp256r1_signature { data: *sig }; + let pubkey = zkvm_secp256r1_pubkey { data: *pk }; + let mut verified = false; + let status = unsafe { zkvm_secp256r1_verify(&msg, &sig, &pubkey, &mut verified) }; + status_ok(status) && verified + } + + fn verify_kzg_proof( + &self, + z: &[u8; 32], + y: &[u8; 32], + commitment: &[u8; 48], + proof: &[u8; 48], + ) -> Result<(), PrecompileHalt> { + let commitment = zkvm_kzg_commitment { data: *commitment }; + let z = zkvm_kzg_field_element { data: *z }; + let y = zkvm_kzg_field_element { data: *y }; + let proof = zkvm_kzg_proof { data: *proof }; + let mut verified = false; + let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) }; + if status_ok(status) && verified { + Ok(()) + } else { + Err(PrecompileHalt::BlobVerifyKzgProofFailed) + } + } + + fn bls12_381_g1_add( + &self, + a: BlsG1Point, + b: BlsG1Point, + ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { + let (a, b) = (bls_g1(a), bls_g1(b)); + let mut output = MaybeUninit::::uninit(); + let status = unsafe { zkvm_bls12_g1_add(&a, &b, output.as_mut_ptr()) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bls12381G1NotOnCurve); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } + + fn bls12_381_g1_msm( + &self, + pairs: &mut dyn Iterator>, + ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { + let mut wire_pairs = Vec::with_capacity(pairs.size_hint().0); + for pair in pairs { + let (point, scalar) = pair?; + wire_pairs.push(zkvm_bls12_381_g1_msm_pair { + point: bls_g1(point), + scalar: zkvm_bls12_381_scalar { data: scalar }, + }); + } + let mut output = MaybeUninit::::uninit(); + let status = unsafe { + zkvm_bls12_g1_msm(wire_pairs.as_ptr(), wire_pairs.len(), output.as_mut_ptr()) + }; + if !status_ok(status) { + return Err(PrecompileHalt::Bls12381G1NotInSubgroup); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } + + fn bls12_381_g2_add( + &self, + a: BlsG2Point, + b: BlsG2Point, + ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { + let (a, b) = (bls_g2(a), bls_g2(b)); + let mut output = MaybeUninit::::uninit(); + let status = unsafe { zkvm_bls12_g2_add(&a, &b, output.as_mut_ptr()) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bls12381G2NotOnCurve); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } + + fn bls12_381_g2_msm( + &self, + pairs: &mut dyn Iterator>, + ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { + let mut wire_pairs = Vec::with_capacity(pairs.size_hint().0); + for pair in pairs { + let (point, scalar) = pair?; + wire_pairs.push(zkvm_bls12_381_g2_msm_pair { + point: bls_g2(point), + scalar: zkvm_bls12_381_scalar { data: scalar }, + }); + } + let mut output = MaybeUninit::::uninit(); + let status = unsafe { + zkvm_bls12_g2_msm(wire_pairs.as_ptr(), wire_pairs.len(), output.as_mut_ptr()) + }; + if !status_ok(status) { + return Err(PrecompileHalt::Bls12381G2NotInSubgroup); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } + + fn bls12_381_pairing_check( + &self, + pairs: &[(BlsG1Point, BlsG2Point)], + ) -> Result { + let pairs: Vec<_> = pairs + .iter() + .copied() + .map(|(p1, p2)| zkvm_bls12_381_pairing_pair { g1: bls_g1(p1), g2: bls_g2(p2) }) + .collect(); + let mut verified = MaybeUninit::::uninit(); + let status = + unsafe { zkvm_bls12_pairing(pairs.as_ptr(), pairs.len(), verified.as_mut_ptr()) }; + if !status_ok(status) { + return Err(PrecompileHalt::Bls12381G1NotInSubgroup); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { verified.assume_init() }) + } + + fn bls12_381_fp_to_g1( + &self, + fp: &[u8; BLS_FP_LEN], + ) -> Result<[u8; BLS_G1_LEN], PrecompileHalt> { + let fp = zkvm_bls12_381_fp { data: *fp }; + let mut output = MaybeUninit::::uninit(); + let status = unsafe { zkvm_bls12_map_fp_to_g1(&fp, output.as_mut_ptr()) }; + if !status_ok(status) { + return Err(PrecompileHalt::NonCanonicalFp); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } + + fn bls12_381_fp2_to_g2( + &self, + fp2: ([u8; BLS_FP_LEN], [u8; BLS_FP_LEN]), + ) -> Result<[u8; BLS_G2_LEN], PrecompileHalt> { + let mut data = [0; BLS_FP_LEN * 2]; + data[..BLS_FP_LEN].copy_from_slice(&fp2.0); + data[BLS_FP_LEN..].copy_from_slice(&fp2.1); + let fp2 = zkvm_bls12_381_fp2 { data }; + let mut output = MaybeUninit::::uninit(); + let status = unsafe { zkvm_bls12_map_fp2_to_g2(&fp2, output.as_mut_ptr()) }; + if !status_ok(status) { + return Err(PrecompileHalt::NonCanonicalFp); + } + // SAFETY: the C interface initializes the output when it returns success. + Ok(unsafe { output.assume_init() }.data) + } +} + +pub(super) fn install() -> bool { + revm::install_crypto(OpenVmCrypto) +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::precompile::DefaultCrypto; + + #[test] + fn portable_operations_match_revm() { + let input = b"OpenVM accelerator C interface"; + assert_eq!(OpenVmCrypto.sha256(input), DefaultCrypto.sha256(input)); + assert_eq!(OpenVmCrypto.ripemd160(input), DefaultCrypto.ripemd160(input)); + assert_eq!( + OpenVmCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]), + DefaultCrypto.modexp(&[0x12; 40], &[0x34; 3], &[0xef; 24]) + ); + + let mut actual = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, + ]; + let mut expected = actual; + let message = [0x0123_4567_89ab_cdef; 16]; + let offset = [0x1020_3040_5060_7080, 0x90a0_b0c0_d0e0_f000]; + OpenVmCrypto.blake2_compress(12, &mut actual, &message, &offset, true); + DefaultCrypto.blake2_compress(12, &mut expected, &message, &offset, true); + assert_eq!(actual, expected); + } + + #[test] + fn p256_accepts_valid_and_rejects_invalid_signatures() { + let input = alloy_primitives::hex::decode("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").unwrap(); + let msg = input[..32].try_into().unwrap(); + let signature = input[32..96].try_into().unwrap(); + let public_key = input[96..].try_into().unwrap(); + assert!(OpenVmCrypto.secp256r1_verify_signature(msg, signature, public_key)); + assert!(!OpenVmCrypto.secp256r1_verify_signature(msg, &[0; 64], public_key)); + } + + #[test] + fn c_status_failures_are_mapped_at_the_client_boundary() { + assert_eq!( + OpenVmCrypto.secp256k1_ecrecover(&[0; 64], 0, &[0; 32]), + Err(PrecompileHalt::Secp256k1RecoverFailed) + ); + assert_eq!( + OpenVmCrypto.bls12_381_fp_to_g1(&[0xff; BLS_FP_LEN]), + Err(PrecompileHalt::NonCanonicalFp) + ); + assert_eq!( + OpenVmCrypto.bn254_g1_add(&[0; 63], &[0; 64]), + Err(PrecompileHalt::Bn254PairLength) + ); + } +} From 1b1099877da0968151f3ed1d33c6cdb63dcf8794 Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 12 Aug 2026 10:25:54 -0400 Subject: [PATCH 43/44] refactor: remove old upper case naming --- crates/accelerators/tests/bn254.rs | 34 ++++++++++++++---------------- crates/accelerators/tests/kzg.rs | 24 ++++++++++----------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/crates/accelerators/tests/bn254.rs b/crates/accelerators/tests/bn254.rs index 1d9d95dba..44b975748 100644 --- a/crates/accelerators/tests/bn254.rs +++ b/crates/accelerators/tests/bn254.rs @@ -2,28 +2,26 @@ use hex_literal::hex; use openvm_accelerators::{ - zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_g1_point as ZkvmBn254G1Point, - zkvm_bn254_g2_point as ZkvmBn254G2Point, zkvm_bn254_pairing, - zkvm_bn254_pairing_pair as ZkvmBn254PairingPair, zkvm_bn254_scalar as ZkvmBn254Scalar, - ZKVM_EFAIL, ZKVM_EOK, + zkvm_bn254_g1_add, zkvm_bn254_g1_mul, zkvm_bn254_g1_point, zkvm_bn254_g2_point, + zkvm_bn254_pairing, zkvm_bn254_pairing_pair, zkvm_bn254_scalar, ZKVM_EFAIL, ZKVM_EOK, }; -fn scalar(value: u8) -> ZkvmBn254Scalar { - let mut scalar = ZkvmBn254Scalar { data: [0; 32] }; +fn scalar(value: u8) -> zkvm_bn254_scalar { + let mut scalar = zkvm_bn254_scalar { data: [0; 32] }; scalar.data[31] = value; scalar } /// BN254 generator (1, 2). -fn generator() -> ZkvmBn254G1Point { - let mut point = ZkvmBn254G1Point { data: [0; 64] }; +fn generator() -> zkvm_bn254_g1_point { + let mut point = zkvm_bn254_g1_point { 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 { +const BN254_2GEN: zkvm_bn254_g1_point = zkvm_bn254_g1_point { data: hex!( "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd3" "15ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4" @@ -31,7 +29,7 @@ const BN254_2GEN: ZkvmBn254G1Point = ZkvmBn254G1Point { }; /// BN254 negated generator (1, p - 2). -const BN254_NEG_GEN: ZkvmBn254G1Point = ZkvmBn254G1Point { +const BN254_NEG_GEN: zkvm_bn254_g1_point = zkvm_bn254_g1_point { data: hex!( "0000000000000000000000000000000000000000000000000000000000000001" "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45" @@ -39,7 +37,7 @@ const BN254_NEG_GEN: ZkvmBn254G1Point = ZkvmBn254G1Point { }; /// BN254 G2 generator in EIP-197 order (`x_c1 || x_c0 || y_c1 || y_c0`). -const BN254_G2_GEN: ZkvmBn254G2Point = ZkvmBn254G2Point { +const BN254_G2_GEN: zkvm_bn254_g2_point = zkvm_bn254_g2_point { data: hex!( "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2" "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed" @@ -51,7 +49,7 @@ const BN254_G2_GEN: ZkvmBn254G2Point = ZkvmBn254G2Point { #[test] fn zkvm_bn254_add_mul_smoke() { let point = generator(); - let mut output = ZkvmBn254G1Point { data: [0; 64] }; + let mut output = zkvm_bn254_g1_point { data: [0; 64] }; let status = unsafe { zkvm_bn254_g1_add(&point, &point, &mut output) }; assert_eq!(status, ZKVM_EOK); @@ -66,8 +64,8 @@ fn zkvm_bn254_add_mul_smoke() { #[test] fn zkvm_bn254_pairing_smoke() { let pairs = [ - ZkvmBn254PairingPair { g1: generator(), g2: BN254_G2_GEN }, - ZkvmBn254PairingPair { g1: BN254_NEG_GEN, g2: BN254_G2_GEN }, + zkvm_bn254_pairing_pair { g1: generator(), g2: BN254_G2_GEN }, + zkvm_bn254_pairing_pair { g1: BN254_NEG_GEN, g2: BN254_G2_GEN }, ]; let mut verified = false; @@ -84,12 +82,12 @@ fn zkvm_bn254_pairing_smoke() { fn bn254_rejects_invalid_point() { let mut not_on_curve = generator(); not_on_curve.data[63] = 3; - let mut output = ZkvmBn254G1Point { data: [0; 64] }; + let mut output = zkvm_bn254_g1_point { data: [0; 64] }; let status = unsafe { zkvm_bn254_g1_mul(¬_on_curve, &scalar(2), &mut output) }; assert_eq!(status, ZKVM_EFAIL); - let pairs = [ZkvmBn254PairingPair { g1: not_on_curve, g2: BN254_G2_GEN }]; + let pairs = [zkvm_bn254_pairing_pair { g1: not_on_curve, g2: BN254_G2_GEN }]; let mut verified = true; let status = unsafe { zkvm_bn254_pairing(pairs.as_ptr(), pairs.len(), &mut verified) }; assert_eq!(status, ZKVM_EFAIL); @@ -100,7 +98,7 @@ fn bn254_rejects_invalid_point() { fn zkvm_bn254_null_pointers() { let point = generator(); let scalar = scalar(2); - let mut output = ZkvmBn254G1Point { data: [0; 64] }; + let mut output = zkvm_bn254_g1_point { data: [0; 64] }; let status = unsafe { zkvm_bn254_g1_add(core::ptr::null(), &point, &mut output) }; assert_eq!(status, ZKVM_EFAIL); @@ -120,7 +118,7 @@ fn zkvm_bn254_null_pointers() { let status = unsafe { zkvm_bn254_g1_mul(&point, &scalar, core::ptr::null_mut()) }; assert_eq!(status, ZKVM_EFAIL); - let pairs = [ZkvmBn254PairingPair { g1: point, g2: BN254_G2_GEN }]; + let pairs = [zkvm_bn254_pairing_pair { g1: point, g2: BN254_G2_GEN }]; let mut verified = false; let status = unsafe { zkvm_bn254_pairing(core::ptr::null(), 0, &mut verified) }; diff --git a/crates/accelerators/tests/kzg.rs b/crates/accelerators/tests/kzg.rs index de0471148..d4fb894a8 100644 --- a/crates/accelerators/tests/kzg.rs +++ b/crates/accelerators/tests/kzg.rs @@ -2,8 +2,8 @@ use hex_literal::hex; use openvm_accelerators::{ - zkvm_kzg_commitment as ZkvmKzgCommitment, zkvm_kzg_field_element as ZkvmKzgFieldElement, - zkvm_kzg_point_eval, zkvm_kzg_proof as ZkvmKzgProof, ZKVM_EFAIL, ZKVM_EOK, + zkvm_kzg_commitment, zkvm_kzg_field_element, zkvm_kzg_point_eval, zkvm_kzg_proof, ZKVM_EFAIL, + ZKVM_EOK, }; // ethereum/consensus-spec-tests: @@ -16,24 +16,24 @@ const PROOF: [u8; 48] = hex!("92c51ff81dd71dab71cefecd79e8274b4b7ba36a0f40e2dc086bc4061c7f63249877db23297212991fd63e07b7ebc348"); /// The compressed point at infinity: 0xc0 followed by zeros. -fn infinity() -> ZkvmKzgCommitment { - let mut point = ZkvmKzgCommitment { data: [0; 48] }; +fn infinity() -> zkvm_kzg_commitment { + let mut point = zkvm_kzg_commitment { data: [0; 48] }; point.data[0] = 0xc0; point } -fn scalar(value: u8) -> ZkvmKzgFieldElement { - let mut s = ZkvmKzgFieldElement { data: [0; 32] }; +fn scalar(value: u8) -> zkvm_kzg_field_element { + let mut s = zkvm_kzg_field_element { data: [0; 32] }; s.data[31] = value; s } #[test] fn zkvm_kzg_point_eval_smoke() { - let commitment = ZkvmKzgCommitment { data: COMMITMENT }; - let proof = ZkvmKzgProof { data: PROOF }; - let z = ZkvmKzgFieldElement { data: Z }; - let y = ZkvmKzgFieldElement { data: Y }; + let commitment = zkvm_kzg_commitment { data: COMMITMENT }; + let proof = zkvm_kzg_proof { data: PROOF }; + let z = zkvm_kzg_field_element { data: Z }; + let y = zkvm_kzg_field_element { data: Y }; let mut verified = false; let status = unsafe { zkvm_kzg_point_eval(&commitment, &z, &y, &proof, &mut verified) }; @@ -41,7 +41,7 @@ fn zkvm_kzg_point_eval_smoke() { assert!(verified); // Malformed cryptographic inputs are a completed verification with a false result. - let mut garbage = ZkvmKzgCommitment { data: [0; 48] }; + let mut garbage = zkvm_kzg_commitment { data: [0; 48] }; garbage.data[0] = 0x01; let status = unsafe { zkvm_kzg_point_eval(&garbage, &z, &y, &proof, &mut verified) }; assert_eq!(status, ZKVM_EOK); @@ -51,7 +51,7 @@ fn zkvm_kzg_point_eval_smoke() { #[test] fn zkvm_kzg_point_eval_null_pointers() { let commitment = infinity(); - let proof: ZkvmKzgProof = infinity(); + let proof: zkvm_kzg_proof = infinity(); let z = scalar(2); let y = scalar(0); let mut verified = false; From 4a3a1c024e08a66d752b93c3f736c69c27df741a Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Wed, 12 Aug 2026 10:53:02 -0400 Subject: [PATCH 44/44] ci: run OpenVM REVM Crypto Tests on accelerator crate changes --- .github/workflows/tests-revm-crypto.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests-revm-crypto.yml b/.github/workflows/tests-revm-crypto.yml index 0333bd062..bdc30d44d 100644 --- a/.github/workflows/tests-revm-crypto.yml +++ b/.github/workflows/tests-revm-crypto.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - "crates/revm-crypto/**" + - "crates/accelerators/**" - "crates/kzg/**" - "crates/curve-utils/**" - "Cargo.toml"