Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ profile.json.gz

# test fixtures
benchmarks/fixtures

#TODO: Remove this
crates/toolchain/tests/rv32im-test-vectors/tests/*
*.o
*.a
*.s
*.txt
riscv/*
104 changes: 55 additions & 49 deletions crates/circuits/primitives/cuda/include/primitives/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,91 +3,97 @@
#include <cstddef>

namespace riscv {
static const size_t RV32_REGISTER_NUM_LIMBS = 4;
static const size_t RV32_CELL_BITS = 8;
static const size_t RV_J_TYPE_IMM_BITS = 21;
inline constexpr size_t RV32_REGISTER_NUM_LIMBS = 4;
inline constexpr size_t RV32_CELL_BITS = 8;
inline constexpr size_t RV_J_TYPE_IMM_BITS = 21;

static const size_t RV32_IMM_AS = 0;
inline constexpr size_t RV32_IMM_AS = 0;
} // namespace riscv

namespace program {
static const size_t PC_BITS = 30;
static const size_t DEFAULT_PC_STEP = 4;
inline constexpr size_t PC_BITS = 30;
inline constexpr size_t DEFAULT_PC_STEP = 4;
} // namespace program

namespace native {
static const size_t AS_IMMEDIATE = 0;
static const size_t AS_NATIVE = 4;
static const size_t EXT_DEG = 4;
static const size_t BETA = 11;
inline constexpr size_t AS_IMMEDIATE = 0;
inline constexpr size_t AS_NATIVE = 4;
inline constexpr size_t EXT_DEG = 4;
inline constexpr size_t BETA = 11;
} // namespace native

namespace poseidon2 {
static const size_t CHUNK = 8;
inline constexpr size_t CHUNK = 8;
} // namespace poseidon2

namespace p3_keccak_air {
static const size_t NUM_ROUNDS = 24;
static const size_t BITS_PER_LIMB = 16;
static const size_t U64_LIMBS = 64 / BITS_PER_LIMB;
static const size_t RATE_BITS = 1088;
static const size_t RATE_LIMBS = RATE_BITS / BITS_PER_LIMB;
inline constexpr size_t NUM_ROUNDS = 24;
inline constexpr size_t BITS_PER_LIMB = 16;
inline constexpr size_t U64_LIMBS = 64 / BITS_PER_LIMB;
inline constexpr size_t RATE_BITS = 1088;
inline constexpr size_t RATE_LIMBS = RATE_BITS / BITS_PER_LIMB;
} // namespace p3_keccak_air

namespace keccak256 {
/// Total number of sponge bytes: number of rate bytes + number of capacity bytes.
static const size_t KECCAK_WIDTH_BYTES = 200;
inline constexpr size_t KECCAK_WIDTH_BYTES = 200;
/// Total number of 16-bit limbs in the sponge.
static const size_t KECCAK_WIDTH_U16S = KECCAK_WIDTH_BYTES / 2;
inline constexpr size_t KECCAK_WIDTH_U16S = KECCAK_WIDTH_BYTES / 2;
/// Number of rate bytes.
static const size_t KECCAK_RATE_BYTES = 136;
inline constexpr size_t KECCAK_RATE_BYTES = 136;
/// Number of 16-bit rate limbs.
static const size_t KECCAK_RATE_U16S = KECCAK_RATE_BYTES / 2;
inline constexpr size_t KECCAK_RATE_U16S = KECCAK_RATE_BYTES / 2;
/// Number of absorb rounds, equal to rate in u64s.
static const size_t NUM_ABSORB_ROUNDS = KECCAK_RATE_BYTES / 8;
inline constexpr size_t NUM_ABSORB_ROUNDS = KECCAK_RATE_BYTES / 8;
/// Number of capacity bytes.
static const size_t KECCAK_CAPACITY_BYTES = 64;
inline constexpr size_t KECCAK_CAPACITY_BYTES = 64;
/// Number of 16-bit capacity limbs.
static const size_t KECCAK_CAPACITY_U16S = KECCAK_CAPACITY_BYTES / 2;
inline constexpr size_t KECCAK_CAPACITY_U16S = KECCAK_CAPACITY_BYTES / 2;
/// Number of output digest bytes used during the squeezing phase.
static const size_t KECCAK_DIGEST_BYTES = 32;
inline constexpr size_t KECCAK_DIGEST_BYTES = 32;
/// Number of 64-bit digest limbs.
static const size_t KECCAK_DIGEST_U64S = KECCAK_DIGEST_BYTES / 8;
inline constexpr size_t KECCAK_DIGEST_U64S = KECCAK_DIGEST_BYTES / 8;

// ==== Constants for register/memory adapter ====
/// Register reads to get dst, src, len
static const size_t KECCAK_REGISTER_READS = 3;
inline constexpr size_t KECCAK_REGISTER_READS = 3;
/// Number of cells to read/write in a single memory access
static const size_t KECCAK_WORD_SIZE = 4;
inline constexpr size_t KECCAK_WORD_SIZE = 4;
/// Memory reads for absorb per row
static const size_t KECCAK_ABSORB_READS = KECCAK_RATE_BYTES / KECCAK_WORD_SIZE;
inline constexpr size_t KECCAK_ABSORB_READS = KECCAK_RATE_BYTES / KECCAK_WORD_SIZE;
/// Memory writes for digest per row
static const size_t KECCAK_DIGEST_WRITES = KECCAK_DIGEST_BYTES / KECCAK_WORD_SIZE;
inline constexpr size_t KECCAK_DIGEST_WRITES = KECCAK_DIGEST_BYTES / KECCAK_WORD_SIZE;
/// keccakf parameters
static const size_t KECCAK_ROUND = 24;
static const size_t KECCAK_STATE_SIZE = 25;
static const size_t KECCAK_Q_SIZE = 192;
inline constexpr size_t KECCAK_ROUND = 24;
inline constexpr size_t KECCAK_STATE_SIZE = 25;
inline constexpr size_t KECCAK_Q_SIZE = 192;
/// From memory config
static const size_t KECCAK_POINTER_MAX_BITS = 29;
inline constexpr size_t KECCAK_POINTER_MAX_BITS = 29;
} // namespace keccak256

namespace mod_builder {
static const size_t MAX_LIMBS = 97;
inline constexpr size_t MAX_LIMBS = 97;
} // namespace mod_builder

namespace sha256 {
static const size_t SHA256_BLOCK_BITS = 512;
static const size_t SHA256_BLOCK_U8S = 64;
static const size_t SHA256_BLOCK_WORDS = 16;
static const size_t SHA256_WORD_U8S = 4;
static const size_t SHA256_WORD_BITS = 32;
static const size_t SHA256_WORD_U16S = 2;
static const size_t SHA256_HASH_WORDS = 8;
static const size_t SHA256_NUM_READ_ROWS = 4;
static const size_t SHA256_ROWS_PER_BLOCK = 17;
static const size_t SHA256_ROUNDS_PER_ROW = 4;
static const size_t SHA256_ROW_VAR_CNT = 5;
static const size_t SHA256_REGISTER_READS = 3;
static const size_t SHA256_READ_SIZE = 16;
static const size_t SHA256_WRITE_SIZE = 32;
} // namespace sha256
inline constexpr size_t SHA256_BLOCK_BITS = 512;
inline constexpr size_t SHA256_BLOCK_U8S = 64;
inline constexpr size_t SHA256_BLOCK_WORDS = 16;
inline constexpr size_t SHA256_WORD_U8S = 4;
inline constexpr size_t SHA256_WORD_BITS = 32;
inline constexpr size_t SHA256_WORD_U16S = 2;
inline constexpr size_t SHA256_HASH_WORDS = 8;
inline constexpr size_t SHA256_NUM_READ_ROWS = 4;
inline constexpr size_t SHA256_ROWS_PER_BLOCK = 17;
inline constexpr size_t SHA256_ROUNDS_PER_ROW = 4;
inline constexpr size_t SHA256_ROW_VAR_CNT = 5;
inline constexpr size_t SHA256_REGISTER_READS = 3;
inline constexpr size_t SHA256_READ_SIZE = 16;
inline constexpr size_t SHA256_WRITE_SIZE = 32;
} // namespace sha256

namespace hintstore {
// Must match MAX_HINT_BUFFER_WORDS_BITS in openvm_rv32im_guest::lib.rs
inline constexpr size_t MAX_HINT_BUFFER_WORDS_BITS = 18;
inline constexpr size_t MAX_HINT_BUFFER_WORDS = (1 << MAX_HINT_BUFFER_WORDS_BITS) - 1;
} // namespace hintstore
4 changes: 2 additions & 2 deletions crates/toolchain/openvm/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use core::alloc::Layout;
use core::fmt::Write;

#[cfg(target_os = "zkvm")]
use openvm_rv32im_guest::{hint_buffer_u32, hint_input, hint_store_u32};
use openvm_rv32im_guest::{hint_buffer_chunked, hint_input, hint_store_u32};
use serde::de::DeserializeOwned;

#[cfg(not(target_os = "zkvm"))]
Expand Down Expand Up @@ -83,7 +83,7 @@ pub(crate) fn read_vec_by_len(len: usize) -> Vec<u8> {
// The heap-embedded-alloc uses linked list allocator, which has a minimum alignment of
// `sizeof(usize) * 2 = 8` on 32-bit architectures: https://github.com/rust-osdev/linked-list-allocator/blob/b5caf3271259ddda60927752fa26527e0ccd2d56/src/hole.rs#L429
let mut bytes = Vec::with_capacity(capacity);
hint_buffer_u32!(bytes.as_mut_ptr(), num_words);
hint_buffer_chunked(bytes.as_mut_ptr(), num_words as usize);
// SAFETY: We populate a `Vec<u8>` by hintstore-ing `num_words` 4 byte words. We set the
// length to `len` and don't care about the extra `capacity - len` bytes stored.
unsafe {
Expand Down
6 changes: 3 additions & 3 deletions crates/toolchain/openvm/src/io/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::mem::MaybeUninit;

use openvm_platform::WORD_SIZE;
#[cfg(target_os = "zkvm")]
use openvm_rv32im_guest::hint_buffer_u32;
use openvm_rv32im_guest::hint_buffer_chunked;

use super::hint_store_word;
use crate::serde::WordRead;
Expand Down Expand Up @@ -31,7 +31,7 @@ impl WordRead for Reader {
let num_words = words.len();
if let Some(new_remaining) = self.bytes_remaining.checked_sub(num_words * WORD_SIZE) {
#[cfg(target_os = "zkvm")]
hint_buffer_u32!(words.as_mut_ptr(), words.len());
hint_buffer_chunked(words.as_mut_ptr() as *mut u8, words.len());
#[cfg(not(target_os = "zkvm"))]
{
for w in words.iter_mut() {
Expand All @@ -51,7 +51,7 @@ impl WordRead for Reader {
}
let mut num_padded_bytes = bytes.len();
#[cfg(target_os = "zkvm")]
hint_buffer_u32!(bytes as *mut [u8] as *mut u32, num_padded_bytes / WORD_SIZE);
hint_buffer_chunked(bytes.as_mut_ptr(), num_padded_bytes / WORD_SIZE);
#[cfg(not(target_os = "zkvm"))]
{
let mut words = bytes.chunks_exact_mut(WORD_SIZE);
Expand Down
4 changes: 2 additions & 2 deletions crates/toolchain/openvm/src/pal_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// system operations in the same way: there is no operating system and even the standard
/// library should be directly handled with intrinsics.
use openvm_platform::{fileno::*, memory::sys_alloc_aligned, rust_rt::terminate, WORD_SIZE};
use openvm_rv32im_guest::{hint_buffer_u32, hint_random, raw_print_str_from_bytes};
use openvm_rv32im_guest::{hint_buffer_chunked, hint_random, raw_print_str_from_bytes};

const DIGEST_WORDS: usize = 8;

Expand Down Expand Up @@ -73,7 +73,7 @@ pub unsafe extern "C" fn sys_sha_buffer(
#[no_mangle]
pub unsafe extern "C" fn sys_rand(recv_buf: *mut u32, words: usize) {
hint_random(words);
hint_buffer_u32!(recv_buf, words);
hint_buffer_chunked(recv_buf as *mut u8, words);
}

/// # Safety
Expand Down
6 changes: 6 additions & 0 deletions crates/vm/src/arch/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ pub enum ExecutionError {
DisabledOperation { pc: u32, opcode: VmOpcode },
#[error("at pc = {pc}")]
HintOutOfBounds { pc: u32 },
#[error("at pc {pc}, hint buffer num_words {num_words} exceeds MAX_HINT_BUFFER_WORDS {max_hint_buffer_words}")]
HintBufferTooLarge {
pc: u32,
num_words: u32,
max_hint_buffer_words: u32,
},
#[error("at pc {pc}, tried to publish into index {public_value_index} when num_public_values = {num_public_values}")]
PublicValueIndexOutOfBounds {
pc: u32,
Expand Down
5 changes: 4 additions & 1 deletion docs/vocs/docs/pages/specs/openvm/isa.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ OpenVM depends on the following parameters, some of which are fixed and some of
| `addr_space_height` | The base 2 log of the number of writable address spaces supported. | Configurable, must satisfy `addr_space_height <= F::bits() - 2` |
| `pointer_max_bits` | The maximum number of bits in a pointer. | Configurable, must satisfy `pointer_max_bits <= F::bits() - 2` |
| `num_public_values` | The number of user public values. | Configurable. If continuation is enabled, it must equal `8` times a power of two(which is nonzero). |
| `MAX_HINT_BUFFER_WORDS_BITS` | The maximum number of bits for hint buffer word count. This determines `MAX_HINT_BUFFER_WORDS = 2^MAX_HINT_BUFFER_WORDS_BITS - 1` = 262,143 words (≈1MB), the maximum words per `HINT_BUFFER_RV32` instruction. | Fixed to 18. |

We explain these parameters in subsequent sections.

Expand Down Expand Up @@ -428,9 +429,11 @@ with user input-output.
| Name | Operands | Description |
| ---------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HINT_STOREW_RV32 | `_,b,_,1,2` | `[r32{0}(b):4]_2 = next 4 bytes from hint stream`. Only valid if next 4 values in hint stream are bytes. |
| HINT_BUFFER_RV32 | `a,b,_,1,2` | `[r32{0}(b):4 * l]_2 = next 4 * l bytes from hint stream` where `l = r32{0}(a)`. Only valid if next `4 * l` values in hint stream are bytes. Very important: `l` should not be 0. The pointer address `r32{0}(b)` does not need to be a multiple of `4`. |
| HINT_BUFFER_RV32 | `a,b,_,1,2` | `[r32{0}(b):4 * l]_2 = next 4 * l bytes from hint stream` where `l = r32{0}(a)`. Only valid if next `4 * l` values in hint stream are bytes. `l` must be non-zero and <= `MAX_HINT_BUFFER_WORDS` (262,143 words ≈ 1MB). The pointer address `r32{0}(b)` does not need to be a multiple of `4`. |
| REVEAL_RV32 | `a,b,c,1,3,_,g` | Pseudo-instruction for `STOREW_RV32 a,b,c,1,3,_,g` writing to the user IO address space `3`. Only valid when continuations are enabled. |

> **Note:** The `MAX_HINT_BUFFER_WORDS` bound on `HINT_BUFFER_RV32` is enforced by both the executor and AIR constraints. The SDK's `hint_buffer_chunked` function automatically splits larger reads into multiple `HINT_BUFFER_RV32` instructions.

#### Phantom Sub-Instructions

The RV32IM extension defines the following phantom sub-instructions.
Expand Down
12 changes: 6 additions & 6 deletions extensions/algebra/moduli-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,15 +875,15 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream {
}
#[cfg(target_os = "zkvm")]
{
use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_store_u32! and hint_buffer_u32!
use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_store_u32! and hint_buffer_chunked

let is_square = core::mem::MaybeUninit::<u32>::uninit();
let sqrt = core::mem::MaybeUninit::<#struct_name>::uninit();
let mut sqrt = core::mem::MaybeUninit::<#struct_name>::uninit();
unsafe {
#hint_sqrt_extern_func(self as *const #struct_name as usize);
let is_square_ptr = is_square.as_ptr() as *const u32;
openvm_rv32im_guest::hint_store_u32!(is_square_ptr);
openvm_rv32im_guest::hint_buffer_u32!(sqrt.as_ptr() as *const u8, <#struct_name as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4);
openvm_rv32im_guest::hint_buffer_chunked(sqrt.as_mut_ptr() as *mut u8, <#struct_name as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4 as usize);
let is_square = is_square.assume_init();
if is_square == 0 || is_square == 1 {
Some((is_square == 1, sqrt.assume_init()))
Expand All @@ -902,14 +902,14 @@ pub fn moduli_declare(input: TokenStream) -> TokenStream {
}
#[cfg(target_os = "zkvm")]
{
use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_buffer_u32!
use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_buffer_chunked

let mut non_qr_uninit = core::mem::MaybeUninit::<Self>::uninit();
let mut non_qr;
unsafe {
#hint_non_qr_extern_func();
let ptr = non_qr_uninit.as_ptr() as *const u8;
openvm_rv32im_guest::hint_buffer_u32!(ptr, <Self as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4);
let ptr = non_qr_uninit.as_mut_ptr() as *mut u8;
openvm_rv32im_guest::hint_buffer_chunked(ptr, <Self as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4 as usize);
non_qr = non_qr_uninit.assume_init();
}
// ensure non_qr < modulus
Expand Down
18 changes: 17 additions & 1 deletion extensions/rv32im/circuit/cuda/src/hintstore.cu
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

using namespace riscv;
using namespace program;
using hintstore::MAX_HINT_BUFFER_WORDS;
using hintstore::MAX_HINT_BUFFER_WORDS_BITS;

template <typename T> struct Rv32HintStoreCols {
// common
Expand Down Expand Up @@ -87,11 +89,25 @@ struct Rv32HintStore {
COL_WRITE_ARRAY(row, Rv32HintStoreCols, mem_ptr_limbs, mem_ptr_limbs);

if (local_idx == 0) {
// The overflow check for mem_ptr + num_words * 4 is not needed because
// 4 * MAX_HINT_BUFFER_WORDS < 2^pointer_max_bits guarantees no overflow
assert(MAX_HINT_BUFFER_WORDS_BITS + 2 < pointer_max_bits);

// Range check for mem_ptr (using pointer_max_bits)
uint32_t msl_rshift = (RV32_REGISTER_NUM_LIMBS - 1) * RV32_CELL_BITS;
uint32_t msl_lshift = RV32_REGISTER_NUM_LIMBS * RV32_CELL_BITS - pointer_max_bits;

// Range check for num_words (using MAX_HINT_BUFFER_WORDS_BITS)
// These constraints only work for MAX_HINT_BUFFER_WORDS_BITS in [16, 23]
assert(MAX_HINT_BUFFER_WORDS_BITS >= 16 && MAX_HINT_BUFFER_WORDS_BITS <= 23);

assert(record.num_words <= MAX_HINT_BUFFER_WORDS);
uint32_t rem_words_limb2_lshift = (RV32_REGISTER_NUM_LIMBS - 1) * RV32_CELL_BITS - MAX_HINT_BUFFER_WORDS_BITS;

// Combined range check for mem_ptr and num_words
bitwise_lookup.add_range(
(record.mem_ptr >> msl_rshift) << msl_lshift,
(record.num_words >> msl_rshift) << msl_lshift
((record.num_words >> 16) & 0xFF) << rem_words_limb2_lshift
);
mem_helper.fill(
row.slice_from(COL_INDEX(Rv32HintStoreCols, mem_ptr_aux_cols)),
Expand Down
10 changes: 10 additions & 0 deletions extensions/rv32im/circuit/src/hintstore/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use openvm_instructions::{
use openvm_rv32im_transpiler::{
Rv32HintStoreOpcode,
Rv32HintStoreOpcode::{HINT_BUFFER, HINT_STOREW},
MAX_HINT_BUFFER_WORDS,
};
use openvm_stark_backend::p3_field::PrimeField32;

Expand Down Expand Up @@ -172,6 +173,15 @@ unsafe fn execute_e12_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_HIN
};
debug_assert_ne!(num_words, 0);

// Bounds check: num_words must not exceed MAX_HINT_BUFFER_WORDS
if num_words > MAX_HINT_BUFFER_WORDS as u32 {
return Err(ExecutionError::HintBufferTooLarge {
pc,
num_words,
max_hint_buffer_words: MAX_HINT_BUFFER_WORDS as u32,
});
}

if exec_state.streams.hint_stream.len() < RV32_REGISTER_NUM_LIMBS * num_words as usize {
let err = ExecutionError::HintOutOfBounds { pc };
return Err(err);
Expand Down
Loading