From bfde64f6433e7b59c7288c4464856a55ba0715be Mon Sep 17 00:00:00 2001 From: Mansur Mukimbekov Date: Tue, 28 Jul 2026 17:08:45 -0400 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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