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#[derive(Debug, Clone, Eq, PartialEq, Error)]
23pub struct VMError {
24 pub source: InstructionError,
26
27 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)]
213mod tests {
214 use assert2::assert;
215 use assert2::let_assert;
216 use isa::op_stack::OpStackError;
217 use isa::triton_program;
218 use proptest::prelude::*;
219 use proptest_arbitrary_interop::arb;
220 use test_strategy::proptest;
221
222 use super::*;
223 use crate::prelude::VM;
224
225 #[test]
226 fn instruction_pointer_overflow() {
227 let program = triton_program!(nop);
228 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
229 let_assert!(InstructionError::InstructionPointerOverflow = err.source);
230 }
231
232 #[test]
233 fn shrink_op_stack_too_much() {
234 let program = triton_program!(pop 3 halt);
235 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
236 let_assert!(InstructionError::OpStackError(OpStackError::TooShallow) = err.source);
237 }
238
239 #[test]
240 fn return_without_call() {
241 let program = triton_program!(return halt);
242 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
243 let_assert!(InstructionError::JumpStackIsEmpty = err.source);
244 }
245
246 #[test]
247 fn recurse_without_call() {
248 let program = triton_program!(recurse halt);
249 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
250 let_assert!(InstructionError::JumpStackIsEmpty = err.source);
251 }
252
253 #[test]
254 fn assert_false() {
255 let program = triton_program!(push 0 assert halt);
256 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
257 let_assert!(InstructionError::AssertionFailed(error) = err.source);
258 assert!(bfe!(1) == error.expected);
259 assert!(bfe!(0) == error.actual);
260 assert!(error.id.is_none());
261 }
262
263 #[test]
264 fn assert_false_with_assertion_context() {
265 let program = triton_program!(push 0 assert error_id 42 halt);
266 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
267 let_assert!(InstructionError::AssertionFailed(err) = err.source);
268 assert!(bfe!(1) == err.expected);
269 assert!(bfe!(0) == err.actual);
270 assert!(Some(42) == err.id);
271 }
272
273 #[test]
274 fn print_unequal_vec_assert_error() {
275 let program = triton_program! {
276 push 4 push 3 push 2 push 1 push 0
277 push 4 push 3 push 2 push 10 push 0
278 assert_vector halt
279 };
280 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
281 let_assert!(InstructionError::VectorAssertionFailed(index, err) = err.source);
282 assert!(1 == index);
283 assert!(bfe!(10) == err.expected);
284 assert!(bfe!(1) == err.actual);
285 assert!(None == err.id);
286 }
287
288 #[proptest]
289 fn assertion_context_error_id_is_propagated_correctly(
290 #[filter(#actual != 1)] actual: i64,
291 error_id: Option<i128>,
292 ) {
293 let program = if let Some(id) = error_id {
294 triton_program! {push {actual} assert error_id {id}}
295 } else {
296 triton_program! {push {actual} assert}
297 };
298 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
299 let_assert!(InstructionError::AssertionFailed(err) = err.source);
300 prop_assert_eq!(bfe!(1), err.expected);
301 prop_assert_eq!(bfe!(actual), err.actual);
302 prop_assert_eq!(error_id, err.id);
303 }
304
305 #[proptest]
306 fn triggering_assertion_failure_results_in_expected_error_id(
307 #[strategy(0_usize..5)] failure_index: usize,
308 ) {
309 let mut almost_all_ones = [1; 5];
310 almost_all_ones[failure_index] = 0;
311
312 let program = triton_program! {
313 push {almost_all_ones[0]} assert error_id 0
314 push {almost_all_ones[1]} assert error_id 1
315 push {almost_all_ones[2]} assert error_id 2
316 push {almost_all_ones[3]} assert error_id 3
317 push {almost_all_ones[4]} assert error_id 4
318 };
319 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
320 let_assert!(InstructionError::AssertionFailed(err) = err.source);
321 let expected_error_id = i128::try_from(failure_index)?;
322 prop_assert_eq!(expected_error_id, err.id.unwrap());
323 }
324
325 #[proptest]
326 fn assert_unequal_vec(
327 #[strategy(arb())] test_vector: [BFieldElement; Digest::LEN],
328 #[strategy(0..Digest::LEN)] disturbance_index: usize,
329 #[strategy(arb())]
330 #[filter(#test_vector[#disturbance_index] != #random_element)]
331 random_element: BFieldElement,
332 error_id: i128,
333 ) {
334 let mut disturbed_vector = test_vector;
335 disturbed_vector[disturbance_index] = random_element;
336
337 let program = triton_program! {
338 push {disturbed_vector[4]}
339 push {disturbed_vector[3]}
340 push {disturbed_vector[2]}
341 push {disturbed_vector[1]}
342 push {disturbed_vector[0]}
343
344 push {test_vector[4]}
345 push {test_vector[3]}
346 push {test_vector[2]}
347 push {test_vector[1]}
348 push {test_vector[0]}
349
350 assert_vector error_id {error_id}
351 halt
352 };
353
354 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
355 let_assert!(InstructionError::VectorAssertionFailed(index, err) = err.source);
356 prop_assert_eq!(disturbance_index, index);
357 prop_assert_eq!(test_vector[index], err.expected, "unequal “expected”");
358 prop_assert_eq!(disturbed_vector[index], err.actual, "unequal “actual”");
359 prop_assert_eq!(Some(error_id), err.id);
360 }
361
362 #[test]
363 fn inverse_of_zero() {
364 let program = triton_program!(push 0 invert halt);
365 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
366 let_assert!(InstructionError::InverseOfZero = err.source);
367 }
368
369 #[test]
370 fn xfe_inverse_of_zero() {
371 let program = triton_program!(push 0 push 0 push 0 x_invert halt);
372 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
373 let_assert!(InstructionError::InverseOfZero = err.source);
374 }
375
376 #[test]
377 fn division_by_zero() {
378 let program = triton_program!(push 0 push 5 div_mod halt);
379 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
380 let_assert!(InstructionError::DivisionByZero = err.source);
381 }
382
383 #[test]
384 fn log_of_zero() {
385 let program = triton_program!(push 0 log_2_floor halt);
386 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
387 let_assert!(InstructionError::LogarithmOfZero = err.source);
388 }
389
390 #[test]
391 fn failed_u32_conversion() {
392 let program = triton_program!(push 4294967297 push 1 and halt);
393 let_assert!(Err(err) = VM::run(program, [].into(), [].into()));
394 let_assert!(InstructionError::OpStackError(err) = err.source);
395 let_assert!(OpStackError::FailedU32Conversion(element) = err);
396 assert!(4_294_967_297 == element.value());
397 }
398}