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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/tests-accelerators.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"crates/curve-utils",
"crates/kzg",
"crates/kzg/tests/programs/verify_kzg",
"crates/accelerators",
]
exclude = []
resolver = "3"
Expand Down
33 changes: 33 additions & 0 deletions crates/accelerators/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[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

[dependencies]
# openvm
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"] }
openvm-sha2 = { workspace = true, features = ["import_sha2"] }

[dev-dependencies]
hex-literal.workspace = true

[features]
default = ["ffi"]
# The extern "C" `zkvm_*` symbols. Rust consumers that only need `ops` can
# disable this.
ffi = []
std = []
90 changes: 90 additions & 0 deletions crates/accelerators/src/ffi/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! 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) } };
// SAFETY: non-NULL checked above; validity is guaranteed by the caller.
let output = unsafe { &mut *output };
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
}

/// 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
}
9 changes: 9 additions & 0 deletions crates/accelerators/src/ffi/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
8 changes: 8 additions & 0 deletions crates/accelerators/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! OpenVM implementation of the zkVM Cryptographic Accelerators C Interface.

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "ffi")]
pub mod ffi;
pub mod ops;
pub mod types;
30 changes: 30 additions & 0 deletions crates/accelerators/src/ops/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Hash operations.

use crate::types::{ZkvmKeccak256Hash, ZkvmRipemd160Hash, 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) {
#[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());
}
23 changes: 23 additions & 0 deletions crates/accelerators/src/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! 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.

mod hash;

pub use hash::{keccak256, ripemd160, sha256};

#[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,
}
Loading
Loading