From e91978c21b70a173c799fea2f3967d76153d3241 Mon Sep 17 00:00:00 2001 From: PixelCircuits Date: Mon, 15 Sep 2025 15:41:11 -0400 Subject: [PATCH 1/3] poseidon2 prototype --- Cargo.lock | 2 + Cargo.toml | 2 + app-swap/core/Cargo.toml | 3 +- app-swap/core/src/lib.rs | 4 +- .../risczero/methods/guest/Cargo.toml | 2 +- void/void-core/Cargo.toml | 9 +- void/void-core/src/hash.rs | 5 + void/void-core/src/hash/poseidon2_r0.rs | 86 +++++++ .../poseidon2_r0/sys_poseidon2_emulated.rs | 231 ++++++++++++++++++ 9 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 void/void-core/src/hash/poseidon2_r0.rs create mode 100644 void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs diff --git a/Cargo.lock b/Cargo.lock index d873f24..a4a1baa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8768,6 +8768,8 @@ dependencies = [ "bytemuck_derive", "hex", "rayon", + "risc0-zkp", + "risc0-zkvm-platform", "serde", "sha2", "tiny-keccak", diff --git a/Cargo.toml b/Cargo.toml index 2719e1d..d434114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ futures = "0.3.31" hex = "0.4.3" rayon = "1.10.0" reqwest = "0.12.15" +risc0-zkp = "3.0.2" +risc0-zkvm-platform = "2.2.1" serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10.8" diff --git a/app-swap/core/Cargo.toml b/app-swap/core/Cargo.toml index e559ceb..ccdf2f7 100644 --- a/app-swap/core/Cargo.toml +++ b/app-swap/core/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" repository = "https://github.com/essential-contributions/void-reference-impl" [dependencies] -void-core = { path = "../../void/void-core", version = "0.1.0" } +void-core = { path = "../../void/void-core", version = "0.1.0", features = ["poseidon2-r0"] } #Testing only dependencies tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"], optional = true } @@ -25,3 +25,4 @@ serde = { version = "1", features = ["derive"], optional = true } default = [] test = ["tracing", "void-node"] query = ["anyhow", "alloy-sol-types", "alloy-primitives", "hex", "serde", "void-node"] +guest = ["void-core/poseidon2-r0-guest"] diff --git a/app-swap/core/src/lib.rs b/app-swap/core/src/lib.rs index 079704a..c3cfc84 100644 --- a/app-swap/core/src/lib.rs +++ b/app-swap/core/src/lib.rs @@ -1,5 +1,5 @@ use void_core::{ - hash::Sha256, + hash::{Poseidon2R0, Sha256}, state::kv_store::{ binary_trie_store::BinaryTrieStore, zk_binary_trie_store::{BinaryTrieWitness, ZkBinaryTrieStore}, @@ -13,7 +13,7 @@ pub mod testing; pub mod queries; pub type EventsHash = Sha256; -pub type StorageHash = Sha256; +pub type StorageHash = Poseidon2R0; pub type AppState = BinaryTrieStore; pub type ZkAppState = ZkBinaryTrieStore; diff --git a/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml b/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml index 0581796..abb36de 100644 --- a/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml +++ b/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [workspace] [dependencies] -swap-core = { path = "../../../../../app-swap/core", version = "0.1.0" } +swap-core = { path = "../../../../../app-swap/core", features=["guest"], version = "0.1.0" } void-prove = { path = "../../../../../void/void-prove", version = "0.1.0", features = ["r0"] } [patch.crates-io] diff --git a/void/void-core/Cargo.toml b/void/void-core/Cargo.toml index 683a72c..b97c220 100644 --- a/void/void-core/Cargo.toml +++ b/void/void-core/Cargo.toml @@ -13,6 +13,13 @@ bytemuck = { workspace = true, features = ["must_cast"] } bytemuck_derive.workspace = true hex.workspace = true rayon.workspace = true -serde = {workspace = true, features = ["derive"]} +risc0-zkp = { workspace = true, optional = true } +risc0-zkvm-platform = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"] } sha2 = { workspace = true, features = ["compress"] } tiny-keccak = { workspace = true, features = ["keccak"] } + +[features] +default = ["poseidon2-r0"] +poseidon2-r0 = ["risc0-zkp"] +poseidon2-r0-guest = ["risc0-zkvm-platform"] diff --git a/void/void-core/src/hash.rs b/void/void-core/src/hash.rs index eaad459..2c7c6ed 100644 --- a/void/void-core/src/hash.rs +++ b/void/void-core/src/hash.rs @@ -4,6 +4,11 @@ mod sha256; pub use keccak::*; pub use sha256::*; +#[cfg(feature = "poseidon2-r0")] +mod poseidon2_r0; +#[cfg(feature = "poseidon2-r0")] +pub use poseidon2_r0::*; + use crate::types::Bytes32; pub trait Hasher: Default { diff --git a/void/void-core/src/hash/poseidon2_r0.rs b/void/void-core/src/hash/poseidon2_r0.rs new file mode 100644 index 0000000..2ee4bac --- /dev/null +++ b/void/void-core/src/hash/poseidon2_r0.rs @@ -0,0 +1,86 @@ +use crate::types::Bytes32; +use super::Hasher; + +/// The SHA2-256 hash function. +#[derive(Copy, Clone, Debug, Default)] +pub struct Poseidon2R0; + +impl Hasher for Poseidon2R0 { + #[inline(always)] + fn hash(&self, input: &[u8]) -> Bytes32 { + sys_poseidon2::hash(&pad_data(&[input])) + } + + #[inline(always)] + fn hash_multi(&self, input: &[&[u8]]) -> Bytes32 { + sys_poseidon2::hash(&pad_data(input)) + } + + #[inline(always)] + fn compress(&self, input: [Bytes32; 2]) -> Bytes32 { + sys_poseidon2::hash(&pad_data(&[&input[0].as_ref(), &input[1].as_ref()])) + } +} + +impl Poseidon2R0 { + #[inline(always)] + pub fn hash(&self, input: &[u8]) -> Bytes32 { + ::hash(self, input) + } + #[inline(always)] + pub fn hash_multi(&self, input: &[&[u8]]) -> Bytes32 { + ::hash_multi(self, input) + } + #[inline(always)] + pub fn compress(&self, input: [Bytes32; 2]) -> Bytes32 { + ::compress(self, input) + } +} + +#[inline(always)] +fn pad_data(data: &[&[u8]]) -> Vec { + //add byte length to data and pad to the nearest 32 bytes + let len = 4 + data.iter().map(|d| d.len()).sum::(); + let block_count = (len + 31) / 32; + let mut padded_in_buf = Vec::with_capacity(block_count * 32); + padded_in_buf.extend_from_slice(&(data.len() as u32).to_le_bytes()); + for &chunk in data { + padded_in_buf.extend_from_slice(chunk); + } + padded_in_buf.resize(block_count * 32, 0); + padded_in_buf +} + +#[cfg(not(feature = "poseidon2-r0-guest"))] +mod sys_poseidon2_emulated; + +#[cfg(not(feature = "poseidon2-r0-guest"))] +mod sys_poseidon2 { + use crate::types::Bytes32; + use super::sys_poseidon2_emulated::sys_poseidon2_emulated; + use risc0_zkp::core::digest::DIGEST_WORDS; + + pub fn hash(padded_data: &[u8]) -> Bytes32 { + let state = [0u32; DIGEST_WORDS]; + let mut out_buf = [0u32; DIGEST_WORDS]; + let block_count = padded_data.len() / 32; + sys_poseidon2_emulated(&state, &padded_data, &mut out_buf, block_count as u32); + Bytes32::from(out_buf) + } +} + +#[cfg(feature = "poseidon2-r0-guest")] +mod sys_poseidon2 { + use crate::types::Bytes32; + use risc0_zkvm_platform::syscall::{sys_poseidon2, DIGEST_WORDS}; + + pub fn hash(padded_data: &[u8]) -> Bytes32 { + let mut state = [0u32; DIGEST_WORDS]; + let mut out_buf = [0u32; DIGEST_WORDS]; + let block_count = padded_data.len() / 32; + unsafe { + sys_poseidon2(&mut state, padded_data.as_ptr(), &mut out_buf, block_count as u32); + } + Bytes32::from(out_buf) + } +} diff --git a/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs b/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs new file mode 100644 index 0000000..1f0f1e0 --- /dev/null +++ b/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs @@ -0,0 +1,231 @@ +//! Emulated Poseidon2 syscall that operates on values (slices) instead of raw addresses. +//! +//! Notes: +//! - This mirrors the RV32IM circuit emulation path for Poseidon2. +//! - The chaining state (inner[16..24]) is read from `state_in` and updated internally. +//! +//! Safety: This is a safe, value-based API. It will panic on malformed inputs (e.g., not enough bytes). + +#![allow(clippy::needless_range_loop)] + +use core::convert::TryInto; + +use risc0_zkp::core::{ + digest::DIGEST_WORDS, + hash::poseidon2::{ + CELLS, M_INT_DIAG_HZN, ROUND_CONSTANTS, ROUNDS_HALF_FULL, ROUNDS_PARTIAL, + }, +}; +use risc0_zkp::field::baby_bear; + +const BABY_BEAR_P_U32: u32 = baby_bear::P; +const BABY_BEAR_P_U64: u64 = baby_bear::P as u64; + +// Flags (match circuit platform) +pub const PFLAG_IS_ELEM: u32 = 0x8000_0000; +pub const PFLAG_CHECK_OUT: u32 = 0x4000_0000; + +/// Emulate the Poseidon2 syscall. +/// +/// Parameters: +/// - state_in: initial chaining state (8 words). If you want "no state", pass an array of zeros. +/// - in_buf: input bytes. Interpreted as: +/// - If PFLAG_IS_ELEM is set: a sequence of 16 u32 words per block (64 bytes). +/// - Otherwise: a sequence of 8 u32 words per block (32 bytes), split into 16 field elems via 16-bit halves. +/// - out_buf: output digest (8 words). If PFLAG_CHECK_OUT is set, this function will compare and panic if mismatch. +/// - bits_count: flags and count in the low 16 bits. Upper bits may include PFLAG_IS_ELEM and PFLAG_CHECK_OUT. +/// +/// Panics: +/// - If in_buf is not large enough for the declared count. +/// - If PFLAG_CHECK_OUT is set and out_buf does not match the computed digest. +pub fn sys_poseidon2_emulated( + state_in: &[u32; DIGEST_WORDS], + in_buf: &[u8], + out_buf: &mut [u32; DIGEST_WORDS], + bits_count: u32, +) { + let is_elem = (bits_count & PFLAG_IS_ELEM) != 0; + let check_out = (bits_count & PFLAG_CHECK_OUT) != 0; + let count = (bits_count & 0xffff) as usize; + + // Determine per-block size + let words_per_block = if is_elem { 2 * DIGEST_WORDS } else { DIGEST_WORDS }; + let bytes_per_block = words_per_block * core::mem::size_of::(); + let needed_bytes = count + .checked_mul(bytes_per_block) + .expect("input size overflow"); + assert!( + in_buf.len() >= needed_bytes, + "insufficient input: have {}, need {}", + in_buf.len(), + needed_bytes + ); + + // Convert the input bytes to u32 words (little-endian) + // We avoid copying the entire slice by decoding per word when loading. + let mut inner = [0u32; CELLS]; + + // Load chaining state into inner[16..24] + for i in 0..DIGEST_WORDS { + inner[DIGEST_WORDS * 2 + i] = state_in[i]; + } + + // Process each block + for blk in 0..count { + let base = blk * bytes_per_block; + + if is_elem { + // Load 16 u32 words as field elements + // First 8 words -> inner[0..8] + for i in 0..DIGEST_WORDS { + inner[i] = read_u32_le(&in_buf[base + i * 4..base + (i + 1) * 4]); + } + // Next 8 words -> inner[8..16] + let off = base + DIGEST_WORDS * 4; + for i in 0..DIGEST_WORDS { + inner[DIGEST_WORDS + i] = read_u32_le(&in_buf[off + i * 4..off + (i + 1) * 4]); + } + } else { + // Load 8 u32 words, split into 16 elems (lo/hi 16-bit) + for i in 0..DIGEST_WORDS { + let word = read_u32_le(&in_buf[base + i * 4..base + (i + 1) * 4]); + inner[2 * i] = word & 0xffff; + inner[2 * i + 1] = word >> 16; + } + } + + // Do the mix: M_EXT, half-full rounds, partial rounds (M_INT), final half-full rounds + multiply_by_m_ext(&mut inner); + for i in 0..ROUNDS_HALF_FULL { + do_ext_round(&mut inner, i); + } + do_int_rounds(&mut inner); + for i in ROUNDS_HALF_FULL..ROUNDS_HALF_FULL * 2 { + do_ext_round(&mut inner, i); + } + } + + // Output: either check against provided out_buf or write into it + if check_out { + for i in 0..DIGEST_WORDS { + if out_buf[i] != inner[i] { + panic!( + "poseidon2 check failed: {:#010x} != {:#010x} at word {}", + out_buf[i], inner[i], i + ); + } + } + } else { + for i in 0..DIGEST_WORDS { + out_buf[i] = inner[i]; + } + } +} + +#[inline(always)] +fn read_u32_le(bytes4: &[u8]) -> u32 { + let arr: [u8; 4] = bytes4.try_into().expect("read_u32_le needs 4 bytes"); + u32::from_le_bytes(arr) +} + +// --------- Helpers that mirror the circuit emulation ---------- + +#[inline(always)] +fn sbox2(x: u32) -> u32 { + let x = x as u64; + let x2 = (x * x) % BABY_BEAR_P_U64; + let x4 = (x2 * x2) % BABY_BEAR_P_U64; + let x6 = (x4 * x2) % BABY_BEAR_P_U64; + let x7 = (x6 * x) % BABY_BEAR_P_U64; + x7 as u32 +} + +#[inline(always)] +fn multiply_by_4x4_circulant(x: &[u32; 4]) -> [u32; 4] { + // See appendix B of the Poseidon2 paper. + const CIRC_FACTOR_2: u64 = 2; + const CIRC_FACTOR_4: u64 = 4; + let t0 = (x[0] as u64 + x[1] as u64) % BABY_BEAR_P_U64; + let t1 = (x[2] as u64 + x[3] as u64) % BABY_BEAR_P_U64; + let t2 = (CIRC_FACTOR_2 * x[1] as u64 + t1) % BABY_BEAR_P_U64; + let t3 = (CIRC_FACTOR_2 * x[3] as u64 + t0) % BABY_BEAR_P_U64; + let t4 = (CIRC_FACTOR_4 * t1 + t3) % BABY_BEAR_P_U64; + let t5 = (CIRC_FACTOR_4 * t0 + t2) % BABY_BEAR_P_U64; + let t6 = (t3 + t5) % BABY_BEAR_P_U64; + let t7 = (t2 + t4) % BABY_BEAR_P_U64; + [t6 as u32, t5 as u32, t7 as u32, t4 as u32] +} + +// Optimized method for multiplication by M_EXT. +#[inline(always)] +fn multiply_by_m_ext(inner: &mut [u32; CELLS]) { + let mut out = [0u32; CELLS]; + let mut tmp_sums = [0u32; 4]; + + for i in 0..(CELLS / 4) { + let chunk = multiply_by_4x4_circulant(&[ + inner[i * 4], + inner[i * 4 + 1], + inner[i * 4 + 2], + inner[i * 4 + 3], + ]); + for j in 0..4 { + let to_add = (chunk[j] as u64 % BABY_BEAR_P_U64) as u32; + tmp_sums[j] = (tmp_sums[j] + to_add) % BABY_BEAR_P_U32; + out[i * 4 + j] = (out[i * 4 + j] + to_add) % BABY_BEAR_P_U32; + } + } + for i in 0..CELLS { + inner[i] = (out[i] + tmp_sums[i % 4]) % BABY_BEAR_P_U32; + } +} + +// Exploit the fact that off-diagonal entries of M_INT are all 1. +#[inline(always)] +fn multiply_by_m_int(inner: &mut [u32; CELLS]) { + let mut sum = 0u64; + for i in 0..CELLS { + sum += inner[i] as u64; + } + sum %= BABY_BEAR_P_U64; + for (i, diag) in M_INT_DIAG_HZN.iter().enumerate().take(CELLS) { + let diag = diag.as_u32() as u64; + let cell = inner[i] as u64; + inner[i] = ((sum + diag * cell) % BABY_BEAR_P_U64) as u32; + } +} + +#[inline(always)] +fn add_round_constants_full(inner: &mut [u32; CELLS], round: usize) { + for i in 0..CELLS { + inner[i] += ROUND_CONSTANTS[round * CELLS + i].as_u32(); + inner[i] %= BABY_BEAR_P_U32; + } +} + +#[inline(always)] +fn add_round_constants_partial(inner: &mut [u32; CELLS], round: usize) { + inner[0] += ROUND_CONSTANTS[round * CELLS].as_u32(); + inner[0] %= BABY_BEAR_P_U32; +} + +#[inline(always)] +fn do_ext_round(inner: &mut [u32; CELLS], mut idx: usize) { + if idx >= ROUNDS_HALF_FULL { + idx += ROUNDS_PARTIAL; + } + add_round_constants_full(inner, idx); + for i in 0..CELLS { + inner[i] = sbox2(inner[i]); + } + multiply_by_m_ext(inner); +} + +#[inline(always)] +fn do_int_rounds(inner: &mut [u32; CELLS]) { + for i in 0..ROUNDS_PARTIAL { + add_round_constants_partial(inner, ROUNDS_HALF_FULL + i); + inner[0] = sbox2(inner[0]); + multiply_by_m_int(inner); + } +} \ No newline at end of file From 9fc8b6a47f1233ced689c5c794d94958f5e047cc Mon Sep 17 00:00:00 2001 From: PixelCircuits Date: Thu, 25 Sep 2025 12:46:27 -0400 Subject: [PATCH 2/3] fix risc0 bug --- Cargo.lock | 97 +++--- app-swap/core/Cargo.toml | 1 - app-swap/core/src/lib.rs | 4 +- .../zk-proving/risczero/methods/Cargo.toml | 2 +- .../risczero/methods/guest/Cargo.toml | 2 +- void/void-core/Cargo.toml | 9 +- void/void-core/src/hash/poseidon2_r0.rs | 289 +++++++++++++++--- .../poseidon2_r0/sys_poseidon2_emulated.rs | 231 -------------- void/void-node/Cargo.toml | 2 +- void/void-prove/Cargo.toml | 2 +- 10 files changed, 302 insertions(+), 337 deletions(-) delete mode 100644 void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs diff --git a/Cargo.lock b/Cargo.lock index a4a1baa..d021d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2069,9 +2069,8 @@ dependencies = [ [[package]] name = "bonsai-sdk" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21055e2f49cbbdbfe9f8f96d597c5527b0c6ab7933341fdc2f147180e48a988e" +version = "1.4.2" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "duplicate", "maybe-async", @@ -6272,9 +6271,8 @@ checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" [[package]] name = "risc0-binfmt" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8f97f81bcdead4101bca06469ecef481a2695cd04e7e877b49dea56a7f6f2a" +version = "3.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "borsh", @@ -6294,9 +6292,8 @@ dependencies = [ [[package]] name = "risc0-build" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bbb512d728e011d03ce0958ca7954624ee13a215bcafd859623b3c63b2a3f60" +version = "4.0.0" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "cargo_metadata 0.19.2", @@ -6304,6 +6301,7 @@ dependencies = [ "dirs 6.0.0", "docker-generate", "hex", + "rayon", "risc0-binfmt", "risc0-zkos-v1compat", "risc0-zkp", @@ -6318,9 +6316,8 @@ dependencies = [ [[package]] name = "risc0-build-kernel" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaaa3e04c71e4244354dd9e3f8b89378cfecfbb03f9c72de4e2e7e0482b30c9a" +version = "2.0.2" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "cc", "directories", @@ -6332,9 +6329,8 @@ dependencies = [ [[package]] name = "risc0-circuit-keccak" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f195f865ac1afdc21a172d7756fdcc21be18e13eb01d78d3d7f2b128fa881ba" +version = "4.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "bytemuck", @@ -6354,9 +6350,8 @@ dependencies = [ [[package]] name = "risc0-circuit-keccak-sys" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a8f21cc053fe9892acebbe0ebe2610a5d79ad638cd17f2e5122cf0b3e7cd1a" +version = "4.0.2" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "cc", "cust", @@ -6370,9 +6365,8 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca8f15c8abc0fd8c097aa7459879110334d191c63dd51d4c28881c4a497279e" +version = "4.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "bytemuck", @@ -6396,9 +6390,8 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion-sys" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f137bcd382520efcd982e4ee131da43f448b12ade979fe9d1fa92d4337dec0" +version = "4.0.2" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "glob", "risc0-build-kernel", @@ -6409,9 +6402,8 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1b0689f4a270a2f247b04397ebb431b8f64fe5170e98ee4f9d71bd04825205" +version = "4.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "bit-vec", @@ -6443,9 +6435,8 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im-sys" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb25f3935e53e89ca020224ad0c09de96cab89a215054c0cee290405074a5166" +version = "4.0.2" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "cc", "cust", @@ -6459,9 +6450,8 @@ dependencies = [ [[package]] name = "risc0-core" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f2723fedace48c6c5a505bd8f97ac4e1712bc4cb769083e10536d862b66987" +version = "3.0.1" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "bytemuck", "nvtx", @@ -6471,9 +6461,8 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724285dc79604abfb2d40feaefe3e335420a6b293511661f77d6af62f1f5fae9" +version = "3.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "ark-bn254", @@ -6485,6 +6474,7 @@ dependencies = [ "cfg-if", "circom-witnesscalc", "hex", + "memmap2", "num-bigint 0.4.6", "num-traits", "risc0-binfmt", @@ -6501,9 +6491,8 @@ dependencies = [ [[package]] name = "risc0-groth16-sys" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580750e1394c5a79b691c8197906263f17a1d3aa7bb46a934362f1ba1475b1a5" +version = "0.2.0" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "blst", @@ -6513,9 +6502,8 @@ dependencies = [ [[package]] name = "risc0-sys" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960c8295fbb87e1e73e332f8f7de2fba0252377575042d9d3e9a4eb50a38e078" +version = "1.5.1" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "blst", @@ -6526,9 +6514,8 @@ dependencies = [ [[package]] name = "risc0-zkos-v1compat" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840c2228803557a8b7dc035a8f196516b6fd68c9dc6ac092f0c86241b5b1bafb" +version = "2.2.1" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "include_bytes_aligned", "no_std_strings", @@ -6537,9 +6524,8 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb6bf356f469bb8744f72a07a37134c5812c1d55d6271bba80e87bdb7a58c8e" +version = "3.0.3" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "anyhow", "blake2", @@ -6569,9 +6555,8 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcce11648a9ff60b8e7af2f0ce7fbf8d25275ab6d414cc91b9da69ee75bc978" +version = "4.0.0" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "addr2line", "anyhow", @@ -6619,9 +6604,8 @@ dependencies = [ [[package]] name = "risc0-zkvm-platform" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfaa10feba15828c788837ddde84b994393936d8f5715228627cfe8625122a40" +version = "2.3.0" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "bytemuck", "cfg-if", @@ -6923,9 +6907,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "rzup" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2aed296f203fa64bcb4b52069356dd86d6ec578593985b919b6995bee1f0ae" +version = "0.6.0" +source = "git+https://github.com/risc0/risc0.git?rev=e03b8f0#e03b8f086e3c0ca64e130788d493a00892b4aabe" dependencies = [ "hex", "rsa", diff --git a/app-swap/core/Cargo.toml b/app-swap/core/Cargo.toml index ccdf2f7..e977a1d 100644 --- a/app-swap/core/Cargo.toml +++ b/app-swap/core/Cargo.toml @@ -25,4 +25,3 @@ serde = { version = "1", features = ["derive"], optional = true } default = [] test = ["tracing", "void-node"] query = ["anyhow", "alloy-sol-types", "alloy-primitives", "hex", "serde", "void-node"] -guest = ["void-core/poseidon2-r0-guest"] diff --git a/app-swap/core/src/lib.rs b/app-swap/core/src/lib.rs index c3cfc84..f63c96d 100644 --- a/app-swap/core/src/lib.rs +++ b/app-swap/core/src/lib.rs @@ -1,5 +1,5 @@ use void_core::{ - hash::{Poseidon2R0, Sha256}, + hash::{Poseidon2, Sha256}, state::kv_store::{ binary_trie_store::BinaryTrieStore, zk_binary_trie_store::{BinaryTrieWitness, ZkBinaryTrieStore}, @@ -13,7 +13,7 @@ pub mod testing; pub mod queries; pub type EventsHash = Sha256; -pub type StorageHash = Poseidon2R0; +pub type StorageHash = Poseidon2; pub type AppState = BinaryTrieStore; pub type ZkAppState = ZkBinaryTrieStore; diff --git a/benchmarks/zk-proving/risczero/methods/Cargo.toml b/benchmarks/zk-proving/risczero/methods/Cargo.toml index 0704499..381a13f 100644 --- a/benchmarks/zk-proving/risczero/methods/Cargo.toml +++ b/benchmarks/zk-proving/risczero/methods/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [build-dependencies] -risc0-build = { version = "^3.0.3" } +risc0-build = { git = "https://github.com/risc0/risc0.git", rev = "e03b8f0" } [package.metadata.risc0] methods = ["guest"] diff --git a/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml b/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml index abb36de..0581796 100644 --- a/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml +++ b/benchmarks/zk-proving/risczero/methods/guest/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [workspace] [dependencies] -swap-core = { path = "../../../../../app-swap/core", features=["guest"], version = "0.1.0" } +swap-core = { path = "../../../../../app-swap/core", version = "0.1.0" } void-prove = { path = "../../../../../void/void-prove", version = "0.1.0", features = ["r0"] } [patch.crates-io] diff --git a/void/void-core/Cargo.toml b/void/void-core/Cargo.toml index b97c220..9d38cb5 100644 --- a/void/void-core/Cargo.toml +++ b/void/void-core/Cargo.toml @@ -13,13 +13,12 @@ bytemuck = { workspace = true, features = ["must_cast"] } bytemuck_derive.workspace = true hex.workspace = true rayon.workspace = true -risc0-zkp = { workspace = true, optional = true } -risc0-zkvm-platform = { workspace = true, optional = true } +risc0-zkp = { git = "https://github.com/risc0/risc0.git", rev = "e03b8f0", optional = true } +risc0-zkvm-platform = { git = "https://github.com/risc0/risc0.git", rev = "e03b8f0", optional = true } serde = { workspace = true, features = ["derive"] } sha2 = { workspace = true, features = ["compress"] } tiny-keccak = { workspace = true, features = ["keccak"] } [features] -default = ["poseidon2-r0"] -poseidon2-r0 = ["risc0-zkp"] -poseidon2-r0-guest = ["risc0-zkvm-platform"] +default = [] +poseidon2-r0 = ["risc0-zkp", "risc0-zkvm-platform"] diff --git a/void/void-core/src/hash/poseidon2_r0.rs b/void/void-core/src/hash/poseidon2_r0.rs index 2ee4bac..ae16b64 100644 --- a/void/void-core/src/hash/poseidon2_r0.rs +++ b/void/void-core/src/hash/poseidon2_r0.rs @@ -1,28 +1,28 @@ use crate::types::Bytes32; use super::Hasher; -/// The SHA2-256 hash function. +/// A wrapper around the Poseidon2 hash function used by RISC0 in their zkVM. #[derive(Copy, Clone, Debug, Default)] -pub struct Poseidon2R0; +pub struct Poseidon2; -impl Hasher for Poseidon2R0 { +impl Hasher for Poseidon2 { #[inline(always)] fn hash(&self, input: &[u8]) -> Bytes32 { - sys_poseidon2::hash(&pad_data(&[input])) + sys_poseidon2::hash(&pad_data(&[input])).into() } #[inline(always)] fn hash_multi(&self, input: &[&[u8]]) -> Bytes32 { - sys_poseidon2::hash(&pad_data(input)) + sys_poseidon2::hash(&pad_data(input)).into() } #[inline(always)] fn compress(&self, input: [Bytes32; 2]) -> Bytes32 { - sys_poseidon2::hash(&pad_data(&[&input[0].as_ref(), &input[1].as_ref()])) + sys_poseidon2::hash(&pad_data(&[&input[0].as_ref(), &input[1].as_ref()])).into() } } -impl Poseidon2R0 { +impl Poseidon2 { #[inline(always)] pub fn hash(&self, input: &[u8]) -> Bytes32 { ::hash(self, input) @@ -38,49 +38,264 @@ impl Poseidon2R0 { } #[inline(always)] -fn pad_data(data: &[&[u8]]) -> Vec { - //add byte length to data and pad to the nearest 32 bytes - let len = 4 + data.iter().map(|d| d.len()).sum::(); - let block_count = (len + 31) / 32; - let mut padded_in_buf = Vec::with_capacity(block_count * 32); - padded_in_buf.extend_from_slice(&(data.len() as u32).to_le_bytes()); +fn pad_data(data: &[&[u8]]) -> Vec { + let len_bytes = 4 + data.iter().map(|d| d.len()).sum::(); + + // round up to 32-byte blocks (8 u32 per block) + let blocks = (len_bytes + 31) / 32; + let mut out = vec![0u32; blocks * 8]; + let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut out); + + // copy data len + data + bytes[..4].copy_from_slice(&(data.len() as u32).to_be_bytes()); + let mut off = 4; for &chunk in data { - padded_in_buf.extend_from_slice(chunk); + let end = off + chunk.len(); + bytes[off..end].copy_from_slice(chunk); + off = end; } - padded_in_buf.resize(block_count * 32, 0); - padded_in_buf -} -#[cfg(not(feature = "poseidon2-r0-guest"))] -mod sys_poseidon2_emulated; + out +} -#[cfg(not(feature = "poseidon2-r0-guest"))] +#[cfg(all(target_os = "zkvm", target_arch = "riscv32"))] mod sys_poseidon2 { - use crate::types::Bytes32; - use super::sys_poseidon2_emulated::sys_poseidon2_emulated; - use risc0_zkp::core::digest::DIGEST_WORDS; + use risc0_zkvm_platform::syscall::{sys_poseidon2, DIGEST_WORDS}; - pub fn hash(padded_data: &[u8]) -> Bytes32 { - let state = [0u32; DIGEST_WORDS]; + pub fn hash(padded_data: &[u32]) -> [u32; 8] { + let mut state = [0u32; DIGEST_WORDS]; let mut out_buf = [0u32; DIGEST_WORDS]; - let block_count = padded_data.len() / 32; - sys_poseidon2_emulated(&state, &padded_data, &mut out_buf, block_count as u32); - Bytes32::from(out_buf) + let block_count = padded_data.len() / 8; + unsafe { + sys_poseidon2(&mut state, padded_data.as_ptr() as *const u8, &mut out_buf, block_count as u32); + } + out_buf } } -#[cfg(feature = "poseidon2-r0-guest")] +#[cfg(not(all(target_os = "zkvm", target_arch = "riscv32")))] mod sys_poseidon2 { - use crate::types::Bytes32; - use risc0_zkvm_platform::syscall::{sys_poseidon2, DIGEST_WORDS}; + #![allow(clippy::needless_range_loop)] - pub fn hash(padded_data: &[u8]) -> Bytes32 { - let mut state = [0u32; DIGEST_WORDS]; + use risc0_zkp::core::digest::DIGEST_WORDS; + + pub fn hash(padded_data: &[u32]) -> [u32; 8] { + let state: [u32; 8] = [0u32; DIGEST_WORDS]; let mut out_buf = [0u32; DIGEST_WORDS]; - let block_count = padded_data.len() / 32; - unsafe { - sys_poseidon2(&mut state, padded_data.as_ptr(), &mut out_buf, block_count as u32); + let block_count = padded_data.len() / 8; + sys_poseidon2_emulated(&state, &padded_data, &mut out_buf, block_count as u32); + out_buf + } + + // --------- Code for emulating Poseidon2 syscall ---------- + use risc0_zkp::core::{ + hash::poseidon2::{ + CELLS, M_INT_DIAG_HZN, ROUND_CONSTANTS, ROUNDS_HALF_FULL, ROUNDS_PARTIAL, + }, + }; + use risc0_zkp::field::baby_bear; + + const BABY_BEAR_P_U32: u32 = baby_bear::P; + const BABY_BEAR_P_U64: u64 = baby_bear::P as u64; + + // Flags + pub const PFLAG_IS_ELEM: u32 = 0x8000_0000; + pub const PFLAG_CHECK_OUT: u32 = 0x4000_0000; + + /// Emulated Poseidon2 syscall that operates on values (slices) instead of raw addresses. + /// + /// Notes: + /// - This mirrors the RV32IM circuit emulation path for Poseidon2. + /// - The chaining state (inner[16..24]) is read from `state_in` and updated internally. + /// + /// Parameters: + /// - state_in: initial chaining state (8 words). If you want "no state", pass an array of zeros. + /// - in_buf: input words (u32). Interpreted as blocks: + /// - If PFLAG_IS_ELEM is set: 16 u32 words per block (each word is a field element candidate). + /// - Otherwise: 8 u32 words per block, each split into 2 field elements via its low/high 16-bit halves. + /// - out_buf: output digest (8 words). If PFLAG_CHECK_OUT is set, this function will compare and panic if mismatch. + /// - bits_count: flags and count in the low 16 bits. Upper bits may include PFLAG_IS_ELEM and PFLAG_CHECK_OUT. + /// + /// Panics: + /// - If in_buf is not large enough for the declared count. + /// - If PFLAG_CHECK_OUT is set and out_buf does not match the computed digest. + pub fn sys_poseidon2_emulated( + state_in: &[u32; DIGEST_WORDS], + in_buf: &[u32], + out_buf: &mut [u32; DIGEST_WORDS], + bits_count: u32, + ) { + let is_elem = (bits_count & PFLAG_IS_ELEM) != 0; + let check_out = (bits_count & PFLAG_CHECK_OUT) != 0; + let count = (bits_count & 0xffff) as usize; + + // Determine per-block size (in words) + let words_per_block = if is_elem { 2 * DIGEST_WORDS } else { DIGEST_WORDS }; + let needed_words = count + .checked_mul(words_per_block) + .expect("input size overflow"); + assert!( + in_buf.len() >= needed_words, + "insufficient input: have {}, need {} words", + in_buf.len(), + needed_words + ); + + let mut inner = [0u32; CELLS]; + + // Load chaining state into inner[16..24] + for i in 0..DIGEST_WORDS { + inner[DIGEST_WORDS * 2 + i] = state_in[i]; + } + + // Process each block + for blk in 0..count { + let base = blk * words_per_block; + + if is_elem { + // Load 16 u32 words directly as field elements + for i in 0..DIGEST_WORDS { + inner[i] = in_buf[base + i]; + } + for i in 0..DIGEST_WORDS { + inner[DIGEST_WORDS + i] = in_buf[base + DIGEST_WORDS + i]; + } + } else { + // Load 8 u32 words, split each into two 16-bit elements + for i in 0..DIGEST_WORDS { + let word = in_buf[base + i]; + inner[2 * i] = word & 0xffff; + inner[2 * i + 1] = word >> 16; + } + } + + // Do the mix: M_EXT, half-full rounds, partial rounds (M_INT), final half-full rounds + multiply_by_m_ext(&mut inner); + for i in 0..ROUNDS_HALF_FULL { + do_ext_round(&mut inner, i); + } + do_int_rounds(&mut inner); + for i in ROUNDS_HALF_FULL..ROUNDS_HALF_FULL * 2 { + do_ext_round(&mut inner, i); + } + } + + // Output: either check against provided out_buf or write into it + if check_out { + for i in 0..DIGEST_WORDS { + if out_buf[i] != inner[i] { + panic!( + "poseidon2 check failed: {:#010x} != {:#010x} at word {}", + out_buf[i], inner[i], i + ); + } + } + } else { + for i in 0..DIGEST_WORDS { + out_buf[i] = inner[i]; + } + } + } + + // --------- Helpers that mirror the circuit emulation ---------- + + #[inline(always)] + fn sbox2(x: u32) -> u32 { + let x = x as u64; + let x2 = (x * x) % BABY_BEAR_P_U64; + let x4 = (x2 * x2) % BABY_BEAR_P_U64; + let x6 = (x4 * x2) % BABY_BEAR_P_U64; + let x7 = (x6 * x) % BABY_BEAR_P_U64; + x7 as u32 + } + + #[inline(always)] + fn multiply_by_4x4_circulant(x: &[u32; 4]) -> [u32; 4] { + // See appendix B of the Poseidon2 paper. + const CIRC_FACTOR_2: u64 = 2; + const CIRC_FACTOR_4: u64 = 4; + let t0 = (x[0] as u64 + x[1] as u64) % BABY_BEAR_P_U64; + let t1 = (x[2] as u64 + x[3] as u64) % BABY_BEAR_P_U64; + let t2 = (CIRC_FACTOR_2 * x[1] as u64 + t1) % BABY_BEAR_P_U64; + let t3 = (CIRC_FACTOR_2 * x[3] as u64 + t0) % BABY_BEAR_P_U64; + let t4 = (CIRC_FACTOR_4 * t1 + t3) % BABY_BEAR_P_U64; + let t5 = (CIRC_FACTOR_4 * t0 + t2) % BABY_BEAR_P_U64; + let t6 = (t3 + t5) % BABY_BEAR_P_U64; + let t7 = (t2 + t4) % BABY_BEAR_P_U64; + [t6 as u32, t5 as u32, t7 as u32, t4 as u32] + } + + // Optimized method for multiplication by M_EXT. + #[inline(always)] + fn multiply_by_m_ext(inner: &mut [u32; CELLS]) { + let mut out = [0u32; CELLS]; + let mut tmp_sums = [0u32; 4]; + + for i in 0..(CELLS / 4) { + let chunk = multiply_by_4x4_circulant(&[ + inner[i * 4], + inner[i * 4 + 1], + inner[i * 4 + 2], + inner[i * 4 + 3], + ]); + for j in 0..4 { + let to_add = (chunk[j] as u64 % BABY_BEAR_P_U64) as u32; + tmp_sums[j] = (tmp_sums[j] + to_add) % BABY_BEAR_P_U32; + out[i * 4 + j] = (out[i * 4 + j] + to_add) % BABY_BEAR_P_U32; + } + } + for i in 0..CELLS { + inner[i] = (out[i] + tmp_sums[i % 4]) % BABY_BEAR_P_U32; + } + } + + // Exploit the fact that off-diagonal entries of M_INT are all 1. + #[inline(always)] + fn multiply_by_m_int(inner: &mut [u32; CELLS]) { + let mut sum = 0u64; + for i in 0..CELLS { + sum += inner[i] as u64; + } + sum %= BABY_BEAR_P_U64; + for (i, diag) in M_INT_DIAG_HZN.iter().enumerate().take(CELLS) { + let diag = diag.as_u32() as u64; + let cell = inner[i] as u64; + inner[i] = ((sum + diag * cell) % BABY_BEAR_P_U64) as u32; + } + } + + #[inline(always)] + fn add_round_constants_full(inner: &mut [u32; CELLS], round: usize) { + for i in 0..CELLS { + inner[i] += ROUND_CONSTANTS[round * CELLS + i].as_u32(); + inner[i] %= BABY_BEAR_P_U32; + } + } + + #[inline(always)] + fn add_round_constants_partial(inner: &mut [u32; CELLS], round: usize) { + inner[0] += ROUND_CONSTANTS[round * CELLS].as_u32(); + inner[0] %= BABY_BEAR_P_U32; + } + + #[inline(always)] + fn do_ext_round(inner: &mut [u32; CELLS], mut idx: usize) { + if idx >= ROUNDS_HALF_FULL { + idx += ROUNDS_PARTIAL; + } + add_round_constants_full(inner, idx); + for i in 0..CELLS { + inner[i] = sbox2(inner[i]); + } + multiply_by_m_ext(inner); + } + + #[inline(always)] + fn do_int_rounds(inner: &mut [u32; CELLS]) { + for i in 0..ROUNDS_PARTIAL { + add_round_constants_partial(inner, ROUNDS_HALF_FULL + i); + inner[0] = sbox2(inner[0]); + multiply_by_m_int(inner); } - Bytes32::from(out_buf) } } diff --git a/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs b/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs deleted file mode 100644 index 1f0f1e0..0000000 --- a/void/void-core/src/hash/poseidon2_r0/sys_poseidon2_emulated.rs +++ /dev/null @@ -1,231 +0,0 @@ -//! Emulated Poseidon2 syscall that operates on values (slices) instead of raw addresses. -//! -//! Notes: -//! - This mirrors the RV32IM circuit emulation path for Poseidon2. -//! - The chaining state (inner[16..24]) is read from `state_in` and updated internally. -//! -//! Safety: This is a safe, value-based API. It will panic on malformed inputs (e.g., not enough bytes). - -#![allow(clippy::needless_range_loop)] - -use core::convert::TryInto; - -use risc0_zkp::core::{ - digest::DIGEST_WORDS, - hash::poseidon2::{ - CELLS, M_INT_DIAG_HZN, ROUND_CONSTANTS, ROUNDS_HALF_FULL, ROUNDS_PARTIAL, - }, -}; -use risc0_zkp::field::baby_bear; - -const BABY_BEAR_P_U32: u32 = baby_bear::P; -const BABY_BEAR_P_U64: u64 = baby_bear::P as u64; - -// Flags (match circuit platform) -pub const PFLAG_IS_ELEM: u32 = 0x8000_0000; -pub const PFLAG_CHECK_OUT: u32 = 0x4000_0000; - -/// Emulate the Poseidon2 syscall. -/// -/// Parameters: -/// - state_in: initial chaining state (8 words). If you want "no state", pass an array of zeros. -/// - in_buf: input bytes. Interpreted as: -/// - If PFLAG_IS_ELEM is set: a sequence of 16 u32 words per block (64 bytes). -/// - Otherwise: a sequence of 8 u32 words per block (32 bytes), split into 16 field elems via 16-bit halves. -/// - out_buf: output digest (8 words). If PFLAG_CHECK_OUT is set, this function will compare and panic if mismatch. -/// - bits_count: flags and count in the low 16 bits. Upper bits may include PFLAG_IS_ELEM and PFLAG_CHECK_OUT. -/// -/// Panics: -/// - If in_buf is not large enough for the declared count. -/// - If PFLAG_CHECK_OUT is set and out_buf does not match the computed digest. -pub fn sys_poseidon2_emulated( - state_in: &[u32; DIGEST_WORDS], - in_buf: &[u8], - out_buf: &mut [u32; DIGEST_WORDS], - bits_count: u32, -) { - let is_elem = (bits_count & PFLAG_IS_ELEM) != 0; - let check_out = (bits_count & PFLAG_CHECK_OUT) != 0; - let count = (bits_count & 0xffff) as usize; - - // Determine per-block size - let words_per_block = if is_elem { 2 * DIGEST_WORDS } else { DIGEST_WORDS }; - let bytes_per_block = words_per_block * core::mem::size_of::(); - let needed_bytes = count - .checked_mul(bytes_per_block) - .expect("input size overflow"); - assert!( - in_buf.len() >= needed_bytes, - "insufficient input: have {}, need {}", - in_buf.len(), - needed_bytes - ); - - // Convert the input bytes to u32 words (little-endian) - // We avoid copying the entire slice by decoding per word when loading. - let mut inner = [0u32; CELLS]; - - // Load chaining state into inner[16..24] - for i in 0..DIGEST_WORDS { - inner[DIGEST_WORDS * 2 + i] = state_in[i]; - } - - // Process each block - for blk in 0..count { - let base = blk * bytes_per_block; - - if is_elem { - // Load 16 u32 words as field elements - // First 8 words -> inner[0..8] - for i in 0..DIGEST_WORDS { - inner[i] = read_u32_le(&in_buf[base + i * 4..base + (i + 1) * 4]); - } - // Next 8 words -> inner[8..16] - let off = base + DIGEST_WORDS * 4; - for i in 0..DIGEST_WORDS { - inner[DIGEST_WORDS + i] = read_u32_le(&in_buf[off + i * 4..off + (i + 1) * 4]); - } - } else { - // Load 8 u32 words, split into 16 elems (lo/hi 16-bit) - for i in 0..DIGEST_WORDS { - let word = read_u32_le(&in_buf[base + i * 4..base + (i + 1) * 4]); - inner[2 * i] = word & 0xffff; - inner[2 * i + 1] = word >> 16; - } - } - - // Do the mix: M_EXT, half-full rounds, partial rounds (M_INT), final half-full rounds - multiply_by_m_ext(&mut inner); - for i in 0..ROUNDS_HALF_FULL { - do_ext_round(&mut inner, i); - } - do_int_rounds(&mut inner); - for i in ROUNDS_HALF_FULL..ROUNDS_HALF_FULL * 2 { - do_ext_round(&mut inner, i); - } - } - - // Output: either check against provided out_buf or write into it - if check_out { - for i in 0..DIGEST_WORDS { - if out_buf[i] != inner[i] { - panic!( - "poseidon2 check failed: {:#010x} != {:#010x} at word {}", - out_buf[i], inner[i], i - ); - } - } - } else { - for i in 0..DIGEST_WORDS { - out_buf[i] = inner[i]; - } - } -} - -#[inline(always)] -fn read_u32_le(bytes4: &[u8]) -> u32 { - let arr: [u8; 4] = bytes4.try_into().expect("read_u32_le needs 4 bytes"); - u32::from_le_bytes(arr) -} - -// --------- Helpers that mirror the circuit emulation ---------- - -#[inline(always)] -fn sbox2(x: u32) -> u32 { - let x = x as u64; - let x2 = (x * x) % BABY_BEAR_P_U64; - let x4 = (x2 * x2) % BABY_BEAR_P_U64; - let x6 = (x4 * x2) % BABY_BEAR_P_U64; - let x7 = (x6 * x) % BABY_BEAR_P_U64; - x7 as u32 -} - -#[inline(always)] -fn multiply_by_4x4_circulant(x: &[u32; 4]) -> [u32; 4] { - // See appendix B of the Poseidon2 paper. - const CIRC_FACTOR_2: u64 = 2; - const CIRC_FACTOR_4: u64 = 4; - let t0 = (x[0] as u64 + x[1] as u64) % BABY_BEAR_P_U64; - let t1 = (x[2] as u64 + x[3] as u64) % BABY_BEAR_P_U64; - let t2 = (CIRC_FACTOR_2 * x[1] as u64 + t1) % BABY_BEAR_P_U64; - let t3 = (CIRC_FACTOR_2 * x[3] as u64 + t0) % BABY_BEAR_P_U64; - let t4 = (CIRC_FACTOR_4 * t1 + t3) % BABY_BEAR_P_U64; - let t5 = (CIRC_FACTOR_4 * t0 + t2) % BABY_BEAR_P_U64; - let t6 = (t3 + t5) % BABY_BEAR_P_U64; - let t7 = (t2 + t4) % BABY_BEAR_P_U64; - [t6 as u32, t5 as u32, t7 as u32, t4 as u32] -} - -// Optimized method for multiplication by M_EXT. -#[inline(always)] -fn multiply_by_m_ext(inner: &mut [u32; CELLS]) { - let mut out = [0u32; CELLS]; - let mut tmp_sums = [0u32; 4]; - - for i in 0..(CELLS / 4) { - let chunk = multiply_by_4x4_circulant(&[ - inner[i * 4], - inner[i * 4 + 1], - inner[i * 4 + 2], - inner[i * 4 + 3], - ]); - for j in 0..4 { - let to_add = (chunk[j] as u64 % BABY_BEAR_P_U64) as u32; - tmp_sums[j] = (tmp_sums[j] + to_add) % BABY_BEAR_P_U32; - out[i * 4 + j] = (out[i * 4 + j] + to_add) % BABY_BEAR_P_U32; - } - } - for i in 0..CELLS { - inner[i] = (out[i] + tmp_sums[i % 4]) % BABY_BEAR_P_U32; - } -} - -// Exploit the fact that off-diagonal entries of M_INT are all 1. -#[inline(always)] -fn multiply_by_m_int(inner: &mut [u32; CELLS]) { - let mut sum = 0u64; - for i in 0..CELLS { - sum += inner[i] as u64; - } - sum %= BABY_BEAR_P_U64; - for (i, diag) in M_INT_DIAG_HZN.iter().enumerate().take(CELLS) { - let diag = diag.as_u32() as u64; - let cell = inner[i] as u64; - inner[i] = ((sum + diag * cell) % BABY_BEAR_P_U64) as u32; - } -} - -#[inline(always)] -fn add_round_constants_full(inner: &mut [u32; CELLS], round: usize) { - for i in 0..CELLS { - inner[i] += ROUND_CONSTANTS[round * CELLS + i].as_u32(); - inner[i] %= BABY_BEAR_P_U32; - } -} - -#[inline(always)] -fn add_round_constants_partial(inner: &mut [u32; CELLS], round: usize) { - inner[0] += ROUND_CONSTANTS[round * CELLS].as_u32(); - inner[0] %= BABY_BEAR_P_U32; -} - -#[inline(always)] -fn do_ext_round(inner: &mut [u32; CELLS], mut idx: usize) { - if idx >= ROUNDS_HALF_FULL { - idx += ROUNDS_PARTIAL; - } - add_round_constants_full(inner, idx); - for i in 0..CELLS { - inner[i] = sbox2(inner[i]); - } - multiply_by_m_ext(inner); -} - -#[inline(always)] -fn do_int_rounds(inner: &mut [u32; CELLS]) { - for i in 0..ROUNDS_PARTIAL { - add_round_constants_partial(inner, ROUNDS_HALF_FULL + i); - inner[0] = sbox2(inner[0]); - multiply_by_m_int(inner); - } -} \ No newline at end of file diff --git a/void/void-node/Cargo.toml b/void/void-node/Cargo.toml index 767f1a8..618ae74 100644 --- a/void/void-node/Cargo.toml +++ b/void/void-node/Cargo.toml @@ -22,7 +22,7 @@ void-core.workspace = true axum = { version = "0.8.4", features = ["json"], optional = true } #risc0 dependencies -risc0-zkvm = { version = "^3.0.3", optional = true } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", rev = "e03b8f0", optional = true } #sp1 dependencies sp1-sdk = { version = "5.0.8", optional = true } diff --git a/void/void-prove/Cargo.toml b/void/void-prove/Cargo.toml index 790a6ae..2b7e06e 100644 --- a/void/void-prove/Cargo.toml +++ b/void/void-prove/Cargo.toml @@ -8,7 +8,7 @@ license.workspace = true repository.workspace = true [dependencies] -risc0-zkvm = { version = "^3.0.3", default-features = false, features = ['std'], optional = true } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", rev = "e03b8f0", default-features = false, features = ['std'], optional = true } sp1-zkvm = { version = "5.0.8", features = ["verify"], optional = true } void-core.workspace = true From 5abef6dc70cdfd4bdf3dc6ffbbfda7c8810ecad8 Mon Sep 17 00:00:00 2001 From: PixelCircuits Date: Thu, 25 Sep 2025 14:10:03 -0400 Subject: [PATCH 3/3] updated benchmark results --- benchmarks/zk-proving/risczero/README.md | 79 ++++++------------------ 1 file changed, 19 insertions(+), 60 deletions(-) diff --git a/benchmarks/zk-proving/risczero/README.md b/benchmarks/zk-proving/risczero/README.md index c4a720c..89b7b9a 100644 --- a/benchmarks/zk-proving/risczero/README.md +++ b/benchmarks/zk-proving/risczero/README.md @@ -62,31 +62,7 @@ The following results were obtained on the various reference machines. ### AMD Ryzen 9 9950X + RTX 5090 GPU proving ``` -Proof generation benchmark results: -Events Time (s) TPS -1 3.84 0.26 -2 3.92 0.51 -5 4.47 1.12 -10 4.23 2.36 -20 5.66 3.53 -50 7.96 6.28 -100 11.43 8.75 -200 19.53 10.24 -500 39.54 12.65 -1000 74.62 13.40 -2000 135.90 14.72 -5000 309.93 16.13 -10000 566.03 17.67 -20000 1021.02 19.59 -Estimated TPS at various latencies: -Latency (s) TPS -5 3.07 -10 7.94 -30 11.90 -60 13.19 -120 14.50 -300 16.10 ``` ### AWS g6.xlarge @@ -94,50 +70,33 @@ GPU proving ``` Proof generation benchmark results: Events Time (s) TPS -1 6.23 0.16 -2 6.26 0.32 -5 6.79 0.74 -10 7.78 1.29 -20 9.23 2.17 -50 13.66 3.66 -100 19.64 5.09 -200 32.81 6.10 -500 69.10 7.24 -1000 126.09 7.93 -2000 234.46 8.53 -5000 520.38 9.61 -10000 942.56 10.61 +1 6.10 0.16 +2 6.11 0.33 +5 6.40 0.78 +10 6.86 1.46 +20 6.98 2.86 +50 9.12 5.48 +100 12.08 8.28 +200 18.05 11.08 +500 35.28 14.17 +1000 61.64 16.22 +2000 112.62 17.76 +5000 256.39 19.50 +10000 473.71 21.11 Estimated TPS at various latencies: Latency (s) TPS 5 -- -10 2.52 -30 5.96 -60 7.08 -120 7.89 -300 8.96 +10 6.48 +30 13.60 +60 16.15 +120 17.95 +300 20.01 +600 -- ``` ### AWS c6i.8xlarge CPU proving ``` -Proof generation benchmark results: -Events Time (s) TPS -1 66.61 0.02 -2 66.52 0.03 -5 84.84 0.06 -10 125.10 0.08 -20 152.68 0.13 -50 297.24 0.17 -100 472.23 0.21 -200 848.63 0.24 -Estimated TPS at various latencies: -Latency (s) TPS -5 -- -10 -- -30 -- -60 -- -120 0.08 -300 0.17 ```