Skip to content
Merged
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: 1 addition & 1 deletion .github/workflows/test-zkvm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ jobs:
--package ere-prover-${{ inputs.zkvm }} \
--package ere-verifier-${{ inputs.zkvm }} \
-- \
${{ inputs.skip_prove_test && '--skip prove' || '' }} \
${{ inputs.skip_prove_test && '--skip test_prove' || '' }} \
${{ inputs.test_threads && format('--test-threads={0}', inputs.test_threads) || '' }}

time chown -R $(id -u):$(id -g) /usr/local/cargo/registry
Expand Down
4 changes: 4 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 @@ -75,6 +75,7 @@ digest = { version = "0.10.7", default-features = false }
eyre = "0.6.12"
fnv = { version = "1.0.7", default-features = false }
futures-util = "0.3"
hex = "0.4"
http = "1"
indexmap = "2.10.0"
metrics = "0.24"
Expand Down
13 changes: 11 additions & 2 deletions crates/platform/openvm/src/zkvm_accelerator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,9 @@ fn accelerated_modexp_bn254_fr(base: &[u8], exp: &[u8]) -> Vec<u8> {
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()
let result = base_fr.exp_bytes(true, exp);
result.assert_reduced();
result.to_be_bytes().as_ref().to_vec()
}

#[inline]
Expand Down Expand Up @@ -562,7 +564,8 @@ fn read_bn_g2_point(input: &[u8; BN_G2_LEN]) -> Option<bn::G2Affine> {
#[inline]
fn encode_bn_g1_point(point: bn::G1Affine) -> [u8; BN_G1_LEN] {
let mut output = [0u8; BN_G1_LEN];

point.x().assert_reduced();
point.y().assert_reduced();
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 {
Expand Down Expand Up @@ -636,6 +639,8 @@ fn encode_bls_g1_point(point: &bls::G1Affine) -> [u8; BLS_G1_LEN] {
}

let mut output = [0u8; BLS_G1_LEN];
point.x().assert_reduced();
point.y().assert_reduced();
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 {
Expand All @@ -654,6 +659,10 @@ fn encode_bls_g2_point(point: &bls::G2Affine) -> [u8; BLS_G2_LEN] {
let mut output = [0u8; BLS_G2_LEN];
let x = point.x();
let y = point.y();
x.c0.assert_reduced();
x.c1.assert_reduced();
y.c0.assert_reduced();
y.c1.assert_reduced();
let x_c0 = x.c0.as_le_bytes();
let x_c1 = x.c1.as_le_bytes();
let y_c0 = y.c0.as_le_bytes();
Expand Down
14 changes: 13 additions & 1 deletion crates/prover/openvm/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ mod tests {
use ere_util_test::{
codec::BincodeLegacy,
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgram,
program::{basic::BasicProgram, zkvm_interface},
};

use crate::prover::OpenVMProver;
Expand Down Expand Up @@ -298,4 +298,16 @@ mod tests {
let test_case = BasicProgram::<BincodeLegacy>::valid_test_case();
run_zkvm_prove(&zkvm, &test_case);
}

#[test]
fn test_execute_zkvm_interface() {
let elf = OpenVMRustRv64imaCustomized
.compile(testing_guest_directory("openvm", "zkvm_interface"), &[])
.unwrap();
let zkvm = OpenVMProver::new(elf, ProverResource::Cpu).unwrap();

for test_case in zkvm_interface::test_cases() {
run_zkvm_execute(&zkvm, &test_case);
}
}
}
14 changes: 13 additions & 1 deletion crates/prover/sp1/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ mod tests {
use ere_util_test::{
codec::BincodeLegacy,
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgram,
program::{basic::BasicProgram, zkvm_interface},
};

use crate::prover::SP1Prover;
Expand Down Expand Up @@ -197,4 +197,16 @@ mod tests {
let test_case = BasicProgram::<BincodeLegacy>::valid_test_case();
run_zkvm_prove(&zkvm, &test_case);
}

#[test]
fn test_execute_zkvm_interface() {
let elf = SP1RustRv64imaCustomized
.compile(testing_guest_directory("sp1", "zkvm_interface"), &[])
.unwrap();
let zkvm = SP1Prover::new(elf, ProverResource::Cpu).unwrap();

for test_case in zkvm_interface::test_cases() {
run_zkvm_execute(&zkvm, &test_case);
}
}
}
14 changes: 13 additions & 1 deletion crates/prover/zisk/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub(crate) mod tests {
use ere_util_test::{
codec::BincodeLegacy,
host::{TestCase, run_zkvm_execute, run_zkvm_prove, testing_guest_directory},
program::basic::BasicProgram,
program::{basic::BasicProgram, zkvm_interface},
};

use crate::prover::ZiskProver;
Expand Down Expand Up @@ -166,4 +166,16 @@ pub(crate) mod tests {
let test_case = BasicProgram::<BincodeLegacy>::valid_test_case();
run_zkvm_prove(&zkvm, &test_case);
}

#[test]
fn test_execute_zkvm_interface() {
let elf = ZiskRustRv64imaCustomized
.compile(testing_guest_directory("zisk", "zkvm_interface"), &[])
.unwrap();
let zkvm = ZiskProver::new(elf, ProverResource::Cpu).unwrap();

for test_case in zkvm_interface::test_cases() {
run_zkvm_execute(&zkvm, &test_case);
}
}
}
8 changes: 7 additions & 1 deletion crates/util/test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ license.workspace = true
bincode = { workspace = true, features = ["alloc", "serde"] }
ciborium = { workspace = true }
ciborium-io = { workspace = true }
hex = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_bytes = { workspace = true, features = ["alloc"] }
serde_json = { workspace = true, optional = true }
sha2.workspace = true
strum = { workspace = true, features = ["derive"] }

# Local dependencies
ere-codec.workspace = true
ere-platform-core.workspace = true
ere-prover-core = { workspace = true, optional = true }

[target.'cfg(target_arch = "riscv64")'.dependencies]
zkvm-interface.workspace = true

[lints]
workspace = true

[features]
default = []
host = ["dep:rand", "dep:ere-prover-core"]
host = ["dep:rand", "dep:ere-prover-core", "dep:hex", "dep:serde_json"]
2 changes: 1 addition & 1 deletion crates/util/test/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use sha2::{Digest, Sha256};

use crate::program::Program;

fn workspace() -> PathBuf {
pub(crate) fn workspace() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
path.pop();
Expand Down
1 change: 1 addition & 0 deletions crates/util/test/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ere_platform_core::Platform;
use sha2::{Digest, Sha256};

pub mod basic;
pub mod zkvm_interface;

/// Program that can be run given [`Platform`] implementation.
pub trait Program {
Expand Down
184 changes: 184 additions & 0 deletions crates/util/test/src/program/zkvm_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//! Program that runs [zkVM accelerator C interface] test vectors and checks each result.
//!
//! Each vector is one crypto call recorded while an Ethereum block executed. The recording sits at
//! the `revm::precompile::Crypto` boundary, so the arguments are already parsed and length checked
//! the way the `zkvm_*` symbols take them.
//!
//! A guest calls the accelerator for every vector and checks the result in place. It commits
//! nothing, so a mismatch fails the execution and the run costs only the accelerator calls. A host
//! build calls nothing, because it only carries the vectors to the guest.
//!
//! [zkVM accelerator C interface]: https://github.com/eth-act/zkvm-standards/blob/main/standards/c-interface-accelerators/zkvm_accelerators.h

use alloc::vec::Vec;

use ere_codec::impl_codec_by_bincode_legacy;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};

use crate::program::Program;

// Only a guest defines the accelerator symbols. A host cannot stub them either, because `ziskos`
// already defines them in the ZisK host prover.
#[cfg(target_arch = "riscv64")]
mod guest;

#[cfg(feature = "host")]
mod host;

#[cfg(feature = "host")]
pub use crate::program::zkvm_interface::host::{
Fixture, FixtureVector, ZkvmInterfaceTestCase, from_hex, test_cases,
};

/// Accelerator one test vector runs, named after the `revm::precompile::Crypto` method that
/// recorded it. The string form is the stem of its fixture file.
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
Display,
EnumIter,
EnumString,
IntoStaticStr,
)]
#[strum(serialize_all = "snake_case")]
pub enum Accelerator {
Sha256,
Ripemd160,
Bn254G1Add,
Bn254G1Mul,
Bn254PairingCheck,
Secp256k1Ecrecover,
Modexp,
Blake2Compress,
Secp256r1VerifySignature,
VerifyKzgProof,
#[strum(serialize = "bls12_381_g1_add")]
Bls12381G1Add,
#[strum(serialize = "bls12_381_g1_msm")]
Bls12381G1Msm,
#[strum(serialize = "bls12_381_g2_add")]
Bls12381G2Add,
#[strum(serialize = "bls12_381_g2_msm")]
Bls12381G2Msm,
#[strum(serialize = "bls12_381_pairing_check")]
Bls12381PairingCheck,
#[strum(serialize = "bls12_381_fp_to_g1")]
Bls12381FpToG1,
#[strum(serialize = "bls12_381_fp2_to_g2")]
Bls12381Fp2ToG2,
}

impl Accelerator {
/// Every accelerator.
pub fn iter() -> impl Iterator<Item = Self> {
<Self as IntoEnumIterator>::iter()
}

/// Byte size of one pair of the packed array in `inputs[0]`. An accelerator that takes no array
/// returns `None`.
pub const fn pair_size(self) -> Option<usize> {
match self {
Self::Bn254PairingCheck => Some(192),
Self::Bls12381G1Msm => Some(128),
Self::Bls12381G2Msm => Some(224),
Self::Bls12381PairingCheck => Some(288),
_ => None,
}
}
}

/// One recorded call.
///
/// Every argument sits in `inputs`, including the by-value integers, which each take their own
/// little-endian entry. An array argument is one packed blob.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Vector {
/// Accelerator this test vector runs.
pub accelerator: Accelerator,
/// Unpadded arguments, in the order the `revm::precompile::Crypto` method takes them. The
/// by-value integers come last.
pub inputs: Vec<Vec<u8>>,
/// Outcome the reference implementation produced from the same arguments.
pub expected: Outcome,
}

impl Vector {
/// Number of pairs of the packed array argument. An accelerator that takes no array returns
/// `1`.
pub fn num_pairs(&self) -> usize {
match self.accelerator.pair_size() {
Some(pair_size) => {
assert_eq!(
self.inputs[0].len() % pair_size,
0,
"packed array is ragged"
);
self.inputs[0].len() / pair_size
}
None => 1,
}
}
}

/// Outcome of one accelerator call. A failed call writes nothing, so `output` is `None` exactly
/// when `status` is non-zero. An accelerator that returns a boolean encodes it as one byte.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Outcome {
/// `0` on success, `-1` on failure.
pub status: i32,
/// Output buffer, or `None` when the call failed.
pub output: Option<Vec<u8>>,
}

impl Outcome {
/// A failed call writes nothing, so a non-zero `status` drops `output`.
pub fn new(status: i32, output: impl Into<Vec<u8>>) -> Self {
Self {
status,
output: (status == 0).then(|| output.into()),
}
}
}

/// Vectors of one accelerator, in the order the guest runs them.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Vectors(pub Vec<Vector>);

impl_codec_by_bincode_legacy!(Vectors);

/// Runs the test vectors and checks each result against the recorded outcome.
pub struct ZkvmInterfaceProgram;

impl Program for ZkvmInterfaceProgram {
type Input = Vectors;
type Output = ();

fn compute(input: Vectors) -> Self::Output {
check(&input.0);
}
}

/// Runs every test vector and panics on the first one that does not match.
#[cfg(target_arch = "riscv64")]
fn check(vectors: &[Vector]) {
for (index, vector) in vectors.iter().enumerate() {
assert!(
guest::run(vector) == vector.expected,
"{} vector {index} does not match the reference implementation",
vector.accelerator
);
}
}

/// A host only carries the test vectors to the guest, so it runs none of them.
#[cfg(not(target_arch = "riscv64"))]
fn check(_: &[Vector]) {}
Loading