use std::collections::HashMap;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;
use num_traits::ConstZero;
use num_traits::Zero;
use triton_vm::isa::instruction::AnInstruction;
use triton_vm::isa::op_stack::NUM_OP_STACK_REGISTERS;
use triton_vm::prelude::*;
use crate::prelude::*;
use crate::push_encodable;
pub trait BasicSnippet {
fn parameters(&self) -> Vec<(DataType, String)>;
fn return_values(&self) -> Vec<(DataType, String)>;
fn entrypoint(&self) -> String;
fn code(&self, library: &mut Library) -> Vec<LabelledInstruction>;
fn annotated_code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
fn generate_hints_for_input_values(inputs: Vec<(DataType, String)>) -> Vec<String> {
let mut input_hints = vec![];
let mut stack_depth = 0;
for (data_type, name) in inputs.into_iter().rev() {
let stack_size = data_type.stack_size();
if stack_size.is_zero() {
continue;
}
let data_name = data_type.label_friendly_name();
let name = name
.replace(|c: char| !c.is_alphanumeric(), "_")
.to_ascii_lowercase();
input_hints.push(format!(
"hint {name}: {data_name} = stack[{stack_depth}..{}]",
stack_depth + stack_size
));
stack_depth += stack_size;
}
input_hints
}
let code = self.code(library);
let Some((entrypoint, snippet_body)) = code.split_first() else {
return code;
};
let entrypoint = entrypoint.to_string();
let observed_entrypoint = entrypoint.trim_end_matches(':');
if *observed_entrypoint != self.entrypoint() {
return code;
}
let input_hints = generate_hints_for_input_values(self.parameters());
triton_asm! {
{observed_entrypoint}:
{&input_hints}
{&snippet_body}
}
}
#[cfg(test)]
fn link_for_isolated_run_populated_static_memory(
&self,
words_statically_allocated: u32,
) -> Vec<LabelledInstruction> {
let mut library = Library::with_preallocated_memory(words_statically_allocated);
let entrypoint = self.entrypoint();
let function_body = self.annotated_code(&mut library);
let library_code = library.all_imports();
let code = triton_asm!(
call {entrypoint}
halt
{&function_body}
{&library_code}
);
code
}
#[doc(hidden)]
fn link_for_isolated_run(&self) -> Vec<LabelledInstruction> {
let mut library = Library::empty();
let entrypoint = self.entrypoint();
let function_body = self.annotated_code(&mut library);
let library_code = library.all_imports();
let code = triton_asm!(
call {entrypoint}
halt
{&function_body}
{&library_code}
);
code
}
#[doc(hidden)]
fn init_stack_for_isolated_run(&self) -> Vec<BFieldElement> {
let code = self.link_for_isolated_run();
let program = Program::new(&code);
let mut stack = vec![];
push_encodable(&mut stack, &program.hash());
stack.resize(NUM_OP_STACK_REGISTERS, BFieldElement::ZERO);
stack
}
fn stack_diff(&self) -> isize {
let io_size = |io: Vec<(DataType, _)>| -> isize {
let size = io.into_iter().map(|(ty, _)| ty.stack_size()).sum::<usize>();
size.try_into().unwrap()
};
io_size(self.return_values()) - io_size(self.parameters())
}
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
HashMap::default()
}
}
pub trait SignedOffSnippet: BasicSnippet {
fn fingerprint(&self) -> SignOffFingerprint {
let mut hasher = std::hash::DefaultHasher::new();
triton_vm::proof::CURRENT_VERSION.hash(&mut hasher);
for instruction in self.code(&mut Library::new()) {
let LabelledInstruction::Instruction(instruction) = instruction else {
continue;
};
if let AnInstruction::Call(_) = instruction {
AnInstruction::Call("").opcode().hash(&mut hasher);
} else {
instruction.hash(&mut hasher)
}
}
SignOffFingerprint(hasher.finish())
}
fn assert_all_sign_offs_are_up_to_date(&self) {
let fingerprint = self.fingerprint();
let mut out_of_date_sign_offs = self
.sign_offs()
.into_iter()
.filter(|(_, fp)| fp != &fingerprint)
.peekable();
if out_of_date_sign_offs.peek().is_none() {
return;
}
let name = self.entrypoint();
for (reviewer, fp) in out_of_date_sign_offs {
eprintln!("reviewer {reviewer} of snippet “{name}” has signed off on fingerprint {fp}")
}
panic!("A sign-off is out of date. Current fingerprint of “{name}”: {fingerprint}");
}
}
impl<T: BasicSnippet + ?Sized> SignedOffSnippet for T {}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Reviewer(pub &'static str);
impl Display for Reviewer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SignOffFingerprint(pub(crate) u64);
impl Display for SignOffFingerprint {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:x}", self.0)
}
}
impl From<u64> for SignOffFingerprint {
fn from(value: u64) -> Self {
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! dummy_snippet {
($name:ident: $($instr:tt)+) => {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct $name;
impl BasicSnippet for $name {
fn parameters(&self) -> Vec<(DataType, String)> { vec![] }
fn return_values(&self) -> Vec<(DataType, String)> { vec![] }
fn entrypoint(&self) -> String {
stringify!($name).to_ascii_lowercase()
}
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
triton_asm!($($instr)+)
}
}
};
}
dummy_snippet!(DummySnippet: dummysnippet: push 14 push 14 pop 2 return);
#[test]
fn init_stack_agrees_with_tvm() {
let calculated_init_stack = DummySnippet.init_stack_for_isolated_run();
let program = DummySnippet.link_for_isolated_run();
let program = Program::new(&program);
let init_vm_state = VMState::new(program, Default::default(), Default::default());
assert_eq!(init_vm_state.op_stack.stack, calculated_init_stack);
}
#[test]
fn defined_traits_are_dyn_compatible() {
fn basic_snippet_is_dyn_compatible(snippet: Box<dyn BasicSnippet>) {
snippet.fingerprint();
}
fn signed_off_snippet_is_dyn_compatible(snippet: Box<dyn SignedOffSnippet>) {
snippet.fingerprint();
}
basic_snippet_is_dyn_compatible(Box::new(DummySnippet));
signed_off_snippet_is_dyn_compatible(Box::new(DummySnippet));
}
#[test]
fn call_targets_dont_influence_snippet_fingerprints() {
dummy_snippet!(SomeLabel: call some_label);
dummy_snippet!(OtherLabel: call other_label);
assert_eq!(SomeLabel.fingerprint(), OtherLabel.fingerprint());
}
#[test]
fn instruction_arguments_do_influence_snippet_fingerprints() {
dummy_snippet!(Push20: push 20);
dummy_snippet!(Push42: push 42);
assert_ne!(Push20.fingerprint(), Push42.fingerprint());
}
}