Skip to main content

Overview

AdviceWriter provides low-level access to write data to the advice tape during the compute_advice phase of a zkVM program. The advice tape allows the guest program to send non-deterministic inputs to the prover that can later be read during the proving phase.

Type Definition

pub struct AdviceWriter;

Methods

get

Returns a reference to the global advice writer.
pub fn get() -> Self
Returns: An AdviceWriter instance Example:
use jolt_sdk::AdviceWriter;

let mut writer = AdviceWriter::get();

write_u8

Writes a single byte (u8) to the advice tape.
pub fn write_u8(&mut self, value: u8)
value
u8
required
The byte value to write
Example:
let mut writer = AdviceWriter::get();
writer.write_u8(42);

write_u16

Writes a halfword (2 bytes, u16) to the advice tape in little-endian format.
pub fn write_u16(&mut self, value: u16)
value
u16
required
The u16 value to write
Example:
let mut writer = AdviceWriter::get();
writer.write_u16(1234);

write_u32

Writes a word (4 bytes, u32) to the advice tape in little-endian format.
pub fn write_u32(&mut self, value: u32)
value
u32
required
The u32 value to write
Example:
let mut writer = AdviceWriter::get();
writer.write_u32(123456);

write_u64

Writes a doubleword (8 bytes, u64) to the advice tape in little-endian format.
pub fn write_u64(&mut self, value: u64)
value
u64
required
The u64 value to write
Example:
let mut writer = AdviceWriter::get();
writer.write_u64(9876543210);

Usage Example

Basic Writing

use jolt_sdk::AdviceWriter;

#[jolt::advice]
fn write_advice_data() {
    let mut writer = AdviceWriter::get();
    
    writer.write_u8(255);
    writer.write_u16(65535);
    writer.write_u32(4294967295);
    writer.write_u64(18446744073709551615);
}

Writing Complex Types

For writing complex types, use the AdviceTapeIO trait:
use jolt_sdk::AdviceTapeIO;

#[jolt::advice]
fn write_complex_data() {
    // Automatically writes length, capacity, and data
    let vec_data = vec![1u32, 2, 3, 4, 5];
    vec_data.write_to_advice_tape();
    
    // Write a tuple
    let tuple_data = (42u64, 123u32, 456u16);
    tuple_data.write_to_advice_tape();
    
    // Write an array
    let array_data = [1u64, 2, 3, 4];
    array_data.write_to_advice_tape();
}

Complete Advice Flow

use jolt_sdk::{AdviceWriter, AdviceReader, AdviceTapeIO};

#[jolt::advice]
fn compute_advice() {
    let mut writer = AdviceWriter::get();
    
    // Write some computed values
    let result = expensive_computation();
    writer.write_u64(result);
}

#[jolt::provable]
fn use_advice() -> u64 {
    let mut reader = AdviceReader::get();
    
    // Read the precomputed value
    let result = reader.read_u64();
    
    // Verify it's correct
    check_advice_eq!(result, expensive_computation());
    
    result
}

fn expensive_computation() -> u64 {
    // Some expensive computation
    42
}

Platform Support

The following table shows platform-specific behavior:
TargetSupportedNotes
riscv32YesUses custom RISC-V instructions
riscv64YesUses custom RISC-V instructions
OthersNoAll methods panic
Advice tape I/O is only supported on RISC-V targets. Attempting to use AdviceWriter on non-RISC-V platforms will result in a panic.

Implementation Details

RISC-V Custom Instructions

AdviceWriter uses the VirtualHostIO custom instruction (opcode 0x5B, funct3 2) to write bytes to the advice tape. The internal write_bytes method handles the actual I/O:
// See jolt-sdk/src/lib.rs:188
core::arch::asm!(
    ".insn i 0x5B, 2, x0, x0, 0",
    in("a0") JOLT_ADVICE_WRITE_CALL_ID,
    in("a1") src_ptr,
    in("a2") len,
    options(nostack, preserves_flags)
);

Data Format

All multi-byte values are written in little-endian format:
  • write_u16(0x1234) writes bytes [0x34, 0x12]
  • write_u32(0x12345678) writes bytes [0x78, 0x56, 0x34, 0x12]
  • write_u64(0x0123456789ABCDEF) writes bytes [0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01]

Best Practices

Instead of manually writing each field of a struct or vector, use the AdviceTapeIO trait which handles serialization automatically:
// Good
my_vec.write_to_advice_tape();

// Avoid
writer.write_u64(my_vec.len() as u64);
for item in &my_vec {
    writer.write_u32(*item);
}
Ensure that the order and types of writes in compute_advice match the reads in your provable function:
#[jolt::advice]
fn compute() {
    writer.write_u32(42);
    writer.write_u64(1000);
}

#[jolt::provable]
fn prove() {
    let a = reader.read_u32(); // Matches write_u32
    let b = reader.read_u64(); // Matches write_u64
}
Always verify values read from the advice tape using check_advice! or check_advice_eq! to ensure correctness:
let hint = reader.read_u64();
let actual = compute_expected_value();
check_advice_eq!(hint, actual);

See Also

Build docs developers (and LLMs) love