Skip to content

Commit

Permalink
feat: better error reporting for failing assert_vector
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-ferdinand committed Oct 9, 2023
1 parent 641ed39 commit ee83ab6
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 49 deletions.
104 changes: 73 additions & 31 deletions triton-vm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::fmt::Display;
use std::fmt::Formatter;

use twenty_first::shared_math::b_field_element::BFieldElement;
use twenty_first::shared_math::digest::DIGEST_LENGTH;

use crate::op_stack::OpStackElement;
use InstructionError::*;

#[derive(Debug, Clone)]
Expand All @@ -12,6 +14,7 @@ pub enum InstructionError {
OpStackTooShallow,
JumpStackIsEmpty,
AssertionFailed(usize, u32, BFieldElement),
VectorAssertionFailed(usize, u32, OpStackElement, BFieldElement, BFieldElement),
InverseOfZero,
DivisionByZero,
LogarithmOfZero,
Expand All @@ -24,40 +27,26 @@ impl Display for InstructionError {
InstructionPointerOverflow(ip) => {
write!(f, "Instruction pointer {ip} points outside of program")
}

OpStackTooShallow => {
write!(f, "Operational stack is too shallow")
}

JumpStackIsEmpty => {
write!(f, "Jump stack is empty.")
}

OpStackTooShallow => write!(f, "Operational stack is too shallow"),
JumpStackIsEmpty => write!(f, "Jump stack is empty."),
AssertionFailed(ip, clk, st0) => {
write!(
f,
"Assertion failed: st0 must be 1. ip: {ip}, clk: {clk}, st0: {st0}"
)
write!(f, "Assertion failed: st0 must be 1. ")?;
write!(f, "ip: {ip}, clk: {clk}, st0: {st0}")
}

InverseOfZero => {
write!(f, "0 does not have a multiplicative inverse")
VectorAssertionFailed(ip, clk, failing_position_lhs, lhs, rhs) => {
let failing_index_lhs: usize = failing_position_lhs.into();
let failing_index_rhs = failing_index_lhs + DIGEST_LENGTH;
write!(f, "Vector assertion failed: ")?;
write!(f, "op_stack[{failing_index_lhs}] = {lhs} != ")?;
write!(f, "{rhs} = op_stack[{failing_index_rhs}]. ")?;
write!(f, "ip: {ip}, clk: {clk}")
}

DivisionByZero => {
write!(f, "Division by 0 is impossible")
}

LogarithmOfZero => {
write!(f, "The logarithm of 0 does not exist")
}

FailedU32Conversion(word) => {
write!(
f,
"Failed to convert BFieldElement {} into u32",
word.value()
)
InverseOfZero => write!(f, "0 does not have a multiplicative inverse"),
DivisionByZero => write!(f, "Division by 0 is impossible"),
LogarithmOfZero => write!(f, "The logarithm of 0 does not exist"),
FailedU32Conversion(bfe) => {
let value = bfe.value();
write!(f, "Failed to convert BFieldElement {value} into u32")
}
}
}
Expand All @@ -68,6 +57,10 @@ impl Error for InstructionError {}
#[cfg(test)]
mod tests {
use crate::triton_program;
use proptest::prelude::*;
use proptest_arbitrary_interop::arb;

use super::*;

#[test]
#[should_panic(expected = "Instruction pointer 1 points outside of program")]
Expand Down Expand Up @@ -104,13 +97,62 @@ mod tests {
program.run([].into(), [].into()).unwrap();
}

proptest! {
#[test]
fn assert_unequal_vec_test(
test_vector in arb::<[BFieldElement; DIGEST_LENGTH]>(),
disturbance_index in 0..DIGEST_LENGTH,
random_element in arb::<BFieldElement>(),
) {
let mut disturbed_vector = test_vector;
disturbed_vector[disturbance_index] = random_element;

if disturbed_vector == test_vector {
return Ok(());
}

let program = triton_program!{
push {test_vector[4]}
push {test_vector[3]}
push {test_vector[2]}
push {test_vector[1]}
push {test_vector[0]}

push {disturbed_vector[4]}
push {disturbed_vector[3]}
push {disturbed_vector[2]}
push {disturbed_vector[1]}
push {disturbed_vector[0]}

assert_vector
halt
};

let err = program.run([].into(), [].into()).unwrap_err();

let err = err.downcast::<InstructionError>().unwrap();
let VectorAssertionFailed(_, _, index, _, _) = err else {
panic!("VM panicked with unexpected error {err}.")
};
let index: usize = index.into();
prop_assert_eq!(disturbance_index, index);
}
}

#[test]
#[should_panic(expected = "0 does not have a multiplicative inverse")]
fn inverse_of_zero_test() {
let program = triton_program!(push 0 invert halt);
program.run([].into(), [].into()).unwrap();
}

#[test]
#[should_panic(expected = "0 does not have a multiplicative inverse")]
fn xfe_inverse_of_zero_test() {
let program = triton_program!(push 0 push 0 push 0 xinvert halt);
program.run([].into(), [].into()).unwrap();
}

#[test]
#[should_panic(expected = "Division by 0 is impossible")]
fn division_by_zero_test() {
Expand Down
37 changes: 19 additions & 18 deletions triton-vm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::VecDeque;
use std::convert::TryInto;
use std::fmt::Display;

use crate::error::InstructionError;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Result;
Expand Down Expand Up @@ -447,17 +448,29 @@ impl<'pgm> VMState<'pgm> {
}

fn instruction_assert_vector(&mut self) -> Result<Option<CoProcessorCall>> {
if !self.assert_vector() {
bail!(AssertionFailed(
self.instruction_pointer,
self.cycle_count,
self.op_stack.peek_at(ST0)
));
for index in 0..DIGEST_LENGTH {
let lhs = index.try_into().unwrap();
let rhs = (index + DIGEST_LENGTH).try_into().unwrap();
if self.op_stack.peek_at(lhs) != self.op_stack.peek_at(rhs) {
bail!(self.new_vector_assertion_failure(index));
}
}
self.instruction_pointer += 1;
Ok(None)
}

fn new_vector_assertion_failure(&self, failing_index_lhs: usize) -> InstructionError {
let failing_position_lhs = failing_index_lhs.try_into().unwrap();
let failing_position_rhs = (failing_index_lhs + DIGEST_LENGTH).try_into().unwrap();
VectorAssertionFailed(
self.instruction_pointer,
self.cycle_count,
failing_position_lhs,
self.op_stack.peek_at(failing_position_lhs),
self.op_stack.peek_at(failing_position_rhs),
)
}

fn instruction_add(&mut self) -> Result<Option<CoProcessorCall>> {
let lhs = self.op_stack.pop()?;
let rhs = self.op_stack.pop()?;
Expand Down Expand Up @@ -791,18 +804,6 @@ impl<'pgm> VMState<'pgm> {
maybe_memory_value.unwrap_or_else(BFieldElement::zero)
}

fn assert_vector(&self) -> bool {
for index in 0..DIGEST_LENGTH {
// Safe as long as 2 * DIGEST_LEN <= OpStackElement::COUNT
let lhs = index.try_into().unwrap();
let rhs = (index + DIGEST_LENGTH).try_into().unwrap();
if self.op_stack.peek_at(lhs) != self.op_stack.peek_at(rhs) {
return false;
}
}
true
}

fn pop_secret_digest(&mut self) -> Result<[BFieldElement; DIGEST_LENGTH]> {
let digest = self
.secret_digests
Expand Down

0 comments on commit ee83ab6

Please sign in to comment.