#![recursion_limit = "2048"]
extern crate self as tasm_lib;
use std::collections::HashMap;
use std::io::Write;
use std::time::SystemTime;
use anyhow::bail;
use itertools::Itertools;
use library::Library;
use memory::dyn_malloc;
use num_traits::Zero;
use snippet_bencher::BenchmarkResult;
use triton_vm::isa::op_stack::NUM_OP_STACK_REGISTERS;
use triton_vm::prelude::*;
pub mod arithmetic;
pub mod array;
pub mod data_type;
pub mod exported_snippets;
pub mod hashing;
pub mod io;
pub mod library;
pub mod linker;
pub mod list;
pub mod memory;
pub mod mmr;
pub mod neptune;
pub mod other_snippets;
pub mod prelude;
pub mod rust_shadowing_helper_functions;
pub mod snippet_bencher;
pub mod structure;
pub mod test_helpers;
pub mod traits;
pub mod verifier;
pub use triton_vm;
use triton_vm::isa::instruction::AnInstruction;
use triton_vm::prelude::TableId;
pub use triton_vm::twenty_first;
use crate::test_helpers::prepend_program_with_stack_setup;
pub type VmHasher = Tip5;
pub type Digest = tip5::Digest;
#[derive(Clone, Debug, Default)]
pub struct InitVmState {
pub stack: Vec<BFieldElement>,
pub public_input: Vec<BFieldElement>,
pub nondeterminism: NonDeterminism,
pub sponge: Option<VmHasher>,
}
impl InitVmState {
pub fn with_stack(stack: Vec<BFieldElement>) -> Self {
InitVmState {
stack,
public_input: vec![],
nondeterminism: NonDeterminism::default(),
sponge: None,
}
}
pub fn with_stack_and_memory(
stack: Vec<BFieldElement>,
memory: HashMap<BFieldElement, BFieldElement>,
) -> Self {
InitVmState {
stack,
public_input: vec![],
nondeterminism: NonDeterminism::default().with_ram(memory),
sponge: None,
}
}
}
#[derive(Clone, Debug)]
pub struct RustShadowOutputState {
pub public_output: Vec<BFieldElement>,
pub stack: Vec<BFieldElement>,
pub ram: HashMap<BFieldElement, BFieldElement>,
pub sponge: Option<VmHasher>,
}
pub fn empty_stack() -> Vec<BFieldElement> {
vec![BFieldElement::zero(); NUM_OP_STACK_REGISTERS]
}
pub fn push_encodable<T: BFieldCodec>(stack: &mut Vec<BFieldElement>, value: &T) {
stack.extend(value.encode().into_iter().rev());
}
pub fn execute_bench_deprecated(
code: &[LabelledInstruction],
stack: &mut Vec<BFieldElement>,
expected_stack_diff: isize,
std_in: Vec<BFieldElement>,
nondeterminism: NonDeterminism,
) -> anyhow::Result<BenchmarkResult> {
let init_stack = stack.to_owned();
let initial_stack_height = init_stack.len() as isize;
let public_input = PublicInput::new(std_in.clone());
let program = Program::new(code);
let mut vm_state = VMState::new(program.clone(), public_input, nondeterminism.clone());
vm_state.op_stack.stack.clone_from(&init_stack);
let (simulation_trace, terminal_state) = VM::trace_execution_of_state(vm_state)?;
let jump_stack = &terminal_state.jump_stack;
if !jump_stack.is_empty() {
bail!("Jump stack must be unchanged after code execution but was {jump_stack:?}")
}
let final_stack_height = terminal_state.op_stack.stack.len() as isize;
if expected_stack_diff != final_stack_height - initial_stack_height {
bail!(
"Code must grow/shrink stack with expected number of elements.\n
init height: {initial_stack_height}\n
end height: {final_stack_height}\n
expected difference: {expected_stack_diff}\n\n
final stack: {}",
terminal_state.op_stack.stack.iter().join(",")
)
}
if std::env::var("DYING_TO_PROVE").is_ok() {
prove_and_verify(program, &std_in, &nondeterminism, Some(init_stack));
}
stack.clone_from(&terminal_state.op_stack.stack);
Ok(BenchmarkResult::new(&simulation_trace))
}
#[allow(clippy::too_many_arguments)]
pub fn execute_test(
code: &[LabelledInstruction],
stack: &mut Vec<BFieldElement>,
expected_stack_diff: isize,
std_in: Vec<BFieldElement>,
nondeterminism: NonDeterminism,
maybe_sponge: Option<VmHasher>,
) -> VMState {
let init_stack = stack.to_owned();
let public_input = PublicInput::new(std_in.clone());
let program = Program::new(code);
let mut vm_state = VMState::new(
program.clone(),
public_input.clone(),
nondeterminism.clone(),
);
vm_state.op_stack.stack.clone_from(&init_stack);
vm_state.sponge = maybe_sponge;
maybe_write_debuggable_vm_state_to_disk(&vm_state);
if let Err(err) = vm_state.run() {
panic!("{err}\n\nFinal state was: {vm_state}")
}
let terminal_state = vm_state;
if !terminal_state.jump_stack.is_empty() {
panic!("Jump stack must be unchanged after code execution");
}
let final_stack_height = terminal_state.op_stack.stack.len() as isize;
let initial_stack_height = init_stack.len();
assert_eq!(
expected_stack_diff,
final_stack_height - initial_stack_height as isize,
"Code must grow/shrink stack with expected number of elements.\n
init height: {initial_stack_height}\n
end height: {final_stack_height}\n
expected difference: {expected_stack_diff}\n\n
initial stack: {}\n
final stack: {}",
init_stack.iter().skip(NUM_OP_STACK_REGISTERS).join(","),
terminal_state
.op_stack
.stack
.iter()
.skip(NUM_OP_STACK_REGISTERS)
.join(","),
);
if std::env::var("DYING_TO_PROVE").is_ok() {
prove_and_verify(program, &std_in, &nondeterminism, Some(init_stack));
}
stack.clone_from(&terminal_state.op_stack.stack);
terminal_state
}
#[deprecated(
since = "0.3.0",
note = "\
Use `maybe_write_debuggable_vm_state_to_disk` instead. Explanation: \
The program is now included in the VM's state and no longer needed separately.\
"
)]
pub fn maybe_write_debuggable_program_to_disk(program: &Program, vm_state: &VMState) {
let Ok(_) = std::env::var("TASMLIB_TRITON_TUI") else {
return;
};
let mut program_file = std::fs::File::create("program.tasm").unwrap();
write!(program_file, "{program}").unwrap();
maybe_write_debuggable_vm_state_to_disk(vm_state);
}
pub fn maybe_write_debuggable_vm_state_to_disk(vm_state: &VMState) {
let Ok(_) = std::env::var("TASMLIB_TRITON_TUI") else {
return;
};
let mut state_file = std::fs::File::create("vm_state.json").unwrap();
let state = serde_json::to_string(&vm_state).unwrap();
write!(state_file, "{state}").unwrap();
}
pub fn execute_with_terminal_state(
program: Program,
std_in: &[BFieldElement],
stack: &[BFieldElement],
nondeterminism: &NonDeterminism,
maybe_sponge: Option<VmHasher>,
) -> Result<VMState, InstructionError> {
let public_input = PublicInput::new(std_in.into());
let mut vm_state = VMState::new(program, public_input, nondeterminism.to_owned());
stack.clone_into(&mut vm_state.op_stack.stack);
vm_state.sponge = maybe_sponge;
maybe_write_debuggable_vm_state_to_disk(&vm_state);
match vm_state.run() {
Ok(()) => {
println!("Triton VM execution successful.");
Ok(vm_state)
}
Err(err) => {
if let Some(ref sponge) = vm_state.sponge {
println!("tasm final sponge:");
println!("{}", sponge.state.iter().join(", "));
}
println!("Triton VM execution failed. Final state:\n{vm_state}");
Err(err)
}
}
}
pub fn prove_and_verify(
program: Program,
std_in: &[BFieldElement],
nondeterminism: &NonDeterminism,
init_stack: Option<Vec<BFieldElement>>,
) {
let labelled_instructions = program.labelled_instructions();
let timing_report_label = match labelled_instructions.first().unwrap() {
LabelledInstruction::Instruction(AnInstruction::Call(func)) => func,
LabelledInstruction::Label(label) => label,
_ => "Some program",
};
let program = match &init_stack {
Some(init_stack) => prepend_program_with_stack_setup(init_stack, &program),
None => program,
};
let claim = Claim::about_program(&program).with_input(std_in.to_owned());
let (aet, public_output) = VM::trace_execution(
program.clone(),
PublicInput::new(std_in.to_owned()),
nondeterminism.clone(),
)
.unwrap();
let claim = claim.with_output(public_output);
let stark = Stark::default();
let tick = SystemTime::now();
triton_vm::profiler::start(timing_report_label);
let proof = stark.prove(&claim, &aet).unwrap();
let profile = triton_vm::profiler::finish();
let measured_time = tick.elapsed().expect("Don't mess with time");
let padded_height = proof.padded_height().unwrap();
let fri = stark.fri(padded_height).unwrap();
let report = profile
.with_cycle_count(aet.processor_trace.nrows())
.with_padded_height(padded_height)
.with_fri_domain_len(fri.domain.length);
println!("{report}");
println!("Done proving. Elapsed time: {:?}", measured_time);
println!(
"Proof was generated from:
table lengths:
processor table: {}
hash table: {}
u32 table: {}
op-stack table: {}
RAM table: {}
Program table: {}
Cascade table: {}
Lookup table: {}",
aet.height_of_table(TableId::Processor),
aet.height_of_table(TableId::Hash),
aet.height_of_table(TableId::U32),
aet.height_of_table(TableId::OpStack),
aet.height_of_table(TableId::Ram),
aet.height_of_table(TableId::Program),
aet.height_of_table(TableId::Cascade),
aet.height_of_table(TableId::Lookup),
);
assert!(
triton_vm::verify(stark, &claim, &proof),
"Generated proof must verify for program:\n{program}",
);
}
pub fn generate_full_profile(
name: &str,
program: Program,
public_input: &PublicInput,
nondeterminism: &NonDeterminism,
) -> String {
let (_output, profile) =
VM::profile(program, public_input.clone(), nondeterminism.clone()).unwrap();
format!("{name}:\n{profile}")
}