diff --git a/zokrates_cli/src/bin.rs b/zokrates_cli/src/bin.rs index 40153d08..c1ab68dc 100644 --- a/zokrates_cli/src/bin.rs +++ b/zokrates_cli/src/bin.rs @@ -380,21 +380,11 @@ fn main() { }) .collect(); - let witness_map = program_ast - .execute(arguments) + let witness = program_ast + .execute(&arguments) .unwrap_or_else(|e| panic!(format!("Execution failed: {}", e))); - println!( - "\nWitness: \n\n{}", - witness_map - .iter() - .filter_map(|(variable, value)| match variable { - variable if variable.is_output() => Some(format!("{} {}", variable, value)), - _ => None, - }) - .collect::>() - .join("\n") - ); + println!("\nWitness: \n\n{}", witness.format_outputs()); // write witness to file let output_path = Path::new(sub_matches.value_of("output").unwrap()); @@ -403,10 +393,7 @@ fn main() { Err(why) => panic!("couldn't create {}: {}", output_path.display(), why), }; let mut bw = BufWriter::new(output_file); - for (var, val) in &witness_map { - write!(&mut bw, "{} {}\n", var, val.to_dec_string()) - .expect("Unable to write data to file."); - } + write!(&mut bw, "{}", witness).expect("Unable to write data to file."); bw.flush().expect("Unable to flush buffer."); } #[cfg(feature = "libsnark")] @@ -656,7 +643,7 @@ mod tests { let (..) = r1cs_program(program_flattened.clone()); let _ = program_flattened - .execute(vec![FieldPrime::from(0)]) + .execute(&vec![FieldPrime::from(0)]) .unwrap(); } } @@ -690,7 +677,7 @@ mod tests { let result = std::panic::catch_unwind(|| { let _ = program_flattened - .execute(vec![FieldPrime::from(0)]) + .execute(&vec![FieldPrime::from(0)]) .unwrap(); }); assert!(result.is_err()); diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index 99c0f58a..f7b346b8 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -3,17 +3,57 @@ use ir::*; use std::collections::BTreeMap; use zokrates_field::field::Field; +pub type ExecutionResult = Result, Error>; + +pub struct Witness(BTreeMap); + +impl Witness { + pub fn return_values(&self) -> Vec { + self.0 + .clone() + .into_iter() + .filter(|(k, _)| k.is_output()) + .map(|(_, v)| v) + .collect() + } + + pub fn format_outputs(&self) -> String { + self.0 + .iter() + .filter_map(|(variable, value)| match variable { + variable if variable.is_output() => Some(format!("{} {}", variable, value)), + _ => None, + }) + .collect::>() + .join("\n") + } +} + +impl fmt::Display for Witness { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + self.0 + .iter() + .map(|(k, v)| format!("{} {}", k, v.to_dec_string())) + .collect::>() + .join("\n") + ) + } +} + impl Prog { - pub fn execute(self, inputs: Vec) -> Result, Error> { - let main = self.main; - assert_eq!(main.arguments.len(), inputs.len()); + pub fn execute + Clone>(&self, inputs: &Vec) -> ExecutionResult { + let main = &self.main; + self.check_inputs(&inputs)?; let mut witness = BTreeMap::new(); witness.insert(FlatVariable::one(), T::one()); for (arg, value) in main.arguments.iter().zip(inputs.iter()) { - witness.insert(arg.clone(), value.clone()); + witness.insert(arg.clone(), value.clone().into()); } - for statement in main.statements { + for statement in &main.statements { match statement { Statement::Constraint(quad, lin) => match lin.is_assignee(&witness) { true => { @@ -24,7 +64,10 @@ impl Prog { let lhs_value = quad.evaluate(&witness); let rhs_value = lin.evaluate(&witness); if lhs_value != rhs_value { - return Err(Error::Constraint(quad, lin, lhs_value, rhs_value)); + return Err(Error::UnsatisfiedConstraint { + left: lhs_value.to_dec_string(), + right: rhs_value.to_dec_string(), + }); } } }, @@ -44,7 +87,18 @@ impl Prog { } } - Ok(witness) + Ok(Witness(witness)) + } + + fn check_inputs(&self, inputs: &Vec) -> Result<(), Error> { + if self.main.arguments.len() == inputs.len() { + Ok(()) + } else { + Err(Error::WrongInputCount { + expected: self.main.arguments.len(), + received: inputs.len(), + }) + } } } @@ -69,21 +123,35 @@ impl QuadComb { } } -#[derive(PartialEq, Debug)] -pub enum Error { - Constraint(QuadComb, LinComb, T, T), +#[derive(PartialEq, Serialize, Deserialize)] +pub enum Error { + UnsatisfiedConstraint { left: String, right: String }, Solver, + WrongInputCount { expected: usize, received: usize }, } -impl fmt::Display for Error { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::Constraint(ref quad, ref lin, ref left_value, ref right_value) => write!( - f, - "Expected {} to equal {}, but {} != {}", - quad, lin, left_value, right_value - ), + Error::UnsatisfiedConstraint { + ref left, + ref right, + } => write!(f, "Expected {} to equal {}", left, right), Error::Solver => write!(f, ""), + Error::WrongInputCount { expected, received } => write!( + f, + "Program takes {} input{} but was passed {} value{}", + expected, + if expected == 1 { "" } else { "s" }, + received, + if received == 1 { "" } else { "s" } + ), } } } + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} diff --git a/zokrates_core/src/ir/mod.rs b/zokrates_core/src/ir/mod.rs index 3d81bed9..62b45cb1 100644 --- a/zokrates_core/src/ir/mod.rs +++ b/zokrates_core/src/ir/mod.rs @@ -13,6 +13,9 @@ mod interpreter; use self::expression::LinComb; use self::expression::QuadComb; +pub use self::interpreter::Error; +pub use self::interpreter::ExecutionResult; + #[derive(Debug, Serialize, Deserialize, Clone)] pub enum Statement { Constraint(QuadComb, LinComb), diff --git a/zokrates_core/tests/bench/add.code b/zokrates_core/tests/bench/add.code new file mode 100644 index 00000000..d2277712 --- /dev/null +++ b/zokrates_core/tests/bench/add.code @@ -0,0 +1,2 @@ +def main(field a, field b) -> (field): + return a + b \ No newline at end of file diff --git a/zokrates_core/tests/bench/add.json b/zokrates_core/tests/bench/add.json new file mode 100644 index 00000000..f7407d67 --- /dev/null +++ b/zokrates_core/tests/bench/add.json @@ -0,0 +1,27 @@ +{ + "tests": [ + { + "input": { + "values": ["1", "2"] + }, + "output": { + "Ok": { + "values": ["3"] + } + } + }, + { + "input": { + "values": ["1", "2", "42"] + }, + "output": { + "Err": { + "WrongInputCount": { + "expected": 2, + "received": 3 + } + } + } + } + ] +} \ No newline at end of file diff --git a/zokrates_core/tests/bench/assert_one.code b/zokrates_core/tests/bench/assert_one.code new file mode 100644 index 00000000..03dafa87 --- /dev/null +++ b/zokrates_core/tests/bench/assert_one.code @@ -0,0 +1,3 @@ +def main(field a) -> (field): + a == 1 + return 1 \ No newline at end of file diff --git a/zokrates_core/tests/bench/assert_one.json b/zokrates_core/tests/bench/assert_one.json new file mode 100644 index 00000000..5709a87f --- /dev/null +++ b/zokrates_core/tests/bench/assert_one.json @@ -0,0 +1,17 @@ +{ + "tests": [ + { + "input": { + "values": ["0"] + }, + "output": { + "Err": { + "UnsatisfiedConstraint": { + "left": "1", + "right": "0" + } + } + } + } + ] +} \ No newline at end of file diff --git a/zokrates_core/tests/integration.rs b/zokrates_core/tests/integration.rs new file mode 100644 index 00000000..af824ffc --- /dev/null +++ b/zokrates_core/tests/integration.rs @@ -0,0 +1,12 @@ +extern crate serde_json; +extern crate zokrates_core; +#[macro_use] +extern crate serde_derive; + +#[macro_use] +mod utils; + +zokrates_test! { + add, + assert_one, +} diff --git a/zokrates_core/tests/utils/mod.rs b/zokrates_core/tests/utils/mod.rs new file mode 100644 index 00000000..b66ce26d --- /dev/null +++ b/zokrates_core/tests/utils/mod.rs @@ -0,0 +1,120 @@ +extern crate serde_json; + +use std::io; +use zokrates_core::compile::{compile as generic_compile, CompileError}; +use zokrates_core::field::{Field, FieldPrime}; +use zokrates_core::ir; + +#[derive(Serialize, Deserialize)] +pub struct Tests { + pub tests: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Input { + pub values: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Test { + pub input: Input, + pub output: TestResult, +} + +pub type TestResult = Result; + +#[derive(PartialEq, Debug)] +pub struct ComparableResult(Result, ir::Error>); + +#[derive(Serialize, Deserialize)] +pub struct Output { + values: Vec, +} + +type Val = String; + +impl From> for ComparableResult { + fn from(r: ir::ExecutionResult) -> ComparableResult { + ComparableResult(r.map(|v| v.return_values())) + } +} + +impl From for ComparableResult { + fn from(r: TestResult) -> ComparableResult { + ComparableResult(r.map(|v| { + v.values + .into_iter() + .map(|v| FieldPrime::from_dec_string(v)) + .collect() + })) + } +} + +pub fn compare( + result: ir::ExecutionResult, + expected: TestResult, +) -> Result<(), String> { + // extract outputs from result + let result = ComparableResult::from(result); + // deserialize expected result + let expected = ComparableResult::from(expected); + + if result != expected { + return Err(format!( + "Expected {:?} but found {:?}", + expected.0, result.0 + )); + } + + Ok(()) +} + +pub fn read_file(path: &str) -> String { + use std::fs::File; + use std::io::Read; + let mut file = File::open(format!("./tests/bench/{}", path)).expect("Unable to open the file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Unable to read the file"); + + contents +} + +pub fn compile(code: &str) -> Result, CompileError> { + generic_compile::(&mut code.as_bytes(), None, None) +} + +macro_rules! zokrates_test { + ($($name:ident,)*) => { + $( + #[test] + fn $name() { + + use zokrates_core::field::{FieldPrime, Field}; + + let code_string = $crate::utils::read_file(&format!("./{}.code", stringify!($name))); + let test_string = $crate::utils::read_file(&format!("./{}.json", stringify!($name))); + + let bin = $crate::utils::compile(&code_string).unwrap(); + + let t: $crate::utils::Tests = serde_json::from_str(&test_string).unwrap(); + + for test in t.tests.into_iter() { + let input = &test.input.values; + let output = bin.execute(&input.iter().map(|v| FieldPrime::from_dec_string(v.clone())).collect()); + + let context = format!(" +{} + +Called with input ({}) + ", code_string, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); + + match $crate::utils::compare(output, test.output) { + Err(e) => panic!("{}{}", context, e), + Ok(..) => {} + }; + } + } + )* + }; +}