Skip to content
Open
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: 2 additions & 0 deletions riscv_transpiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ seq-macro = { workspace = true }
serde-big-array = "*"
keccak = "*"

object = "*"

dynasmrt = { version = "4", optional = true }
riscv-decode = {version = "0.2", optional = true }
capstone = {version = "0.13", optional = true }
Expand Down
1 change: 1 addition & 0 deletions riscv_transpiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
pub mod ir;
pub mod jit;
pub mod replayer;
pub mod riscof;
pub mod vm;
pub mod witness;

Expand Down
181 changes: 181 additions & 0 deletions riscv_transpiler/src/riscof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//! RISCOF (RISC-V Compliance Framework) integration for running compliance tests.

use std::path::Path;

use object::{Object, ObjectSymbol};

use crate::ir::{preprocess_bytecode, FullMachineDecoderConfig, Instruction};
use crate::vm::{DelegationsCounters, RamPeek, RamWithRomRegion, Register, SimpleTape, State, VM};

/// Default entry point for RISCOF tests.
pub const DEFAULT_ENTRY_POINT: u32 = 0x0100_0000;

const ROM_SECOND_WORD_BITS: usize = common_constants::rom::ROM_SECOND_WORD_BITS;
const MEMORY_SIZE: usize = 1 << 30;

/// Run a RISCOF compliance test binary and extract signatures to the given path.
pub fn run_with_riscof_signature_extraction(
binary: &[u8],
elf_data: &[u8],
signature_path: &Path,
max_cycles: usize,
entry_point: u32,
) {
let ram = execute_binary(binary, max_cycles, entry_point);

match find_signature_bounds(elf_data) {
Some((begin, end)) => {
let signatures = collect_signatures(&ram, begin, end);
write_signatures(&signatures, signature_path);
}
None => {
use std::io::Write;
let mut file =
std::fs::File::create(signature_path).expect("must create signature file");
writeln!(file, "begin_signature or end_signature symbol not found in ELF")
.expect("must write to signature file");
}
}
}

fn execute_binary(
binary: &[u8],
max_cycles: usize,
entry_point: u32,
) -> RamWithRomRegion<ROM_SECOND_WORD_BITS> {
let binary_words = bytes_to_words(binary);
let entry_offset = (entry_point / 4) as usize;
let total_words = entry_offset + binary_words.len();

let mut padded_instructions = vec![0u32; total_words];
padded_instructions[entry_offset..].copy_from_slice(&binary_words);

let instructions: Vec<Instruction> =
preprocess_bytecode::<FullMachineDecoderConfig>(&padded_instructions);
let tape = SimpleTape::new(&instructions);

let ram_words = MEMORY_SIZE / core::mem::size_of::<u32>();
let mut backing = vec![
Register {
value: 0,
timestamp: 0
};
ram_words
];
for (i, &word) in binary_words.iter().enumerate() {
backing[entry_offset + i].value = word;
}
let mut ram = RamWithRomRegion::<ROM_SECOND_WORD_BITS> { backing };

let mut state = State::initial_with_counters(DelegationsCounters::default());
state.pc = entry_point;

VM::<DelegationsCounters>::run_basic_unrolled(
&mut state,
&mut ram,
&mut (),
&tape,
max_cycles,
&mut (),
);

ram
}

fn bytes_to_words(bytes: &[u8]) -> Vec<u32> {
let padded_len = (bytes.len() + 3) / 4 * 4;
let mut padded = bytes.to_vec();
padded.resize(padded_len, 0);

padded
.as_chunks::<4>()
.0
.iter()
.map(|el| u32::from_le_bytes(*el))
.collect()
}

fn find_signature_bounds(elf_data: &[u8]) -> Option<(u64, u64)> {
let elf = object::File::parse(elf_data).expect("must parse ELF file");

let mut begin = None;
let mut end = None;

for symbol in elf.symbols() {
if let Ok(name) = symbol.name() {
if name == "begin_signature" {
begin = Some(symbol.address());
} else if name == "end_signature" {
end = Some(symbol.address());
}
if begin.is_some() && end.is_some() {
break;
}
}
}

match (begin, end) {
(Some(b), Some(e)) => Some((b, e)),
_ => None,
}
}

fn collect_signatures(
ram: &RamWithRomRegion<ROM_SECOND_WORD_BITS>,
begin: u64,
end: u64,
) -> Vec<u32> {
assert!(begin <= end, "begin_signature > end_signature");
assert!(begin % 4 == 0, "begin_signature not 4-byte aligned");
assert!(end % 4 == 0, "end_signature not 4-byte aligned");

let word_count = ((end - begin) / 4) as usize;
let mut signatures = Vec::with_capacity(word_count);

let mut addr = begin as u32;
let end_addr = end as u32;

while addr < end_addr {
let word = ram.peek_word(addr);
signatures.push(word);
addr += 4;
}

signatures
}

fn write_signatures(signatures: &[u32], path: &Path) {
use std::io::Write;

let mut file = std::fs::File::create(path).expect("must create signature file");

for &sig in signatures {
writeln!(file, "{:08x}", sig).expect("must write to signature file");
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_write_signatures() {
use std::io::Read;

let signatures = vec![0xdeadbeef, 0x12345678, 0x00000001];
let temp_dir = std::env::temp_dir();
let path = temp_dir.join("test_signatures.txt");

write_signatures(&signatures, &path);

let mut content = String::new();
std::fs::File::open(&path)
.expect("open should succeed")
.read_to_string(&mut content)
.expect("read should succeed");

assert_eq!(content, "deadbeef\n12345678\n00000001\n");

std::fs::remove_file(&path).ok();
}
}
1 change: 0 additions & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
[toolchain]
channel = "nightly"

47 changes: 46 additions & 1 deletion tools/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use cli_lib::vk::generate_vk;
use execution_utils::{Machine, ProgramProof, RecursionStrategy, VerifierCircuitsIdentifiers};
use reqwest::blocking::Client;
use serde_json::Value;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::{fs, io::Write, iter};

use prover::{
Expand Down Expand Up @@ -153,6 +153,22 @@ enum Commands {
machine: Machine,
},

/// Run a binary for RISCOF compliance testing (extracts begin_signature/end_signature).
RunForRiscof {
/// Binary file to execute
#[arg(short, long)]
bin: String,
/// ELF file for extracting signature symbols
#[arg(long)]
elf: String,
/// Output path for signature file
#[arg(long)]
signatures: PathBuf,
/// Number of riscV cycles to run.
#[arg(long)]
cycles: usize,
},

/// Generates verification key hash, for a given binary.
/// This way you can compare it with the one inside the proof, to make sure that
/// the proof is really checking the execution of a given code.
Expand Down Expand Up @@ -387,6 +403,14 @@ fn main() {

run_binary(bin, cycles, input_data, expected_results, machine);
}
Commands::RunForRiscof {
bin,
elf,
signatures,
cycles,
} => {
run_for_riscof_binary(&bin, &elf, &signatures, *cycles);
}
Commands::GenerateVk {
bin,
machine,
Expand Down Expand Up @@ -673,6 +697,27 @@ fn u32_to_file(output_file: &String, numbers: &[u32]) {
println!("Successfully wrote to file: {}", output_file);
}

fn run_for_riscof_binary(
bin_path: &String,
elf_path: &String,
signatures: &Path,
cycles: usize,
) {
use riscv_transpiler::riscof;

let binary = fs::read(bin_path).expect("Failed to read binary file");
let elf_data = fs::read(elf_path).expect("Failed to read ELF file");

riscof::run_with_riscof_signature_extraction(
&binary,
&elf_data,
signatures,
cycles,
riscof::DEFAULT_ENTRY_POINT,
);
println!("Signature file written to: {}", signatures.display());
}

fn run_binary(
bin_path: &String,
cycles: &Option<usize>,
Expand Down