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}