triton_vm/
error.rs

1pub use isa::error::InstructionError;
2pub use isa::error::NumberOfWordsError;
3pub use isa::error::OpStackElementError;
4pub use isa::error::OpStackError;
5pub use isa::error::ParseError;
6pub use isa::error::ProgramDecodingError;
7
8use std::fmt;
9use std::fmt::Display;
10use std::fmt::Formatter;
11
12use thiserror::Error;
13use twenty_first::error::MerkleTreeError;
14use twenty_first::prelude::*;
15
16use crate::proof_item::ProofItem;
17use crate::proof_item::ProofItemVariant;
18use crate::proof_stream::ProofStream;
19use crate::vm::VMState;
20
21/// Indicates a runtime error that resulted in a crash of Triton VM.
22#[derive(Debug, Clone, Eq, PartialEq, Error)]
23pub struct VMError {
24    /// The reason Triton VM crashed.
25    pub source: InstructionError,
26
27    /// The state of Triton VM at the time of the crash.
28    pub vm_state: Box<VMState>,
29}
30
31impl VMError {
32    pub fn new(source: InstructionError, vm_state: VMState) -> Self {
33        let vm_state = Box::new(vm_state);
34        Self { source, vm_state }
35    }
36}
37
38impl Display for VMError {
39    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40        writeln!(f, "VM error: {}", self.source)?;
41        writeln!(f, "VM state:")?;
42        writeln!(f, "{}", self.vm_state)
43    }
44}
45
46#[non_exhaustive]
47#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
48pub enum ArithmeticDomainError {
49    #[error("the domain's length must be a power of 2 but was {0}")]
50    PrimitiveRootNotSupported(u64),
51
52    #[error("the domain's length must be at least 2 to be halved, but it was {0}")]
53    TooSmallForHalving(usize),
54}
55
56#[non_exhaustive]
57#[derive(Debug, Error)]
58pub enum ProofStreamError {
59    #[error("queue must be non-empty in order to dequeue an item")]
60    EmptyQueue,
61
62    #[error("expected {expected}, got {got}")]
63    UnexpectedItem {
64        expected: ProofItemVariant,
65        got: ProofItem,
66    },
67
68    #[error("the proof stream must contain a log2_padded_height item")]
69    NoLog2PaddedHeight,
70
71    #[error("the proof stream must contain exactly one log2_padded_height item")]
72    TooManyLog2PaddedHeights,
73
74    #[error(transparent)]
75    DecodingError(#[from] <ProofStream as BFieldCodec>::Error),
76}
77
78#[non_exhaustive]
79#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
80pub enum FriSetupError {
81    #[error("the expansion factor must be greater than 1")]
82    ExpansionFactorTooSmall,
83
84    #[error("the expansion factor must be a power of 2")]
85    ExpansionFactorUnsupported,
86
87    #[error("the expansion factor must be smaller than the domain length")]
88    ExpansionFactorMismatch,
89
90    #[error(transparent)]
91    ArithmeticDomainError(#[from] ArithmeticDomainError),
92}
93
94#[non_exhaustive]
95#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
96pub enum FriProvingError {
97    #[error(transparent)]
98    MerkleTreeError(#[from] MerkleTreeError),
99
100    #[error(transparent)]
101    ArithmeticDomainError(#[from] ArithmeticDomainError),
102}
103
104#[non_exhaustive]
105#[derive(Debug, Error)]
106pub enum FriValidationError {
107    #[error("the number of revealed leaves does not match the number of collinearity checks")]
108    IncorrectNumberOfRevealedLeaves,
109
110    #[error("Merkle tree authentication failed")]
111    BadMerkleAuthenticationPath,
112
113    #[error("computed and received codeword of last round do not match")]
114    LastCodewordMismatch,
115
116    #[error("evaluations of last round's polynomial and last round codeword do not match")]
117    LastRoundPolynomialEvaluationMismatch,
118
119    #[error("last round's polynomial has too high degree")]
120    LastRoundPolynomialHasTooHighDegree,
121
122    #[error("received codeword of last round does not correspond to its commitment")]
123    BadMerkleRootForLastCodeword,
124
125    #[error(transparent)]
126    ProofStreamError(#[from] ProofStreamError),
127
128    #[error(transparent)]
129    MerkleTreeError(#[from] MerkleTreeError),
130
131    #[error(transparent)]
132    ArithmeticDomainError(#[from] ArithmeticDomainError),
133}
134
135#[non_exhaustive]
136#[derive(Debug, Clone, Eq, PartialEq, Error)]
137pub enum ProvingError {
138    #[error("claimed program digest does not match actual program digest")]
139    ProgramDigestMismatch,
140
141    #[error("claimed public output does not match actual public output")]
142    PublicOutputMismatch,
143
144    #[error("expected row of length {expected_len} but got {actual_len}")]
145    TableRowConversionError {
146        expected_len: usize,
147        actual_len: usize,
148    },
149
150    #[error(transparent)]
151    MerkleTreeError(#[from] MerkleTreeError),
152
153    #[error(transparent)]
154    ArithmeticDomainError(#[from] ArithmeticDomainError),
155
156    #[error(transparent)]
157    FriSetupError(#[from] FriSetupError),
158
159    #[error(transparent)]
160    FriProvingError(#[from] FriProvingError),
161
162    #[error(transparent)]
163    VMError(#[from] VMError),
164}
165
166#[non_exhaustive]
167#[derive(Debug, Error)]
168pub enum VerificationError {
169    #[error("received and computed out-of-domain quotient values don't match")]
170    OutOfDomainQuotientValueMismatch,
171
172    #[error("failed to verify authentication path for main codeword")]
173    MainCodewordAuthenticationFailure,
174
175    #[error("failed to verify authentication path for auxiliary codeword")]
176    AuxiliaryCodewordAuthenticationFailure,
177
178    #[error("failed to verify authentication path for combined quotient codeword")]
179    QuotientCodewordAuthenticationFailure,
180
181    #[error("received and computed combination codewords don't match")]
182    CombinationCodewordMismatch,
183
184    #[error("the number of received combination codeword indices does not match the parameters")]
185    IncorrectNumberOfRowIndices,
186
187    #[error("the number of received FRI codeword values does not match the parameters")]
188    IncorrectNumberOfFRIValues,
189
190    #[error("the number of received quotient segment elements does not match the parameters")]
191    IncorrectNumberOfQuotientSegmentElements,
192
193    #[error("the number of received main table rows does not match the parameters")]
194    IncorrectNumberOfMainTableRows,
195
196    #[error("the number of received auxiliary table rows does not match the parameters")]
197    IncorrectNumberOfAuxTableRows,
198
199    #[error(transparent)]
200    ProofStreamError(#[from] ProofStreamError),
201
202    #[error(transparent)]
203    ArithmeticDomainError(#[from] ArithmeticDomainError),
204
205    #[error(transparent)]
206    FriSetupError(#[from] FriSetupError),
207
208    #[error(transparent)]
209    FriValidationError(#[from] FriValidationError),
210}
211
212#[cfg(test)]
213#[cfg_attr(coverage_nightly, coverage(off))]
214mod tests {
215    use assert2::assert;
216    use assert2::let_assert;
217    use isa::op_stack::OpStackError;
218    use isa::triton_program;
219    use proptest::prelude::*;
220    use proptest_arbitrary_interop::arb;
221    use test_strategy::proptest;
222
223    use super::*;
224    use crate::prelude::VM;
225
226    #[test]
227    fn instruction_pointer_overflow() {
228        let program = triton_program!(nop);
229        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
230        let_assert!(InstructionError::InstructionPointerOverflow = err.source);
231    }
232
233    #[test]
234    fn shrink_op_stack_too_much() {
235        let program = triton_program!(pop 3 halt);
236        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
237        let_assert!(InstructionError::OpStackError(OpStackError::TooShallow) = err.source);
238    }
239
240    #[test]
241    fn return_without_call() {
242        let program = triton_program!(return halt);
243        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
244        let_assert!(InstructionError::JumpStackIsEmpty = err.source);
245    }
246
247    #[test]
248    fn recurse_without_call() {
249        let program = triton_program!(recurse halt);
250        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
251        let_assert!(InstructionError::JumpStackIsEmpty = err.source);
252    }
253
254    #[test]
255    fn assert_false() {
256        let program = triton_program!(push 0 assert halt);
257        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
258        let_assert!(InstructionError::AssertionFailed(error) = err.source);
259        assert!(bfe!(1) == error.expected);
260        assert!(bfe!(0) == error.actual);
261        assert!(error.id.is_none());
262    }
263
264    #[test]
265    fn assert_false_with_assertion_context() {
266        let program = triton_program!(push 0 assert error_id 42 halt);
267        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
268        let_assert!(InstructionError::AssertionFailed(err) = err.source);
269        assert!(bfe!(1) == err.expected);
270        assert!(bfe!(0) == err.actual);
271        assert!(Some(42) == err.id);
272    }
273
274    #[test]
275    fn print_unequal_vec_assert_error() {
276        let program = triton_program! {
277            push 4 push 3 push 2 push  1 push 0
278            push 4 push 3 push 2 push 10 push 0
279            assert_vector halt
280        };
281        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
282        let_assert!(InstructionError::VectorAssertionFailed(index, err) = err.source);
283        assert!(1 == index);
284        assert!(bfe!(10) == err.expected);
285        assert!(bfe!(1) == err.actual);
286        assert!(None == err.id);
287    }
288
289    #[proptest]
290    fn assertion_context_error_id_is_propagated_correctly(
291        #[filter(#actual != 1)] actual: i64,
292        error_id: Option<i128>,
293    ) {
294        let program = if let Some(id) = error_id {
295            triton_program! {push {actual} assert error_id {id}}
296        } else {
297            triton_program! {push {actual} assert}
298        };
299        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
300        let_assert!(InstructionError::AssertionFailed(err) = err.source);
301        prop_assert_eq!(bfe!(1), err.expected);
302        prop_assert_eq!(bfe!(actual), err.actual);
303        prop_assert_eq!(error_id, err.id);
304    }
305
306    #[proptest]
307    fn triggering_assertion_failure_results_in_expected_error_id(
308        #[strategy(0_usize..5)] failure_index: usize,
309    ) {
310        let mut almost_all_ones = [1; 5];
311        almost_all_ones[failure_index] = 0;
312
313        let program = triton_program! {
314            push {almost_all_ones[0]} assert error_id 0
315            push {almost_all_ones[1]} assert error_id 1
316            push {almost_all_ones[2]} assert error_id 2
317            push {almost_all_ones[3]} assert error_id 3
318            push {almost_all_ones[4]} assert error_id 4
319        };
320        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
321        let_assert!(InstructionError::AssertionFailed(err) = err.source);
322        let expected_error_id = i128::try_from(failure_index)?;
323        prop_assert_eq!(expected_error_id, err.id.unwrap());
324    }
325
326    #[proptest]
327    fn assert_unequal_vec(
328        #[strategy(arb())] test_vector: [BFieldElement; Digest::LEN],
329        #[strategy(0..Digest::LEN)] disturbance_index: usize,
330        #[strategy(arb())]
331        #[filter(#test_vector[#disturbance_index] != #random_element)]
332        random_element: BFieldElement,
333        error_id: i128,
334    ) {
335        let mut disturbed_vector = test_vector;
336        disturbed_vector[disturbance_index] = random_element;
337
338        let program = triton_program! {
339            push {disturbed_vector[4]}
340            push {disturbed_vector[3]}
341            push {disturbed_vector[2]}
342            push {disturbed_vector[1]}
343            push {disturbed_vector[0]}
344
345            push {test_vector[4]}
346            push {test_vector[3]}
347            push {test_vector[2]}
348            push {test_vector[1]}
349            push {test_vector[0]}
350
351            assert_vector error_id {error_id}
352            halt
353        };
354
355        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
356        let_assert!(InstructionError::VectorAssertionFailed(index, err) = err.source);
357        prop_assert_eq!(disturbance_index, index);
358        prop_assert_eq!(test_vector[index], err.expected, "unequal “expected”");
359        prop_assert_eq!(disturbed_vector[index], err.actual, "unequal “actual”");
360        prop_assert_eq!(Some(error_id), err.id);
361    }
362
363    #[test]
364    fn inverse_of_zero() {
365        let program = triton_program!(push 0 invert halt);
366        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
367        let_assert!(InstructionError::InverseOfZero = err.source);
368    }
369
370    #[test]
371    fn xfe_inverse_of_zero() {
372        let program = triton_program!(push 0 push 0 push 0 x_invert halt);
373        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
374        let_assert!(InstructionError::InverseOfZero = err.source);
375    }
376
377    #[test]
378    fn division_by_zero() {
379        let program = triton_program!(push 0 push 5 div_mod halt);
380        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
381        let_assert!(InstructionError::DivisionByZero = err.source);
382    }
383
384    #[test]
385    fn log_of_zero() {
386        let program = triton_program!(push 0 log_2_floor halt);
387        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
388        let_assert!(InstructionError::LogarithmOfZero = err.source);
389    }
390
391    #[test]
392    fn failed_u32_conversion() {
393        let program = triton_program!(push 4294967297 push 1 and halt);
394        let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
395        let_assert!(InstructionError::OpStackError(err) = err.source);
396        let_assert!(OpStackError::FailedU32Conversion(element) = err);
397        assert!(4_294_967_297 == element.value());
398    }
399}