Browse Source

zkas: Add zk circuit compiler.

parazyd 4 years ago
parent
commit
4999914663
11 changed files with 993 additions and 0 deletions
  1. 136 0
      zkas/Cargo.lock
  2. 11 0
      zkas/Cargo.toml
  3. 33 0
      zkas/Makefile
  4. 53 0
      zkas/src/bin/zkas.rs
  5. 142 0
      zkas/src/compiler.rs
  6. 6 0
      zkas/src/lib.rs
  7. 84 0
      zkas/src/output.rs
  8. 288 0
      zkas/src/parser.rs
  9. 53 0
      zkas/src/state.rs
  10. 67 0
      zkas/src/tracer.rs
  11. 120 0
      zkas/src/types.rs

+ 136 - 0
zkas/Cargo.lock

@@ -0,0 +1,136 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "varuint"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "896414bb0f21aba00ff26b0a8a3679331b5a86886f217c9d9396b919a7bf4ac6"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zkas"
+version = "0.0.1"
+dependencies = [
+ "anyhow",
+ "clap",
+ "lazy_static",
+ "varuint",
+]

+ 11 - 0
zkas/Cargo.toml

@@ -0,0 +1,11 @@
+[package]
+name = "zkas"
+version = "0.0.1"
+authors = ["darkfi <dev@dark.fi"]
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.44"
+clap = "2.33.3"
+lazy_static = "1.4.0"
+varuint = "0.6.0"

+ 33 - 0
zkas/Makefile

@@ -0,0 +1,33 @@
+.POSIX:
+
+CARGO = cargo
+
+BIN = zkas
+
+# Dependencies which should force the binaries to be rebuilt
+BINDEPS = \
+	Cargo.toml \
+	$(shell find src -type f)
+
+all: $(BIN)
+
+$(BIN): $(BINDEPS)
+	$(CARGO) build --release --all-features --bin $@
+	cp target/release/$@ $@
+
+test:
+	$(CARGO) test --release --all-features
+
+fix:
+	$(CARGO) fix --release --all-features --allow-dirty
+
+clippy:
+	$(CARGO) clippy --release --all-features
+
+clean:
+	rm -f $(BIN)
+
+distclean: clean
+	rm -rf target
+
+.PHONY: all test fix clippy clean distclean

+ 53 - 0
zkas/src/bin/zkas.rs

@@ -0,0 +1,53 @@
+use anyhow::Result;
+use clap::clap_app;
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+
+use zkas::compiler::{CompiledContract, Compiler};
+use zkas::output::{bincode_output, text_output};
+use zkas::parser::{load_lines, parse_lines};
+
+fn main() -> Result<()> {
+    let args = clap_app!(zkas =>
+        (@arg INPUT: +required "ZK script to compile")
+        (@arg DISPLAY: -d --display "Show the compiled code in human readable format")
+        (@arg OUTPUT: -o --output +takes_value "Output file")
+    )
+    .get_matches();
+
+    let file = File::open(args.value_of("INPUT").unwrap())?;
+    let lines = load_lines(BufReader::new(file).lines());
+    //println!("{:#?}", lines);
+    let syntax = parse_lines(lines);
+    //println!("{:#?}", syntax);
+    let schema = syntax.verify();
+    //println!("{:#?}", schema);
+
+    let mut contracts = vec![];
+    for (name, witness, uncompiled_code) in schema {
+        let compiler = Compiler::new(witness.clone(), uncompiled_code, syntax.constants.clone());
+        let code = compiler.compile();
+        //println!("{:#?}", code);
+        contracts.push(CompiledContract::new(name, witness, code));
+    }
+
+    if args.is_present("DISPLAY") {
+        text_output(contracts.clone(), syntax.constants.clone())?;
+    }
+
+    let output_file = if args.is_present("OUTPUT") {
+        match args.value_of("OUTPUT").unwrap() {
+            "-" => {
+                println!("Unable to output compiled code to stdout");
+                std::process::exit(1);
+            }
+            v => v.to_string(),
+        }
+    } else {
+        format!("{}.bin", args.value_of("INPUT").unwrap())
+    };
+
+    bincode_output(&output_file, contracts, syntax.constants)?;
+
+    Ok(())
+}

+ 142 - 0
zkas/src/compiler.rs

@@ -0,0 +1,142 @@
+use std::collections::HashMap;
+
+use crate::parser::{SchemaCode, SchemaWitness};
+use crate::state::{Constants, Line};
+use crate::types::{FuncFormat, TypeId};
+
+#[derive(Debug, Clone)]
+pub struct CodeLine {
+    pub func_format: FuncFormat,
+    pub return_values: Vec<String>,
+    pub args: Vec<String>,
+    pub arg_idxs: Vec<usize>,
+    pub code_line: Line,
+}
+
+impl CodeLine {
+    pub fn new(
+        func_format: FuncFormat,
+        return_values: Vec<String>,
+        args: Vec<String>,
+        arg_idxs: Vec<usize>,
+        code_line: Line,
+    ) -> Self {
+        CodeLine {
+            func_format,
+            return_values,
+            args,
+            arg_idxs,
+            code_line,
+        }
+    }
+}
+
+fn alloc(
+    stacks: &mut Vec<Vec<String>>,
+    stack_vars: &mut HashMap<String, (TypeId, usize)>,
+    variable: String,
+    type_id: TypeId,
+) {
+    assert!(type_id as usize <= stacks.len());
+    let idx = stacks[type_id as usize].len();
+    // Add variable to the stack for its TypeId
+    stacks[type_id as usize].push(variable.clone());
+    // Create mapping from variable name
+    stack_vars.insert(variable, (type_id, idx));
+}
+
+pub struct Compiler {
+    pub witness: SchemaWitness,
+    pub uncompiled_code: SchemaCode,
+    pub constants: Constants,
+}
+
+impl Compiler {
+    pub fn new(witness: SchemaWitness, uncompiled_code: SchemaCode, constants: Constants) -> Self {
+        Compiler {
+            witness,
+            uncompiled_code,
+            constants,
+        }
+    }
+
+    pub fn compile(&self) -> Vec<CodeLine> {
+        let mut code = vec![];
+
+        // Each unique TypeID has its own stack
+        let mut stacks = vec![];
+        let mut stack: Vec<String>;
+        for _ in 0..TypeId::LastId as usize {
+            stack = vec![];
+            stacks.push(stack);
+        }
+
+        // Map from variable name to stacks above
+        let mut stack_vars = HashMap::new();
+
+        // Load constants
+        for variable in self.constants.variables() {
+            let type_id = self.constants.lookup(variable.to_string());
+            alloc(&mut stacks, &mut stack_vars, variable.to_string(), type_id);
+        }
+
+        // Preload stack with our witness values
+        for (type_id, variable, _line) in self.witness.clone() {
+            alloc(&mut stacks, &mut stack_vars, variable.to_string(), type_id);
+        }
+
+        for (func_format, retvals, args, code_line) in &self.uncompiled_code {
+            assert!(args.len() == func_format.param_types.len());
+
+            let mut arg_idxs = vec![];
+
+            // Loop through all arguments
+            for (variable, type_id) in args.iter().zip(func_format.param_types.iter()) {
+                assert!(*type_id as usize <= stacks.len());
+                assert!(stack_vars.contains_key(variable));
+                // Find the index for the M by N matrix of our variable
+                let (loc_type_id, loc_idx) = stack_vars.get(variable).unwrap();
+                assert!(type_id == loc_type_id);
+                assert!(&stacks[*loc_type_id as usize][*loc_idx as usize] == variable);
+
+                // This is the info to be serialized, not the variable names
+                arg_idxs.push(*loc_idx);
+            }
+
+            assert!(retvals.len() == func_format.return_type_ids.len());
+
+            for (retval, ret_id) in retvals.iter().zip(func_format.return_type_ids.iter()) {
+                // Allocate returned values so they can be used by
+                // subsequent function calls.
+                alloc(&mut stacks, &mut stack_vars, retval.to_string(), *ret_id);
+            }
+
+            code.push(CodeLine::new(
+                func_format.clone(),
+                retvals.clone(),
+                args.clone(),
+                arg_idxs,
+                code_line.clone(),
+            ));
+        }
+
+        code
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct CompiledContract {
+    pub name: String,
+    pub witness: SchemaWitness,
+    pub code: Vec<CodeLine>,
+}
+
+impl CompiledContract {
+    pub fn new(name: String, witness: SchemaWitness, code: Vec<CodeLine>) -> Self {
+        CompiledContract {
+            name,
+            witness,
+            code,
+        }
+    }
+}

+ 6 - 0
zkas/src/lib.rs

@@ -0,0 +1,6 @@
+pub mod compiler;
+pub mod output;
+pub mod parser;
+pub(crate) mod state;
+pub(crate) mod tracer;
+pub(crate) mod types;

+ 84 - 0
zkas/src/output.rs

@@ -0,0 +1,84 @@
+use anyhow::Result;
+use std::fs::File;
+use std::io::{stdout, Write};
+use varuint::*;
+
+use crate::compiler::CompiledContract;
+use crate::state::Constants;
+
+pub fn text_output(contracts: Vec<CompiledContract>, constants: Constants) -> Result<()> {
+    let mut f = stdout();
+    f.write_all(b"Constants\n")?;
+
+    for variable in constants.variables() {
+        let type_id = constants.lookup(variable.to_string());
+        f.write_all(format!("  {:#?} {}\n", type_id, variable).as_bytes())?;
+    }
+
+    for contract in contracts {
+        f.write_all(format!("{}:\n", contract.name).as_bytes())?;
+
+        f.write_all(b"  Witness:\n")?;
+        for (type_id, variable, _) in contract.witness {
+            f.write_all(format!("    {:#?} {}\n", type_id, variable).as_bytes())?;
+        }
+
+        f.write_all(b"  Code:\n")?;
+        for code in contract.code {
+            f.write_all(format!("    # args = {:?}\n", code.args).as_bytes())?;
+            f.write_all(
+                format!(
+                    "    {:?} {:?} {:?}\n",
+                    code.func_format.func_id, code.return_values, code.arg_idxs
+                )
+                .as_bytes(),
+            )?;
+        }
+    }
+
+    Ok(())
+}
+
+pub fn bincode_output(
+    filename: &str,
+    contracts: Vec<CompiledContract>,
+    constants: Constants,
+) -> Result<()> {
+    //let mut cursor = Cursor::new(vec![]);
+    let mut cursor = File::create(filename)?;
+
+    cursor.write_varint(constants.variables().len() as u64)?;
+    for variable in constants.variables() {
+        cursor.write_varint(variable.len() as u64)?;
+        let _ = cursor.write(variable.as_bytes())?;
+        let type_id = constants.lookup(variable.to_string());
+        let _ = cursor.write(&[type_id as u8])?;
+    }
+
+    cursor.write_varint(contracts.len() as u64)?;
+    for contract in contracts {
+        cursor.write_varint(contract.name.len() as u64)?;
+        let _ = cursor.write(contract.name.as_bytes())?;
+
+        cursor.write_varint(contract.witness.len() as u64)?;
+        for (type_id, variable, _) in contract.witness {
+            cursor.write_varint(variable.len() as u64)?;
+            let _ = cursor.write(variable.as_bytes())?;
+            let _ = cursor.write(&[type_id as u8])?;
+        }
+
+        cursor.write_varint(contract.code.len() as u64)?;
+        for code in contract.code {
+            let func_id = code.func_format.func_id;
+            let _ = cursor.write(&[func_id as u8])?;
+
+            for arg_idx in code.arg_idxs {
+                let _ = cursor.write(&(arg_idx as u32).to_le_bytes())?;
+            }
+        }
+    }
+
+    //cursor.set_position(0);
+
+    Ok(())
+}

+ 288 - 0
zkas/src/parser.rs

@@ -0,0 +1,288 @@
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::io;
+
+use crate::state::{Constants, Line};
+use crate::tracer::DynamicTracer;
+use crate::types::{FuncFormat, TypeId, ALLOWED_TYPES, FUNCTION_FORMATS};
+
+pub type SchemaWitness = Vec<(TypeId, String, Line)>;
+pub type SchemaCode = Vec<(FuncFormat, Vec<String>, Vec<String>, Line)>;
+pub type Schema = Vec<(String, SchemaWitness, SchemaCode)>;
+
+#[derive(Default, Debug)]
+pub struct Syntax {
+    pub contracts: HashMap<String, Vec<Line>>,
+    pub circuits: HashMap<String, Vec<Line>>,
+    pub constants: Constants,
+}
+
+impl Syntax {
+    fn new() -> Self {
+        Syntax {
+            contracts: HashMap::new(),
+            circuits: HashMap::new(),
+            constants: Constants::new(),
+        }
+    }
+
+    fn parse_contract(&mut self, line: Line, iter: &mut std::slice::Iter<'_, Line>) {
+        assert!(line.tokens[0] == "contract");
+
+        if line.tokens.len() != 3 || line.tokens[2] != "{" {
+            println!("parser error: malformed contract opening");
+            panic!("{:?}", line);
+        }
+
+        let name = line.tokens[1].clone();
+        if self.contracts.contains_key(name.as_str()) {
+            println!("parser error: duplicate contract '{}", name);
+            panic!("{:?}", line);
+        }
+
+        let mut lines: Vec<Line> = vec![];
+
+        loop {
+            let ln = iter.next();
+            if ln.is_none() {
+                println!("parser error: unexpected eof parsing contract '{}'", name);
+                panic!("{:?}", line);
+            }
+            let ln = ln.unwrap();
+
+            assert!(!ln.tokens.is_empty());
+            if ln.tokens[0] == "}" {
+                break;
+            }
+
+            lines.push(ln.clone());
+        }
+
+        self.contracts.insert(name, lines);
+    }
+
+    fn parse_circuit(&mut self, line: Line, iter: &mut std::slice::Iter<'_, Line>) {
+        assert!(line.tokens[0] == "circuit");
+
+        if line.tokens.len() != 3 || line.tokens[2] != "{" {
+            println!("parser error: malformed circuit opening");
+            panic!("{:?}", line);
+        }
+
+        let name = line.tokens[1].clone();
+        if self.circuits.contains_key(name.as_str()) {
+            println!("parser error: duplicate circuit '{}'", name);
+            panic!("{:?}", line);
+        }
+
+        let mut lines: Vec<Line> = vec![];
+
+        loop {
+            let ln = iter.next();
+            if ln.is_none() {
+                println!("parser error: unexpected eof parsing circuit '{}'", name);
+                panic!("{:?}", line);
+            }
+            let ln = ln.unwrap();
+
+            assert!(!ln.tokens.is_empty());
+            if ln.tokens[0] == "}" {
+                break;
+            }
+
+            lines.push(ln.clone());
+        }
+
+        self.circuits.insert(name, lines);
+    }
+
+    fn parse_constant(&mut self, line: Line) {
+        assert!(line.tokens[0] == "constant");
+
+        if line.tokens.len() != 3 {
+            println!("parser error: malformed constant line");
+            panic!("{:?}", line);
+        }
+
+        let type_name = line.tokens[1].clone();
+        let variable = line.tokens[2].clone();
+
+        if !ALLOWED_TYPES.contains_key(type_name.as_str()) {
+            println!("parser error: unknown type '{}'", type_name);
+            panic!("{:?}", line);
+        }
+
+        if let Some(type_id) = ALLOWED_TYPES.get(type_name.as_str()) {
+            self.constants.add(variable, *type_id);
+            return;
+        }
+
+        unreachable!();
+    }
+
+    fn static_checks(&self) {
+        for (_, lines) in self.contracts.iter() {
+            for line in lines {
+                if line.tokens.len() != 2 {
+                    println!("parser error: incorrect number of tokens");
+                    panic!("{:?}", line);
+                }
+
+                let type_name = &line.tokens[0];
+                let variable = &line.tokens[1];
+
+                if !ALLOWED_TYPES.contains_key(type_name.as_str()) {
+                    println!("parser error: unknown type for variable '{}'", variable);
+                    panic!("{:?}", line);
+                }
+            }
+        }
+
+        for (_, lines) in self.circuits.iter() {
+            for line in lines {
+                assert!(!line.tokens.is_empty());
+                let func_name = &line.tokens[0];
+                let args = &line.tokens[1..];
+
+                if !FUNCTION_FORMATS.contains_key(func_name.as_str()) {
+                    println!("parser error: unknown function call '{}'", func_name);
+                    panic!("{:?}", line);
+                }
+
+                let func_format = FUNCTION_FORMATS.get(func_name.as_str()).unwrap();
+
+                if args.len() != func_format.total_arguments() {
+                    println!(
+                        "parser error: incorrect num of args for '{}' function call",
+                        func_name
+                    );
+                    panic!("{:?}", line);
+                }
+            }
+        }
+
+        // Finally check there are matching circuits and contracts.
+        let circuits: HashSet<_> = self.circuits.keys().collect();
+        let contracts: HashSet<_> = self.contracts.keys().collect();
+        for n in circuits.union(&contracts) {
+            if !self.contracts.contains_key(*n) {
+                panic!("missing contract for '{}'", n);
+            }
+            if !self.circuits.contains_key(*n) {
+                panic!("missing circuit for '{}'", n);
+            }
+        }
+    }
+
+    fn format_data(&self) -> Schema {
+        let mut schema = vec![];
+
+        for (name, circuit) in self.circuits.iter() {
+            assert!(self.contracts.contains_key(name));
+            let contract = self.contracts.get(name).unwrap();
+
+            let mut witness = vec![];
+            for line in contract {
+                assert!(line.tokens.len() == 2);
+                let type_name = &line.tokens[0];
+                let variable = &line.tokens[1];
+                assert!(ALLOWED_TYPES.contains_key(type_name.as_str()));
+                let type_id = ALLOWED_TYPES.get(type_name.as_str()).unwrap();
+                witness.push((*type_id, variable.to_string(), line.clone()));
+            }
+
+            let mut code = vec![];
+            for line in circuit {
+                assert!(!line.tokens.is_empty());
+                let func_name = &line.tokens[0];
+                let mut args = &line.tokens[1..];
+                assert!(FUNCTION_FORMATS.contains_key(func_name.as_str()));
+                let func_format = FUNCTION_FORMATS.get(func_name.as_str()).unwrap();
+                assert!(args.len() == func_format.total_arguments());
+
+                let mut retvals = vec![];
+                if !func_format.return_type_ids.is_empty() {
+                    let rv_len = func_format.return_type_ids.len();
+                    retvals.extend_from_slice(&args[..rv_len]);
+                    args = &args[rv_len..];
+                }
+
+                // let _func_id = func_format.func_id;
+                code.push((func_format.clone(), retvals, args.to_vec(), line.clone()));
+            }
+
+            schema.push((name.clone(), witness, code));
+        }
+
+        schema
+    }
+
+    fn trace_circuits(&self, schema: Schema) {
+        for (name, witness, code) in schema {
+            let tracer = DynamicTracer::new(name, witness, code, self.constants.clone());
+            tracer.execute();
+        }
+    }
+
+    pub fn verify(&self) -> Schema {
+        self.static_checks();
+        let schema = self.format_data();
+        self.trace_circuits(schema.clone());
+        schema
+    }
+}
+
+pub fn load_lines(lines: io::Lines<io::BufReader<File>>) -> Vec<Line> {
+    let mut source = vec![];
+
+    for (n, original_line) in lines.enumerate() {
+        if let Ok(ogline) = original_line {
+            let line_number = n + 1;
+
+            // Remove whitespace on both sides
+            let orig = ogline.clone();
+            let line = orig.trim_start().trim_end();
+
+            // Strip out comments
+            let spl: Vec<&str> = line.split('#').collect();
+            if spl[0].is_empty() {
+                continue;
+            }
+
+            // Split at whitespace
+            let spl: Vec<String> = spl[0].split(' ').map(|s| s.to_string()).collect();
+
+            source.push(Line::new(spl, line.to_string(), line_number as u32));
+        }
+    }
+
+    source
+}
+
+pub fn parse_lines(lines: Vec<Line>) -> Syntax {
+    let mut syntax = Syntax::new();
+    let mut iter = lines.iter();
+
+    loop {
+        let line = iter.next();
+        if line.is_none() {
+            break;
+        }
+
+        let line = line.unwrap();
+        assert!(!line.tokens.is_empty());
+
+        match line.tokens[0].as_str() {
+            "contract" => syntax.parse_contract(line.clone(), &mut iter),
+            "circuit" => syntax.parse_circuit(line.clone(), &mut iter),
+            "constant" => syntax.parse_constant(line.clone()),
+            "}" => {
+                println!("unmatched delimiter '}}'");
+                panic!("{:?}", line);
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    syntax
+}

+ 53 - 0
zkas/src/state.rs

@@ -0,0 +1,53 @@
+use std::collections::{hash_map::Keys, HashMap};
+
+use crate::types::TypeId;
+
+#[derive(Debug, Clone)]
+pub struct Line {
+    pub tokens: Vec<String>,
+    pub orig: String,
+    pub number: u32,
+}
+
+impl Line {
+    pub fn new(tokens: Vec<String>, orig: String, number: u32) -> Self {
+        Line {
+            tokens,
+            orig,
+            number,
+        }
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct Constants {
+    pub table: Vec<TypeId>,
+    pub map: HashMap<String, usize>,
+}
+
+impl Constants {
+    pub fn new() -> Self {
+        Constants {
+            table: vec![],
+            map: HashMap::new(),
+        }
+    }
+
+    pub fn add(&mut self, variable: String, type_id: TypeId) {
+        let idx = self.table.len();
+        self.table.push(type_id);
+        self.map.insert(variable, idx);
+    }
+
+    pub fn lookup(&self, variable: String) -> TypeId {
+        if let Some(idx) = self.map.get(variable.as_str()) {
+            return self.table[*idx];
+        }
+
+        panic!();
+    }
+
+    pub fn variables(&self) -> Keys<'_, String, usize> {
+        self.map.keys()
+    }
+}

+ 67 - 0
zkas/src/tracer.rs

@@ -0,0 +1,67 @@
+use std::collections::HashMap;
+
+use crate::parser::{SchemaCode, SchemaWitness};
+use crate::state::Constants;
+
+pub struct DynamicTracer {
+    //name: String,
+    witness: SchemaWitness,
+    code: SchemaCode,
+    constants: Constants,
+}
+
+impl DynamicTracer {
+    pub fn new(
+        _name: String,
+        witness: SchemaWitness,
+        code: SchemaCode,
+        constants: Constants,
+    ) -> Self {
+        DynamicTracer {
+            //name,
+            witness,
+            code,
+            constants,
+        }
+    }
+
+    pub fn execute(&self) {
+        let mut stack = HashMap::new();
+
+        // Load constants
+        for variable in self.constants.variables() {
+            stack.insert(variable, self.constants.lookup(variable.to_string()));
+        }
+
+        // Preload stack with our witness values
+        for (type_id, variable, _) in &self.witness {
+            stack.insert(&variable, *type_id);
+        }
+
+        for (func_format, retvals, args, code_line) in &self.code {
+            assert!(args.len() == func_format.param_types.len());
+
+            for (variable, type_id) in args.iter().zip(func_format.param_types.iter()) {
+                if !stack.contains_key(variable) {
+                    println!("variable '{}' is not defined", variable);
+                    panic!("{:?}", code_line);
+                }
+
+                let stack_type_id = stack.get(variable).unwrap();
+                if stack_type_id != type_id {
+                    println!("variable '{}' has incorrect type", variable);
+                    println!("found '{:?}' but expected '{:?}'", stack_type_id, type_id);
+                    panic!("{:?}", code_line);
+                }
+
+                assert!(retvals.len() == func_format.return_type_ids.len());
+                for (retval, ret_id) in retvals.iter().zip(func_format.return_type_ids.iter()) {
+                    // Note that later variables shadow earlier ones.
+                    // We accept this.
+
+                    stack.insert(retval, *ret_id);
+                }
+            }
+        }
+    }
+}

+ 120 - 0
zkas/src/types.rs

@@ -0,0 +1,120 @@
+use lazy_static::lazy_static;
+use std::collections::HashMap;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum TypeId {
+    Base,
+    Scalar,
+    EcPoint,
+    EcFixedPoint,
+    LastId,
+}
+
+#[derive(Debug, Clone)]
+pub enum FuncId {
+    PoseidonHash,
+    Add,
+    ConstrainInstance,
+    EcMulShort,
+    EcMul,
+    EcAdd,
+    EcGetX,
+    EcGetY,
+}
+
+lazy_static! {
+    pub static ref ALLOWED_TYPES: HashMap<&'static str, TypeId> = {
+        let mut map = HashMap::new();
+
+        map.insert("Base", TypeId::Base);
+        map.insert("Scalar", TypeId::Scalar);
+        map.insert("EcFixedPoint", TypeId::EcFixedPoint);
+
+        map
+    };
+}
+
+#[derive(Debug, Clone)]
+pub struct FuncFormat {
+    pub func_id: FuncId,
+    pub return_type_ids: Vec<TypeId>,
+    pub param_types: Vec<TypeId>,
+}
+
+impl FuncFormat {
+    pub fn new(func_id: FuncId, return_type_ids: &[TypeId], param_types: &[TypeId]) -> Self {
+        FuncFormat {
+            func_id,
+            return_type_ids: return_type_ids.to_vec(),
+            param_types: param_types.to_vec(),
+        }
+    }
+
+    pub fn total_arguments(&self) -> usize {
+        self.return_type_ids.len() + self.param_types.len()
+    }
+}
+
+lazy_static! {
+    pub static ref FUNCTION_FORMATS: HashMap<&'static str, FuncFormat> = {
+        let mut map = HashMap::new();
+
+        map.insert(
+            "poseidon_hash",
+            FuncFormat::new(
+                FuncId::PoseidonHash,
+                &[TypeId::Base],
+                &[TypeId::Base, TypeId::Base],
+            ),
+        );
+
+        map.insert(
+            "add",
+            FuncFormat::new(FuncId::Add, &[TypeId::Base], &[TypeId::Base, TypeId::Base]),
+        );
+
+        map.insert(
+            "constrain_instance",
+            FuncFormat::new(FuncId::ConstrainInstance, &[], &[TypeId::Base]),
+        );
+
+        map.insert(
+            "ec_mul_short",
+            FuncFormat::new(
+                FuncId::EcMulShort,
+                &[TypeId::EcPoint],
+                &[TypeId::Base, TypeId::EcFixedPoint],
+            ),
+        );
+
+        map.insert(
+            "ec_mul",
+            FuncFormat::new(
+                FuncId::EcMul,
+                &[TypeId::EcPoint],
+                &[TypeId::Scalar, TypeId::EcFixedPoint],
+            ),
+        );
+
+        map.insert(
+            "ec_add",
+            FuncFormat::new(
+                FuncId::EcAdd,
+                &[TypeId::EcPoint],
+                &[TypeId::EcPoint, TypeId::EcPoint],
+            ),
+        );
+
+        map.insert(
+            "ec_get_x",
+            FuncFormat::new(FuncId::EcGetX, &[TypeId::Base], &[TypeId::EcPoint]),
+        );
+
+        map.insert(
+            "ec_get_y",
+            FuncFormat::new(FuncId::EcGetY, &[TypeId::Base], &[TypeId::EcPoint]),
+        );
+
+        map
+    };
+}