diff --git a/toolchain/build/src/lib.rs b/toolchain/build/src/lib.rs
index e78531172d..f4c21070c2 100644
--- a/toolchain/build/src/lib.rs
+++ b/toolchain/build/src/lib.rs
@@ -257,11 +257,11 @@ pub fn build_guest_package
(
"--target-dir",
target_dir.as_ref().to_str().unwrap(),
]);
- tty_println(&format!("cargo command: {:?}", cmd));
if !is_debug() {
cmd.args(["--release"]);
}
+ tty_println(&format!("cargo command: {:?}", cmd));
let mut child = cmd
.stderr(Stdio::piped())
diff --git a/toolchain/instructions/src/phantom.rs b/toolchain/instructions/src/phantom.rs
index eb4ecf0127..dda16e42e2 100644
--- a/toolchain/instructions/src/phantom.rs
+++ b/toolchain/instructions/src/phantom.rs
@@ -13,6 +13,8 @@ pub enum PhantomInstruction {
PrintF,
/// Prepare the next input vector for hinting.
HintInput,
+ /// Prepare the next input vector for hinting, but prepend it with a 4-byte decomposition of its length instead of one field element.
+ HintInputRv32,
/// Prepare the little-endian bit decomposition of a variable for hinting.
HintBits,
/// Start tracing
diff --git a/toolchain/riscv/axvm/src/env/mod.rs b/toolchain/riscv/axvm/src/env/mod.rs
deleted file mode 100644
index 96a2206a22..0000000000
--- a/toolchain/riscv/axvm/src/env/mod.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2024 RISC Zero, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Functions for interacting with the host environment.
-//!
-//! The zkVM provides a set of functions to perform operations that manage
-//! execution, I/O, and proof composition. The set of functions related to each
-//! of these operations are described below.
-//!
-//! ## System State
-//!
-//! The guest has some control over the execution of the zkVM by pausing or
-//! exiting the program explicitly. This can be achieved using the [pause] and
-//! [exit] functions.
-//!
-//! ## Proof Verification
-//!
-//! The zkVM supports verification of RISC Zero [receipts] in a guest program,
-//! enabling [proof composition]. This can be achieved using the [verify()] and
-//! [verify_integrity] functions.
-//!
-//! ## Input and Output
-//!
-//! The zkVM provides a set of functions for handling input, public output, and
-//! private output. This is useful when interacting with the host and committing
-//! to some data publicly.
-//!
-//! The zkVM provides functions that automatically perform (de)serialization on
-//! types and, for performance reasons, there is also a `_slice` variant that
-//! works with raw slices of plain old data. Performing operations on slices is
-//! more efficient, saving cycles during execution and consequently producing
-//! smaller proofs that are faster to produce. However, the `_slice` variants
-//! can be less ergonomic, so consider trade-offs when choosing between the two.
-//! For more information about guest optimization, see RISC Zero's [instruction
-//! on guest optimization][guest-optimization]
-//!
-//! Convenience functions to read and write to default file descriptors are
-//! provided. See [read()], [write()], [self::commit] (and their `_slice`
-//! variants) for more information.
-//!
-//! In order to access default file descriptors directly, see [stdin], [stdout],
-//! [stderr] and [journal]. These file descriptors are either [FdReader] or
-//! [FdWriter] instances, which can be used to read from or write to the host.
-//! To read from or write into them, use the [Read] and [Write] traits.
-//!
-//! WARNING: Specifying a file descriptor with the same value of a default file
-//! descriptor is not recommended and may lead to unexpected behavior. A list of
-//! default file descriptors can be found in the [fileno] module.
-//!
-//! ## Utility
-//!
-//! The zkVM provides utility functions to log messages to the debug console and
-//! to measure the number of processor cycles that have occurred since the guest
-//! began. These can be achieved using the [log] and [cycle_count] functions.
-//!
-//! [receipts]: crate::Receipt
-//! [proof composition]:https://www.risczero.com/blog/proof-composition
-//! [guest-optimization]:
-//! https://dev.risczero.com/api/zkvm/optimization#when-reading-data-as-raw-bytes-use-envread_slice
-
-extern crate alloc;
-
-use axvm_platform;
-
-/// Terminate execution of the zkVM.
-///
-/// Use an exit code of 0 to indicate success, and non-zero to indicate an error.
-#[inline(always)]
-pub fn exit() -> ! {
- axvm_platform::rust_rt::terminate::();
- unreachable!();
-}
diff --git a/toolchain/riscv/axvm/src/intrinsics/io.rs b/toolchain/riscv/axvm/src/intrinsics/io.rs
new file mode 100644
index 0000000000..bc22275a8d
--- /dev/null
+++ b/toolchain/riscv/axvm/src/intrinsics/io.rs
@@ -0,0 +1,17 @@
+// use crate::custom_insn_i;
+
+use axvm_platform::{custom_insn_i, intrinsics::CUSTOM_0};
+
+/// Store the next 4 bytes from the hint stream to [[rd] + imm]_2.
+#[macro_export]
+macro_rules! hint_store_u32 {
+ ($x:ident, $imm:expr) => {
+ axvm_platform::custom_insn_i!(axvm_platform::intrinsics::CUSTOM_0, 0b001, $x, "x0", $imm)
+ };
+}
+
+/// Reset the hint stream with the next hint.
+#[inline(always)]
+pub fn hint_input() {
+ custom_insn_i!(CUSTOM_0, 0b011, "x0", "x0", 0);
+}
diff --git a/toolchain/riscv/axvm/src/intrinsics/mod.rs b/toolchain/riscv/axvm/src/intrinsics/mod.rs
index d6c4f0426a..0dabfe4216 100644
--- a/toolchain/riscv/axvm/src/intrinsics/mod.rs
+++ b/toolchain/riscv/axvm/src/intrinsics/mod.rs
@@ -1,5 +1,8 @@
//! Functions that call custom instructions that use axVM intrinsic instructions.
mod hash;
+/// Library functions for user input/output.
+pub mod io;
pub use hash::*;
+pub use io::*;
diff --git a/toolchain/riscv/axvm/src/io.rs b/toolchain/riscv/axvm/src/io.rs
new file mode 100644
index 0000000000..24d33485f6
--- /dev/null
+++ b/toolchain/riscv/axvm/src/io.rs
@@ -0,0 +1,47 @@
+//! User IO functions
+
+use alloc::vec::Vec;
+use core::alloc::Layout;
+
+use crate::{hint_store_u32, intrinsics::hint_input};
+
+/// Read `size: u32` and then `size` bytes from the hint stream into a vector.
+pub fn read_vec() -> Vec {
+ hint_input();
+ read_vec_by_len(read_u32() as usize)
+}
+
+/// Read the next 4 bytes from the hint stream into a register.
+/// Because [hint_store_u32] stores a word to memory, this function first reads to memory and then
+/// loads from memory to register.
+#[inline(always)]
+#[allow(asm_sub_register)]
+pub fn read_u32() -> u32 {
+ let ptr = unsafe { alloc::alloc::alloc(Layout::from_size_align(4, 4).unwrap()) };
+ let addr = ptr as u32;
+ hint_store_u32!(addr, 0);
+ let result: u32;
+ unsafe {
+ core::arch::asm!("lw {rd}, ({rs1})", rd = out(reg) result, rs1 = in(reg) addr);
+ }
+ result
+}
+
+/// Read the next `len` bytes from the hint stream into a vector.
+fn read_vec_by_len(len: usize) -> Vec {
+ let num_words = (len + 3) / 4;
+ let capacity = num_words * 4;
+ // Allocate a buffer of the required length that is 4 byte aligned
+ // Note: this expect message doesn't matter until our panic handler actually cares about it
+ let layout = Layout::from_size_align(capacity, 4).expect("vec is too large");
+ // SAFETY: We populate a `Vec` 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.
+ let ptr_start = unsafe { alloc::alloc::alloc(layout) };
+ let mut ptr = ptr_start;
+
+ // Note: if len % 4 != 0, this will discard some last bytes
+ for _ in 0..num_words {
+ hint_store_u32!(ptr, 0);
+ ptr = unsafe { ptr.add(4) };
+ }
+ unsafe { Vec::from_raw_parts(ptr_start, len, capacity) }
+}
diff --git a/toolchain/riscv/axvm/src/lib.rs b/toolchain/riscv/axvm/src/lib.rs
index e2913b8a5d..b6f92504d9 100644
--- a/toolchain/riscv/axvm/src/lib.rs
+++ b/toolchain/riscv/axvm/src/lib.rs
@@ -1,89 +1,16 @@
-// Copyright 2024 RISC Zero, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! The RISC Zero zkVM's guest-side RISC-V API.
-//!
-//! Code that is validated by the [RISC Zero zkVM](crate) is run inside the guest. In almost all
-//! practical cases, the guest will want to read private input data from the host and write public
-//! data to the journal. This can be done with [env::read] and [env::commit], respectively;
-//! additional I/O functionality is also available in [mod@env].
-//!
-//! ## Installation
-//!
-//! To build and run RISC Zero zkVM code, you will need to install the RISC Zero
-//! toolchain, which can be done using the rzup utility:
-//!
-//! ```sh
-//! curl -L https://risczero.com/install | bash
-//! rzup install
-//! ```
-//!
-//! ## Example
-//!
-//! The following guest code[^starter-ex] proves a number is
-//! composite by multiplying two unsigned integers, and panicking if either is
-//! `1` or if the multiplication overflows:
-//!
-//! ```ignore
-//! #![no_main]
-//! #![no_std]
-//!
-//! use risc0_zkvm::guest::env;
-//!
-//! risc0_zkvm::guest::entry!(main);
-//!
-//! fn main() {
-//! // Load the first number from the host
-//! let a: u64 = env::read();
-//! // Load the second number from the host
-//! let b: u64 = env::read();
-//! // Verify that neither of them are 1 (i.e. nontrivial factors)
-//! if a == 1 || b == 1 {
-//! panic!("Trivial factors")
-//! }
-//! // Compute the product while being careful with integer overflow
-//! let product = a.checked_mul(b).expect("Integer overflow");
-//! env::commit(&product);
-//! }
-//! ```
-//!
-//! Notice how [env::read] is used to load the two factors, and [env::commit] is used to make their
-//! composite product publicly available. All input an output of your guest is private except for
-//! what is written to the journal with [env::commit].
-//!
-//! By default, the guest only has the Rust `core` libraries and not `std`. A partial
-//! implementation of the Rust standard libraries can be enabled with the `std` feature on this [crate].
-//! When this feature is not enabled, the lines including `#![no_std]` and `#![no_main]` are
-//! required, as well as the use of the [crate::guest::entry] macro. When `std` is enabled, these
-//! three lines can be omitted and many features of `std` can be used.
-//!
-//! If you encounter problems building zkVM guest code, you can see if we have a
-//! known workaround for your issue by looking in our
-//! [rust guest workarounds](https://github.com/risc0/risc0/issues?q=is%3Aissue+is%3Aopen+label%3A%22rust+guest+workarounds%22)
-//! tag on GitHub.
-//!
-//! [^starter-ex]: The example is based on the [Factors example](https://github.com/risc0/risc0/tree/main/examples/factors).
+//! # axVM
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+#![feature(asm_const)]
extern crate alloc;
-pub mod env;
pub mod intrinsics;
+pub mod io;
+pub mod process;
pub mod serde;
#[cfg(target_os = "zkvm")]
@@ -165,7 +92,8 @@ unsafe extern "C" fn __start() -> ! {
main()
}
- env::exit::<0>();
+ process::exit();
+ unreachable!()
}
#[cfg(target_os = "zkvm")]
diff --git a/toolchain/riscv/axvm/src/process.rs b/toolchain/riscv/axvm/src/process.rs
new file mode 100644
index 0000000000..bb98b32ca4
--- /dev/null
+++ b/toolchain/riscv/axvm/src/process.rs
@@ -0,0 +1,11 @@
+//! System exit and panic functions.
+
+/// Exit the program with exit code 0.
+pub fn exit() {
+ axvm_platform::rust_rt::terminate::<0>();
+}
+
+/// Exit the program with exit code 1.
+pub fn panic() {
+ axvm_platform::rust_rt::terminate::<1>();
+}
diff --git a/toolchain/riscv/examples/hint/program/.cargo/config.toml b/toolchain/riscv/examples/hint/program/.cargo/config.toml
new file mode 100644
index 0000000000..bb18f45366
--- /dev/null
+++ b/toolchain/riscv/examples/hint/program/.cargo/config.toml
@@ -0,0 +1,5 @@
+[build]
+target = "riscv32im-risc0-zkvm-elf"
+
+[unstable]
+build-std = ["core", "alloc", "proc_macro", "panic_abort"]
diff --git a/toolchain/riscv/examples/hint/program/Cargo.toml b/toolchain/riscv/examples/hint/program/Cargo.toml
new file mode 100644
index 0000000000..a04e080de9
--- /dev/null
+++ b/toolchain/riscv/examples/hint/program/Cargo.toml
@@ -0,0 +1,8 @@
+[workspace]
+[package]
+version = "0.1.0"
+name = "axvm-hint-program"
+edition = "2021"
+
+[dependencies]
+axvm = { path = "../../../axvm" }
diff --git a/toolchain/riscv/examples/hint/program/src/main.rs b/toolchain/riscv/examples/hint/program/src/main.rs
new file mode 100644
index 0000000000..b2ca6ac68e
--- /dev/null
+++ b/toolchain/riscv/examples/hint/program/src/main.rs
@@ -0,0 +1,13 @@
+#![no_main]
+#![no_std]
+use axvm::io::read_vec;
+
+axvm::entry!(main);
+
+pub fn main() {
+ let vec = read_vec();
+ assert_eq!(vec.len(), 4);
+ for i in 0..4 {
+ assert_eq!(vec[i], i as u8);
+ }
+}
diff --git a/toolchain/riscv/platform/src/intrinsics/mod.rs b/toolchain/riscv/platform/src/intrinsics/mod.rs
new file mode 100644
index 0000000000..b264654605
--- /dev/null
+++ b/toolchain/riscv/platform/src/intrinsics/mod.rs
@@ -0,0 +1,53 @@
+pub const CUSTOM_0: u8 = 0x0b;
+pub const CUSTOM_1: u8 = 0x2b;
+
+#[macro_export]
+macro_rules! custom_insn_i {
+ ($opcode:expr, $funct3:expr, $rd:literal, $rs1:literal, $imm:expr) => {
+ unsafe {
+ core::arch::asm!(concat!(
+ ".insn i {opcode}, {funct3}, ",
+ $rd,
+ ", ",
+ $rs1,
+ ", {imm}",
+ ), opcode = const $opcode, funct3 = const $funct3, imm = const $imm)
+ }
+ };
+ ($opcode:expr, $funct3:expr, $x:ident, $rs1:literal, $imm:expr) => {
+ unsafe {
+ core::arch::asm!(concat!(
+ ".insn i {opcode}, {funct3}, {rd}, ",
+ $rs1,
+ ", {imm}",
+ ), opcode = const $opcode, funct3 = const $funct3, rd = in(reg) $x, imm = const $imm)
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! custom_insn_r {
+ ($opcode:expr, $funct3:expr, $rd:literal, $rs1:literal, $rs2:literal) => {
+ unsafe {
+ core::arch::asm!(concat!(
+ ".insn r {opcode}, {funct3}, ",
+ $rd,
+ ", ",
+ $rs1,
+ ", ",
+ $rs2,
+ ), opcode = const $opcode, funct3 = const $funct3)
+ }
+ };
+ ($opcode:expr, $funct3:expr, $x:ident, $rs1:literal, $rs2:literal) => {
+ unsafe {
+ core::arch::asm!(concat!(
+ ".insn r {opcode}, {funct3}, {rd}, ",
+ $rs1,
+ ", ",
+ $rs2,
+ ), opcode = const $opcode, funct3 = const $funct3, rd = out(reg) $x)
+ }
+ };
+ // TODO: implement more variants with like rs1 = in(reg) $y etc
+}
diff --git a/toolchain/riscv/platform/src/lib.rs b/toolchain/riscv/platform/src/lib.rs
index 1267dc3e7b..9f001cf9fe 100644
--- a/toolchain/riscv/platform/src/lib.rs
+++ b/toolchain/riscv/platform/src/lib.rs
@@ -26,6 +26,7 @@ pub mod memory;
// mod getrandom;
#[cfg(all(feature = "rust-runtime", target_os = "zkvm"))]
pub mod heap;
+pub mod intrinsics;
#[cfg(all(feature = "export-libm", target_os = "zkvm"))]
mod libm_extern;
#[cfg(feature = "rust-runtime")]
diff --git a/toolchain/riscv/platform/src/rust_rt.rs b/toolchain/riscv/platform/src/rust_rt.rs
index 3b6f90ab9e..de4ea56b00 100644
--- a/toolchain/riscv/platform/src/rust_rt.rs
+++ b/toolchain/riscv/platform/src/rust_rt.rs
@@ -23,17 +23,17 @@
#[cfg(target_os = "zkvm")]
use core::arch::asm;
+#[cfg(target_os = "zkvm")]
+use crate::{custom_insn_i, custom_insn_r, intrinsics::CUSTOM_0};
+
extern crate alloc;
#[inline(always)]
pub fn terminate() {
#[cfg(target_os = "zkvm")]
- unsafe {
- asm!(".insn i 0x0b, 0, x0, x0, {ec}", ec = const EXIT_CODE)
- };
+ custom_insn_i!(CUSTOM_0, 0, "x0", "x0", EXIT_CODE);
#[cfg(not(target_os = "zkvm"))]
{
- core::hint::black_box(());
unimplemented!()
}
}
diff --git a/toolchain/riscv/transpiler/src/rrs.rs b/toolchain/riscv/transpiler/src/rrs.rs
index 1dc65f37ea..04e3646bdb 100644
--- a/toolchain/riscv/transpiler/src/rrs.rs
+++ b/toolchain/riscv/transpiler/src/rrs.rs
@@ -9,8 +9,10 @@ use axvm_circuit::{
rv32im::adapters::RV32_REGISTER_NUM_LIMBS,
};
use axvm_instructions::{
- instruction::Instruction, riscv::RvIntrinsic, EccOpcode, Rv32ModularArithmeticOpcode,
+ instruction::Instruction, riscv::RvIntrinsic, EccOpcode, PhantomInstruction,
+ Rv32HintStoreOpcode, Rv32ModularArithmeticOpcode,
};
+use axvm_platform::intrinsics::{CUSTOM_0, CUSTOM_1};
use p3_field::PrimeField32;
use rrs_lib::{
instruction_formats::{BType, IType, ITypeShamt, JType, RType, SType, UType},
@@ -245,24 +247,32 @@ fn process_custom_instruction(instruction_u32: u32) -> Instruct
let funct3 = ((instruction_u32 >> 12) & 0b111) as u8; // All our instructions are R- or I-type
match opcode {
- 0x0b => {
- match funct3 {
- 0b000 => {
- let imm = (instruction_u32 >> 20) & 0xfff;
- Some(terminate(imm.try_into().expect("exit code must be byte")))
- }
- 0b001 => {
- // keccak or poseidon
- None
- }
- 0b010 => {
- // u256
- todo!("Implement u256 transpiler");
- }
- _ => None,
+ CUSTOM_0 => match funct3 {
+ 0b000 => {
+ let imm = (instruction_u32 >> 20) & 0xfff;
+ Some(terminate(imm.try_into().expect("exit code must be byte")))
}
- }
- 0x2b => {
+ 0b001 => {
+ let rd = (instruction_u32 >> 7) & 0x1f;
+ let imm = (instruction_u32 >> 20) & 0xfff;
+ Some(Instruction::from_isize(
+ Rv32HintStoreOpcode::HINT_STOREW.with_default_offset(),
+ 0,
+ (RV32_REGISTER_NUM_LIMBS * rd as usize) as isize,
+ imm as isize,
+ 1,
+ 2,
+ ))
+ }
+ 0b011 => Some(Instruction::phantom(
+ PhantomInstruction::HintInputRv32,
+ F::zero(),
+ F::zero(),
+ 0,
+ )),
+ _ => unimplemented!(),
+ },
+ CUSTOM_1 => {
match funct3 {
Rv32ModularArithmeticOpcode::FUNCT3 => {
// mod operations
@@ -315,7 +325,6 @@ pub(crate) fn transpile(instructions_u32: &[u32]) -> Vec(PhantomData);
for instruction_u32 in instructions_u32 {
- // TODO: we probably want to forbid such instructions, but for now we just skip them
assert!(*instruction_u32 != 115, "ecall is not supported");
let instruction = process_instruction(&mut transpiler, *instruction_u32)
.unwrap_or_else(|| process_custom_instruction(*instruction_u32));
diff --git a/toolchain/riscv/transpiler/src/tests.rs b/toolchain/riscv/transpiler/src/tests.rs
index 756f57baa6..88e4c59856 100644
--- a/toolchain/riscv/transpiler/src/tests.rs
+++ b/toolchain/riscv/transpiler/src/tests.rs
@@ -12,6 +12,7 @@ use axvm_circuit::{
use axvm_platform::memory::MEM_SIZE;
use eyre::Result;
use p3_baby_bear::BabyBear;
+use p3_field::AbstractField;
use tempfile::tempdir;
use test_case::test_case;
@@ -91,6 +92,27 @@ fn test_rv32i_prove(examples_path: &str, min_segments: usize) -> Result<()> {
Ok(())
}
+#[test]
+fn test_rv32i_prove_with_hint() -> Result<()> {
+ let pkg = get_package(get_examples_dir().join("hint/program"));
+ let target_dir = tempdir()?;
+ let guest_opts = GuestOptions::default().into();
+ build_guest_package(&pkg, &target_dir, &guest_opts, None);
+ let elf_path = guest_methods(&pkg, &target_dir, &[]).pop().unwrap();
+ let config = VmConfig {
+ max_segment_len: (1 << 18) - 1,
+ ..VmConfig::rv32i()
+ };
+ let (_, exe) = setup_executor_from_elf(elf_path, config.clone())?;
+ air_test_with_min_segments(
+ config,
+ exe,
+ vec![[0, 1, 2, 3].map(F::from_canonical_u32).to_vec()],
+ 1,
+ );
+ Ok(())
+}
+
#[test_case("data/rv32im-intrin-from-as")]
fn test_intrinsic_runtime(elf_path: &str) -> Result<()> {
setup_tracing();
diff --git a/vm/src/system/phantom/mod.rs b/vm/src/system/phantom/mod.rs
index fcb440ffb0..462a433cf7 100644
--- a/vm/src/system/phantom/mod.rs
+++ b/vm/src/system/phantom/mod.rs
@@ -137,7 +137,7 @@ impl InstructionExecutor for PhantomChip {
let value = RefCell::borrow(&self.memory).unsafe_read_cell(addr_space, a);
println!("{}", value);
}
- PhantomInstruction::HintInput => {
+ PhantomInstruction::HintInput | PhantomInstruction::HintInputRv32 => {
let mut streams = self.streams.get().unwrap().lock();
let hint = match streams.input_stream.pop_front() {
Some(hint) => hint,
@@ -146,9 +146,18 @@ impl InstructionExecutor for PhantomChip {
}
};
streams.hint_stream.clear();
- streams
- .hint_stream
- .push_back(F::from_canonical_usize(hint.len()));
+ if phantom == PhantomInstruction::HintInputRv32 {
+ streams.hint_stream.extend(
+ (hint.len() as u32)
+ .to_le_bytes()
+ .iter()
+ .map(|b| F::from_canonical_u8(*b)),
+ );
+ } else {
+ streams
+ .hint_stream
+ .push_back(F::from_canonical_usize(hint.len()));
+ }
streams.hint_stream.extend(hint);
drop(streams);
}