tasm_lib/traits/algorithm.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use rand::prelude::*;
use triton_vm::prelude::*;
use super::basic_snippet::BasicSnippet;
use super::rust_shadow::RustShadow;
use crate::linker::execute_bench;
use crate::linker::link_for_isolated_run;
use crate::snippet_bencher::write_benchmarks;
use crate::snippet_bencher::BenchmarkCase;
use crate::snippet_bencher::NamedBenchmarkResult;
use crate::test_helpers::test_rust_equivalence_given_complete_state;
use crate::InitVmState;
use crate::VmHasher;
/// An Algorithm can modify memory and take ND_input.
///
/// An Algorithm is a piece of tasm code that can modify memory even at addresses below
/// the dynamic memory allocator, and can take nondeterministic input. It cannot read from
/// standard in or write to standard out.
///
/// See also: [closure], [function], [procedure], [accessor], [mem_preserver],
/// [read_only_algorithm]
///
/// [closure]: crate::traits::closure::Closure
/// [function]: crate::traits::function::Function
/// [procedure]: crate::traits::procedure::Procedure
/// [accessor]: crate::traits::accessor::Accessor
/// [mem_preserver]: crate::traits::mem_preserver::MemPreserver
/// [read_only_algorithm]: crate::traits::read_only_algorithm::ReadOnlyAlgorithm
pub trait Algorithm: BasicSnippet {
fn rust_shadow(
&self,
stack: &mut Vec<BFieldElement>,
memory: &mut HashMap<BFieldElement, BFieldElement>,
nondeterminism: &NonDeterminism,
);
/// Take a object about which something is being proven in order to extract out the
/// right nondeterminism. Update the mutably referenced non-determism argument.
///
/// For example:
/// - When proving the correct verification of a proof, you might want to pull all
/// digests out of the authentication structures and put them in the `digests`
/// field of the non-determinism. This way the VM can avoid the processing
/// required by authentication structures and just divine in the right digests
/// as it walks up the Merkle trees.
/// - When proving the correct sorting of a list, the VM ought to avoid running a
/// sorting algorithm; instead it should divine the sorted list and then prove
/// that the two lists are equal. The preprocessing step in this case would take
/// the unsorted list, sort it, and use the sorted list to populate the non-
/// determinism.
/// - When verifying a Falcon signature, at some point the NTT of a vector is needed.
/// The VM should not compute the NTT because expensive; instead it should divine
/// the NTT-transformed vector and verify that it satisfies the right relation. In
/// this case the preprocessor calculates the NTT and populates the non-determinism
/// with the transformed vector.
fn preprocess<T: BFieldCodec>(_meta_input: T, _nondeterminism: &mut NonDeterminism) {}
fn pseudorandom_initial_state(
&self,
seed: [u8; 32],
bench_case: Option<BenchmarkCase>,
) -> AlgorithmInitialState;
fn corner_case_initial_states(&self) -> Vec<AlgorithmInitialState> {
vec![]
}
}
#[derive(Debug, Clone, Default)]
pub struct AlgorithmInitialState {
pub stack: Vec<BFieldElement>,
pub nondeterminism: NonDeterminism,
}
impl From<AlgorithmInitialState> for InitVmState {
fn from(value: AlgorithmInitialState) -> Self {
Self {
stack: value.stack,
nondeterminism: value.nondeterminism,
..Default::default()
}
}
}
pub struct ShadowedAlgorithm<T: Algorithm + 'static> {
algorithm: Rc<RefCell<T>>,
}
impl<T: Algorithm + 'static> ShadowedAlgorithm<T> {
pub fn new(algorithm: T) -> Self {
Self {
algorithm: Rc::new(RefCell::new(algorithm)),
}
}
}
impl<T> RustShadow for ShadowedAlgorithm<T>
where
T: Algorithm + 'static,
{
fn inner(&self) -> Rc<RefCell<dyn BasicSnippet>> {
self.algorithm.clone()
}
fn rust_shadow_wrapper(
&self,
_stdin: &[BFieldElement],
nondeterminism: &NonDeterminism,
stack: &mut Vec<BFieldElement>,
memory: &mut HashMap<BFieldElement, BFieldElement>,
_sponge: &mut Option<VmHasher>,
) -> Vec<BFieldElement> {
self.algorithm
.borrow()
.rust_shadow(stack, memory, nondeterminism);
vec![]
}
fn test(&self) {
for corner_case in self
.algorithm
.borrow()
.corner_case_initial_states()
.into_iter()
{
let stdin = vec![];
test_rust_equivalence_given_complete_state(
self,
&corner_case.stack,
&stdin,
&corner_case.nondeterminism,
&None,
None,
);
}
let num_states = 10;
let seed: [u8; 32] = thread_rng().gen();
let mut rng: StdRng = SeedableRng::from_seed(seed);
for _ in 0..num_states {
let seed: [u8; 32] = rng.gen();
let AlgorithmInitialState {
stack,
nondeterminism,
} = self
.algorithm
.borrow()
.pseudorandom_initial_state(seed, None);
let stdin = vec![];
test_rust_equivalence_given_complete_state(
self,
&stack,
&stdin,
&nondeterminism,
&None,
None,
);
}
}
fn bench(&self) {
let mut rng: StdRng = SeedableRng::from_seed(
hex::decode("73a24b6b8b32e4d7d563a4d9a85f476573a24b6b8b32e4d7d563a4d9a85f4765")
.unwrap()
.try_into()
.unwrap(),
);
let mut benchmarks = Vec::with_capacity(2);
for bench_case in [BenchmarkCase::CommonCase, BenchmarkCase::WorstCase] {
let AlgorithmInitialState {
stack,
nondeterminism,
} = self
.algorithm
.borrow()
.pseudorandom_initial_state(rng.gen(), Some(bench_case));
let program = link_for_isolated_run(self.algorithm.clone());
let benchmark = execute_bench(&program, &stack, vec![], nondeterminism, None);
let benchmark = NamedBenchmarkResult {
name: self.algorithm.borrow().entrypoint(),
benchmark_result: benchmark,
case: bench_case,
};
benchmarks.push(benchmark);
}
write_benchmarks(benchmarks);
}
}