triton_vm/lib.rs
1//! Triton Virtual Machine is a Zero-Knowledge Proof System (ZKPS) for proving
2//! correct execution of programs written in Triton assembly. The proof system
3//! is a [zk-STARK](Stark), which is a state-of-the-art ZKPS.
4//!
5//! Generally, all arithmetic performed by Triton VM happens in the prime field
6//! with 2^64 - 2^32 + 1 elements. Instructions for u32 operations are provided.
7//!
8//! For a full overview over all available instructions and their effects, see
9//! the [specification](https://triton-vm.org/spec/instructions.html).
10//!
11//! [Triton VM's STARK](Stark) is parametric, but it is highly recommended to
12//! use the provided [default](Stark::default). Furthermore, certain runtime
13//! characteristics are [configurable](config), and usually don't need changing.
14//!
15//! # Non-Determinism
16//!
17//! Triton VM is a non-deterministic machine. That is,
18//! 1. Triton VM's random access memory can be initialized arbitrarily, and
19//! 1. for a select few instructions (namely `divine` and `merkle_step`),
20//! correct state transition is not fully determined by the current state and
21//! Triton VM's public input.
22//!
23//! The input for those non-deterministic instructions use dedicated input
24//! streams. Those, together with the initial RAM, are collectively called
25//! [`NonDeterminism`].
26//!
27//! # Examples
28//!
29//! Below are a few examples on how to use Triton VM. They show the instruction
30//! set architecture in action and highlight the core methods required to
31//! generate & verify a proof of correct execution. Some of these are
32//! convenience function [`prove_program()`] as well as the [`prove()`] and
33//! [`verify()`] methods.
34//!
35//! ## Factorial
36//!
37//! Compute the factorial of the given public input.
38//!
39//! The execution of the factorial program is already fully determined by the
40//! public input. Hence, in this case, there is no need for specifying
41//! non-determinism. Keep reading for an example that does use non-determinism.
42//!
43//! The [`triton_program!`] macro is used to conveniently write Triton assembly.
44//! In below example, the state of the operational stack is shown as a comment
45//! after most instructions.
46//!
47//! ```
48//! # use triton_vm::prelude::*;
49//! let factorial_program = triton_program!(
50//! read_io 1 // n
51//! push 1 // n 1
52//! call factorial // 0 n!
53//! write_io 1 // 0
54//! halt
55//!
56//! factorial: // n acc
57//! // if n == 0: return
58//! dup 1 // n acc n
59//! push 0 eq // n acc n==0
60//! skiz // n acc
61//! return // 0 acc
62//! // else: multiply accumulator with n and recurse
63//! dup 1 // n acc n
64//! mul // n acc·n
65//! swap 1 // acc·n n
66//! push -1 add // acc·n n-1
67//! swap 1 // n-1 acc·n
68//! recurse
69//! );
70//! let public_input = PublicInput::new(bfe_vec![10]);
71//! let non_determinism = NonDeterminism::default();
72//!
73//! let (stark, claim, proof) =
74//! triton_vm::prove_program(factorial_program, public_input, non_determinism).unwrap();
75//!
76//! let verdict = triton_vm::verify(stark, &claim, &proof);
77//! assert!(verdict);
78//!
79//! assert_eq!(1, claim.output.len());
80//! assert_eq!(3_628_800, claim.output[0].value());
81//! ```
82//!
83//! ## Non-Determinism
84//!
85//! In the following example, a public field elements equality to the sum of
86//! some squared secret elements is proven. For demonstration purposes, some of
87//! the secret elements come from secret in, and some are read from RAM, which
88//! can be initialized arbitrarily.
89//!
90//! Note that the non-determinism is not required for proof verification, and
91//! does not appear in the claim or the proof. It is only used for proof
92//! generation. This way, the verifier can be convinced that the prover did
93//! indeed know some input that satisfies the claim, but learns nothing beyond
94//! that fact.
95//!
96//! The third potential source of non-determinism is intended for verifying
97//! Merkle authentication paths. It is not used in this example. See
98//! [`NonDeterminism`] for more information.
99//!
100//! ```
101//! # use triton_vm::prelude::*;
102//! let sum_of_squares_program = triton_program!(
103//! read_io 1 // n
104//! call sum_of_squares_secret_in // n sum_1
105//! call sum_of_squares_ram // n sum_1 sum_2
106//! add // n sum_1+sum_2
107//! eq // n==(sum_1+sum_2)
108//! assert // abort the VM if n!=(sum_1+sum_2)
109//! halt
110//!
111//! sum_of_squares_secret_in:
112//! divine 1 dup 0 mul // s₁²
113//! divine 1 dup 0 mul add // s₁²+s₂²
114//! divine 1 dup 0 mul add // s₁²+s₂²+s₃²
115//! return
116//!
117//! sum_of_squares_ram:
118//! push 17 // 18
119//! read_mem 1 // s₄ 17
120//! pop 1 // s₄
121//! dup 0 mul // s₄²
122//! push 42 // s₄² 43
123//! read_mem 1 // s₄² s₅ 42
124//! pop 1 // s₄² s₅
125//! dup 0 mul // s₄² s₅²
126//! add // s₄²+s₅²
127//! return
128//! );
129//! let public_input = PublicInput::from([bfe!(597)]);
130//! let secret_input = [5, 9, 11].map(|v| bfe!(v));
131//! let initial_ram = [(17, 3), (42, 19)].map(|(address, v)| (bfe!(address), bfe!(v)));
132//! let non_determinism = NonDeterminism::from(secret_input).with_ram(initial_ram);
133//!
134//! let (stark, claim, proof) =
135//! triton_vm::prove_program(sum_of_squares_program, public_input, non_determinism).unwrap();
136//!
137//! let verdict = triton_vm::verify(stark, &claim, &proof);
138//! assert!(verdict);
139//! ```
140//!
141//! ## Crashing Triton VM
142//!
143//! Successful termination of a program is not guaranteed. For example, a
144//! program must execute `halt` as its last instruction. Certain instructions,
145//! such as `assert`, `invert`, or the u32 instructions, can also cause the VM
146//! to crash. Upon crashing Triton VM, methods like [`run`](VM::run) and
147//! [`trace_execution`](VM::trace_execution) will return a [`VMError`]. This can
148//! be helpful for debugging.
149//!
150//! ```
151//! # use triton_vm::prelude::*;
152//! let crashing_program = triton_program!(push 2 assert error_id 42 halt);
153//! let vm_error = VM::run(crashing_program, [].into(), [].into()).unwrap_err();
154//! let InstructionError::AssertionFailed(ref assertion_error) = vm_error.source else {
155//! unreachable!();
156//! };
157//!
158//! assert_eq!(Some(42), assertion_error.id);
159//! eprintln!("{vm_error}"); // inspect the VM state
160//! ```
161
162#![recursion_limit = "4096"]
163//
164// If code coverage tool `cargo-llvm-cov` is running with the nightly toolchain,
165// enable the unstable “coverage” attribute. This allows using the annotation
166// `#[coverage(off)]` to explicitly exclude certain parts of the code from
167// being considered as “code under test.” Most prominently, the annotation
168// should be added to every `#[cfg(test)]` module. Since the “coverage”
169// feature is enable only conditionally, the annotation to use is:
170// #[cfg_attr(coverage_nightly, coverage(off))]
171//
172// See also:
173// - https://github.com/taiki-e/cargo-llvm-cov#exclude-code-from-coverage
174// - https://github.com/rust-lang/rust/issues/84605
175#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
176
177pub use air;
178pub use isa;
179pub use twenty_first;
180
181use isa::program::Program;
182
183use crate::error::ProvingError;
184use crate::prelude::*;
185
186pub mod aet;
187pub mod arithmetic_domain;
188pub mod challenges;
189pub mod config;
190pub mod constraints;
191pub mod error;
192pub mod example_programs;
193pub mod execution_trace_profiler;
194pub mod fri;
195pub mod memory_layout;
196mod ndarray_helper;
197pub mod prelude;
198pub mod profiler;
199pub mod proof;
200pub mod proof_item;
201pub mod proof_stream;
202pub mod stark;
203pub mod table;
204pub mod vm;
205
206#[cfg(test)]
207#[cfg_attr(coverage_nightly, coverage(off))]
208mod shared_tests;
209
210/// Prove correct execution of a program written in Triton assembly.
211/// This is a convenience function, abstracting away the details of the STARK
212/// construction. If you want to have more control over the STARK construction,
213/// this method can serve as a reference for how to use Triton VM.
214///
215/// Note that all arithmetic is in the prime field with 2^64 - 2^32 + 1
216/// elements. If the provided public input or secret input contains elements
217/// larger than this, proof generation will be aborted.
218///
219/// The program executed by Triton VM must terminate gracefully, i.e., with
220/// instruction `halt`. If the program crashes, _e.g._, due to an out-of-bounds
221/// instruction pointer or a failing `assert` instruction, proof generation will
222/// fail.
223///
224/// The default STARK parameters used by Triton VM give a (conjectured) security
225/// level of 160 bits.
226pub fn prove_program(
227 program: Program,
228 public_input: PublicInput,
229 non_determinism: NonDeterminism,
230) -> Result<(Stark, Claim, Proof), ProvingError> {
231 // Set up the claim that is to be proven. The claim contains all public
232 // information. The proof is zero-knowledge with respect to everything else.
233 //
234 // While it is more convenient to construct a
235 // `Claim::about_program(&program)`, this API is purposefully not used here
236 // to highlight that only a program's hash digest, not the full program, is
237 // part of the claim.
238 let claim = Claim::new(program.hash()).with_input(public_input.clone());
239
240 // Generate
241 // - the witness required for proof generation, i.e., the Algebraic
242 // Execution Trace (AET), and
243 // - the (public) output of the program.
244 //
245 // Crashes in the VM can occur for many reasons. For example:
246 // - due to failing `assert` instructions,
247 // - due to an out-of-bounds instruction pointer,
248 // - if the program does not terminate gracefully, _i.e._, with instruction
249 // `halt`,
250 // - if any of the two inputs does not conform to the program,
251 // - because of a bug in the program, among other things.
252 // If the VM crashes, proof generation will fail.
253 let (aet, public_output) = VM::trace_execution(program, public_input, non_determinism)?;
254
255 // Now that the public output is computed, populate the claim accordingly.
256 let claim = claim.with_output(public_output);
257
258 // The default parameters give a (conjectured) security level of 160 bits.
259 let stark = Stark::default();
260
261 // Generate the proof.
262 let proof = stark.prove(&claim, &aet)?;
263
264 Ok((stark, claim, proof))
265}
266
267/// A convenience function for proving a [`Claim`] and the program that claim
268/// corresponds to. Method [`prove_program`] gives a simpler interface with less
269/// control.
270pub fn prove(
271 stark: Stark,
272 claim: &Claim,
273 program: Program,
274 non_determinism: NonDeterminism,
275) -> Result<Proof, ProvingError> {
276 let program_digest = program.hash();
277 if program_digest != claim.program_digest {
278 return Err(ProvingError::ProgramDigestMismatch);
279 }
280 let (aet, public_output) =
281 VM::trace_execution(program, (&claim.input).into(), non_determinism)?;
282 if public_output != claim.output {
283 return Err(ProvingError::PublicOutputMismatch);
284 }
285
286 stark.prove(claim, &aet)
287}
288
289/// Verify a proof generated by [`prove`] or [`prove_program`].
290///
291/// Use [`Stark::verify`] for more verbose verification failures.
292#[must_use]
293pub fn verify(stark: Stark, claim: &Claim, proof: &Proof) -> bool {
294 stark.verify(claim, proof).is_ok()
295}
296
297#[cfg(test)]
298#[cfg_attr(coverage_nightly, coverage(off))]
299mod tests {
300 use assert2::assert;
301 use assert2::let_assert;
302 use isa::instruction::LabelledInstruction;
303 use isa::instruction::TypeHint;
304 use proptest::prelude::*;
305 use proptest_arbitrary_interop::arb;
306 use test_strategy::proptest;
307 use twenty_first::prelude::*;
308
309 use crate::prelude::*;
310
311 use super::*;
312
313 /// The compiler automatically adds any applicable auto trait (all of which
314 /// are marker traits) to self-defined types. This implies that these
315 /// trait bounds might vanish if the necessary pre-conditions are no
316 /// longer met. That'd be a breaking API change!
317 ///
318 /// To prevent _accidental_ removal of auto trait implementations, this
319 /// method tests for their presence. If you are re-designing any of the
320 /// types below and a test fails as a result, that might be fine. You
321 /// are now definitely aware of a consequence you might not have known
322 /// about otherwise. (If you were already aware you know how subtle this
323 /// stuff can be and are hopefully fine with reading this comment.)
324 ///
325 /// Inspired by “Rust for Rustaceans” by Jon Gjengset.
326 fn implements_auto_traits<T: Sized + Send + Sync + Unpin>() {}
327
328 #[test]
329 fn public_types_implement_usual_auto_traits() {
330 // re-exports
331 implements_auto_traits::<BFieldElement>();
332 implements_auto_traits::<Digest>();
333 implements_auto_traits::<Tip5>();
334 implements_auto_traits::<XFieldElement>();
335
336 // prelude
337 implements_auto_traits::<LabelledInstruction>();
338 implements_auto_traits::<NonDeterminism>();
339 implements_auto_traits::<Program>();
340 implements_auto_traits::<PublicInput>();
341 implements_auto_traits::<Claim>();
342 implements_auto_traits::<Proof>();
343 implements_auto_traits::<Prover>();
344 implements_auto_traits::<Stark>();
345 implements_auto_traits::<Verifier>();
346 implements_auto_traits::<VM>();
347 implements_auto_traits::<VMState>();
348
349 // errors
350 implements_auto_traits::<error::VMError>();
351 implements_auto_traits::<error::ArithmeticDomainError>();
352 implements_auto_traits::<error::ProofStreamError>();
353 implements_auto_traits::<error::FriSetupError>();
354 implements_auto_traits::<error::FriProvingError>();
355 implements_auto_traits::<error::FriValidationError>();
356 implements_auto_traits::<error::ProvingError>();
357 implements_auto_traits::<error::VerificationError>();
358
359 // table things
360 implements_auto_traits::<challenges::Challenges>();
361 implements_auto_traits::<table::degree_lowering::DegreeLoweringMainColumn>();
362 implements_auto_traits::<table::degree_lowering::DegreeLoweringAuxColumn>();
363 implements_auto_traits::<table::degree_lowering::DegreeLoweringTable>();
364 implements_auto_traits::<table::master_table::MasterMainTable>();
365 implements_auto_traits::<table::master_table::MasterAuxTable>();
366 implements_auto_traits::<table::op_stack::OpStackTableEntry>();
367 implements_auto_traits::<table::ram::RamTableCall>();
368 implements_auto_traits::<table::u32::U32TableEntry>();
369
370 // other
371 implements_auto_traits::<aet::AlgebraicExecutionTrace>();
372 implements_auto_traits::<aet::TableHeight>();
373 implements_auto_traits::<arithmetic_domain::ArithmeticDomain>();
374 implements_auto_traits::<execution_trace_profiler::ExecutionTraceProfile>();
375 implements_auto_traits::<execution_trace_profiler::ProfileLine>();
376 implements_auto_traits::<execution_trace_profiler::VMTableHeights>();
377 implements_auto_traits::<fri::Fri>();
378 implements_auto_traits::<memory_layout::DynamicTasmConstraintEvaluationMemoryLayout>();
379 implements_auto_traits::<memory_layout::MemoryRegion>();
380 implements_auto_traits::<memory_layout::StaticTasmConstraintEvaluationMemoryLayout>();
381 implements_auto_traits::<profiler::VMPerformanceProfile>();
382 implements_auto_traits::<proof_item::FriResponse>();
383 implements_auto_traits::<proof_item::ProofItem>();
384 implements_auto_traits::<proof_stream::ProofStream>();
385 implements_auto_traits::<TypeHint>();
386 implements_auto_traits::<vm::CoProcessorCall>();
387 }
388
389 #[proptest]
390 fn prove_verify_knowledge_of_hash_preimage(
391 #[strategy(arb())] hash_preimage: Digest,
392 #[strategy(arb())] some_tie_to_an_outer_context: Digest,
393 ) {
394 let hash_digest = Tip5::hash_pair(hash_preimage, Digest::default()).values();
395
396 let program = triton_program! {
397 divine 5
398 hash
399 push {hash_digest[4]}
400 push {hash_digest[3]}
401 push {hash_digest[2]}
402 push {hash_digest[1]}
403 push {hash_digest[0]}
404 assert_vector
405 read_io 5
406 halt
407 };
408
409 let public_input = PublicInput::from(some_tie_to_an_outer_context.reversed().values());
410 let non_determinism = NonDeterminism::new(hash_preimage.reversed().values());
411 let maybe_proof = prove_program(program.clone(), public_input.clone(), non_determinism);
412 let (stark, claim, proof) =
413 maybe_proof.map_err(|err| TestCaseError::Fail(err.to_string().into()))?;
414 prop_assert_eq!(Stark::default(), stark);
415
416 let verdict = verify(stark, &claim, &proof);
417 prop_assert!(verdict);
418
419 prop_assert!(claim.output.is_empty());
420 let expected_program_digest = program.hash();
421 prop_assert_eq!(expected_program_digest, claim.program_digest);
422 prop_assert_eq!(public_input.individual_tokens, claim.input);
423 }
424
425 #[test]
426 fn lib_use_initial_ram() {
427 let program = triton_program!(
428 push 51 read_mem 1 pop 1
429 push 42 read_mem 1 pop 1
430 mul
431 write_io 1 halt
432 );
433
434 let public_input = PublicInput::default();
435 let initial_ram = [(42, 17), (51, 13)].map(|(address, v)| (bfe!(address), bfe!(v)));
436 let non_determinism = NonDeterminism::default().with_ram(initial_ram);
437 let (stark, claim, proof) = prove_program(program, public_input, non_determinism).unwrap();
438 assert!(13 * 17 == claim.output[0].value());
439
440 let verdict = verify(stark, &claim, &proof);
441 assert!(verdict);
442 }
443
444 #[test]
445 fn lib_prove_verify() {
446 let program = triton_program!(push 1 assert halt);
447 let claim = Claim::about_program(&program);
448
449 let stark = Stark::default();
450 let proof = prove(stark, &claim, program, [].into()).unwrap();
451 let verdict = verify(stark, &claim, &proof);
452 assert!(verdict);
453 }
454
455 #[test]
456 fn prove_then_verify_concurrently() {
457 let program = crate::example_programs::FIBONACCI_SEQUENCE.clone();
458 let input = PublicInput::from(bfe_array![100]);
459
460 let (stark, claim, proof) =
461 prove_program(program, input, NonDeterminism::default()).unwrap();
462
463 let verify = || assert!(stark.verify(&claim, &proof).is_ok());
464 rayon::join(verify, verify);
465 }
466
467 #[test]
468 fn lib_prove_with_incorrect_program_digest_gives_appropriate_error() {
469 let program = triton_program!(push 1 assert halt);
470 let other_program = triton_program!(push 2 assert halt);
471 let claim = Claim::about_program(&other_program);
472
473 let stark = Stark::default();
474 let_assert!(Err(err) = prove(stark, &claim, program, [].into()));
475 assert!(let ProvingError::ProgramDigestMismatch = err);
476 }
477
478 #[test]
479 fn lib_prove_with_incorrect_public_output_gives_appropriate_error() {
480 let program = triton_program! { read_io 1 push 2 mul write_io 1 halt };
481 let claim = Claim::about_program(&program)
482 .with_input(bfe_vec![2])
483 .with_output(bfe_vec![5]);
484
485 let stark = Stark::default();
486 let_assert!(Err(err) = prove(stark, &claim, program, [].into()));
487 assert!(let ProvingError::PublicOutputMismatch = err);
488 }
489
490 #[test]
491 fn nested_triton_asm_interpolation() {
492 let double_write = triton_asm![write_io 1; 2];
493 let quadruple_write = triton_asm!({&double_write} write_io 2);
494 let snippet_0 = triton_asm!(push 7 nop call my_label);
495 let snippet_1 = triton_asm!(pop 2 halt my_label: push 8 push 9 {&quadruple_write});
496 let source_code = triton_asm!(push 6 {&snippet_0} {&snippet_1} halt);
497
498 let program = triton_program!({ &source_code });
499 let public_output = VM::run(program, [].into(), [].into()).unwrap();
500
501 let expected_output = bfe_vec![9, 8, 7, 6];
502 assert_eq!(expected_output, public_output);
503 }
504
505 #[test]
506 fn triton_asm_interpolation_of_many_pops() {
507 let push_25 = triton_asm![push 0; 25];
508 let pop_25 = triton_asm![pop 5; 5];
509 let program = triton_program! { push 1 { &push_25 } { &pop_25 } assert halt };
510 VM::run(program, [].into(), [].into()).unwrap();
511 }
512
513 #[test]
514 #[should_panic(expected = "IndexOutOfBounds(0)")]
515 fn parsing_pop_with_illegal_argument_fails() {
516 triton_instr!(pop 0);
517 }
518
519 #[test]
520 fn triton_asm_macro_can_parse_type_hints() {
521 let instructions = triton_asm!(
522 hint name_0: Type0 = stack[0..8]
523 hint name_1 = stack[1..9]
524 hint name_2: Type2 = stack[2]
525 hint name_3 = stack[3]
526 );
527
528 assert!(4 == instructions.len());
529 let_assert!(LabelledInstruction::TypeHint(type_hint_0) = instructions[0].clone());
530 let_assert!(LabelledInstruction::TypeHint(type_hint_1) = instructions[1].clone());
531 let_assert!(LabelledInstruction::TypeHint(type_hint_2) = instructions[2].clone());
532 let_assert!(LabelledInstruction::TypeHint(type_hint_3) = instructions[3].clone());
533
534 let expected_type_hint_0 = TypeHint {
535 starting_index: 0,
536 length: 8,
537 type_name: Some("Type0".to_string()),
538 variable_name: "name_0".to_string(),
539 };
540 let expected_type_hint_1 = TypeHint {
541 starting_index: 1,
542 length: 8,
543 type_name: None,
544 variable_name: "name_1".to_string(),
545 };
546 let expected_type_hint_2 = TypeHint {
547 starting_index: 2,
548 length: 1,
549 type_name: Some("Type2".to_string()),
550 variable_name: "name_2".to_string(),
551 };
552 let expected_type_hint_3 = TypeHint {
553 starting_index: 3,
554 length: 1,
555 type_name: None,
556 variable_name: "name_3".to_string(),
557 };
558
559 assert!(expected_type_hint_0 == type_hint_0);
560 assert!(expected_type_hint_1 == type_hint_1);
561 assert!(expected_type_hint_2 == type_hint_2);
562 assert!(expected_type_hint_3 == type_hint_3);
563 }
564
565 #[test]
566 fn triton_program_macro_can_parse_type_hints() {
567 let program = triton_program! {
568 push 3 hint loop_counter = stack[0]
569 call my_loop
570 pop 1
571 halt
572
573 my_loop:
574 dup 0 push 0 eq
575 hint return_condition: bool = stack[0]
576 skiz return
577 divine 3
578 swap 3
579 hint magic_number: XFE = stack[1..4]
580 hint fizzled_magic = stack[5..8]
581 recurse
582 };
583
584 let expected_type_hint_address_02 = TypeHint {
585 starting_index: 0,
586 length: 1,
587 type_name: None,
588 variable_name: "loop_counter".to_string(),
589 };
590 let expected_type_hint_address_12 = TypeHint {
591 starting_index: 0,
592 length: 1,
593 type_name: Some("bool".to_string()),
594 variable_name: "return_condition".to_string(),
595 };
596 let expected_type_hint_address_18_0 = TypeHint {
597 starting_index: 1,
598 length: 3,
599 type_name: Some("XFE".to_string()),
600 variable_name: "magic_number".to_string(),
601 };
602 let expected_type_hint_address_18_1 = TypeHint {
603 starting_index: 5,
604 length: 3,
605 type_name: None,
606 variable_name: "fizzled_magic".to_string(),
607 };
608
609 assert!(vec![expected_type_hint_address_02] == program.type_hints_at(2));
610
611 assert!(vec![expected_type_hint_address_12] == program.type_hints_at(12));
612
613 let expected_type_hints_address_18 = vec![
614 expected_type_hint_address_18_0,
615 expected_type_hint_address_18_1,
616 ];
617 assert!(expected_type_hints_address_18 == program.type_hints_at(18));
618 }
619}