Skip to content
Open
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
2 changes: 2 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 @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions crates/accelerators/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
38 changes: 38 additions & 0 deletions crates/accelerators/src/ffi/kzg.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
2 changes: 2 additions & 0 deletions crates/accelerators/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
36 changes: 36 additions & 0 deletions crates/accelerators/src/ops/kzg.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
2 changes: 2 additions & 0 deletions crates/accelerators/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
106 changes: 106 additions & 0 deletions crates/accelerators/tests/conformance/kzg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! 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::{
ffi::zkvm_kzg_point_eval,
ops::{kzg_point_eval, Error},
types::{ZkvmKzgCommitment, ZkvmKzgFieldElement, ZkvmKzgProof, ZkvmStatus},
};

/// 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);
}

#[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);
}
1 change: 1 addition & 0 deletions crates/accelerators/tests/conformance/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
mod blake2;
mod ecdsa;
mod hash;
mod kzg;
mod modexp;
Loading