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/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/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..74dba8e1f --- /dev/null +++ b/crates/accelerators/tests/conformance/kzg.rs @@ -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); +} 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;