From 7e10d070b3b009bd7d0829ef370eae3cf3ca7a3b Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Tue, 18 Jun 2024 22:05:01 +0700 Subject: [PATCH] upload code --- .gitignore | 1 + Cargo.lock | 219 +++++++++ Cargo.toml | 11 + src/execution.rs | 1112 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 91 ++++ src/zkasm.pest | 269 +++++++++++ src/zkasm.rs | 623 ++++++++++++++++++++++++++ test.zkasm | 43 ++ 8 files changed, 2369 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/execution.rs create mode 100644 src/main.rs create mode 100644 src/zkasm.pest create mode 100644 src/zkasm.rs create mode 100644 test.zkasm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8dd4ee2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,219 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "pest" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "zkevm" +version = "0.1.0" +dependencies = [ + "either", + "pest", + "pest_derive", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3116a18 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "zkevm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +either = "1.9.0" +pest = "2.7.6" +pest_derive = "2.7.6" diff --git a/src/execution.rs b/src/execution.rs new file mode 100644 index 0000000..27306b3 --- /dev/null +++ b/src/execution.rs @@ -0,0 +1,1112 @@ +// we try to run the program instruction by instruction +// and try to map between variables and registers + +use std::collections::HashMap; +use std::fmt; + +use crate::zkasm::*; + +#[derive(Debug, Clone)] +pub enum VariableAbstraction { + FreeInput(String), // abstract to a free expression + Variable(String), // abstract to a variable with name + Value(u64), // abstract to a defined value + Expr(Expr), // abstract to an expression +} + +impl fmt::Display for VariableAbstraction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + VariableAbstraction::FreeInput(free) => { + write!(f, "{}", free) + } + VariableAbstraction::Value(v) => { + write!(f, "{}", v) + } + VariableAbstraction::Variable(v) => { + write!(f, "{}", v) + } + VariableAbstraction::Expr(expr) => { + write!(f, "{}", expr) + } + } + } +} + +impl VariableAbstraction { + pub fn is_freeinput(&self) -> bool { + match self { + Self::FreeInput(_) => true, + _ => false, + } + } + + pub fn as_expr(self) -> Expr { + match self { + VariableAbstraction::Value(v) => Expr::Value(v), + VariableAbstraction::Variable(v) => Expr::NameVariable(v), + VariableAbstraction::FreeInput(f) => Expr::FreeInput(f), + VariableAbstraction::Expr(e) => e, + } + } +} + +// each context has +// - a mapping between register and variable (could be symbolic) +// - a mapping between variable and its value +// - a list of all variables +// +// this context is used to deduce what variable is being used +// in the instruction +#[derive(Debug)] +pub struct Context { + // TODO: merge register mapping -> variable mapping or something + // because I first designed only register mapping, + // "what register is pointing to" + // but I label everything (naming variable), and casually + // point registers to labels (variables) to ease the stress of long + // expression when printing + // the value inside the variable is blurred, and thus we + // need another variable mapping, variable -> evalulated (or not) value + // however, due to time, and complexity, + // a full evaluation context is impossible to be done in the current time + // (would be equivalent to running the whole code) + // so this variable mapping is only for very few cases + register_mapping: HashMap>, + variable_mapping: HashMap>, + // TODO: freeinput is not easy to deal with + freeinput_holder: Option, + has_freeinput: bool, // has free $ but does not have register for freeinput + // any expr is calculated but not saved in any register + immediate_result: Option<(Option, Box)>, +} + +impl Context { + pub fn new() -> Self { + let mut ctx = Self { + register_mapping: HashMap::new(), + variable_mapping: HashMap::new(), + freeinput_holder: None, + has_freeinput: false, + immediate_result: None, + }; + // set predefined registers so that it will always have a value and not generate a new + // variable when requested + ctx.register_mapping.insert( + Register::RR, + Box::new(VariableAbstraction::Variable("RR".into())), + ); + ctx + } +} + +// a name generator for variable +pub struct VariableGenerator { + counter: u32, +} + +impl VariableGenerator { + pub fn new() -> Self { + Self { counter: 0 } + } + + pub fn gen(self: &mut Self, prefix: &str) -> String { + // println!("generate var{}", self.counter); + let name = prefix.to_owned() + "_var" + &self.counter.to_string(); + self.counter += 1; + return name; + } +} + +pub struct Execution { + context: Context, + vargen: VariableGenerator, + fallthrough: bool, +} + +impl Execution { + pub fn new() -> Self { + Self { + context: Context::new(), + vargen: VariableGenerator::new(), + fallthrough: true, + } + } + + fn rename_expr(&mut self, expr: Expr) -> Expr { + match expr { + Expr::Unary(op, expr) => Expr::Unary(op, Box::new(self.rename_expr(*expr))), + Expr::Binary(lhs, op, rhs) => Expr::Binary( + Box::new(self.rename_expr(*lhs)), + op, + Box::new(self.rename_expr(*rhs)), + ), + Expr::Tenary(expr, ifbranch, elsebranch) => Expr::Tenary( + Box::new(self.rename_expr(*expr)), + Box::new(self.rename_expr(*ifbranch)), + Box::new(self.rename_expr(*elsebranch)), + ), + Expr::Register(r) => { + let v = self.get_register(r); + if let VariableAbstraction::Variable(var) = v.as_ref() { + Expr::NameVariable(var.into()) + } else if let VariableAbstraction::Expr(e) = v.as_ref() { + e.clone() + } else if let VariableAbstraction::Value(value) = v.as_ref() { + Expr::Value(*value) + } else { + println!("renaming expr {} with registry value {}", expr, v); + unreachable!() + } + } + e => e, + } + } + + pub fn eval(&mut self, expr: &Expr) -> Box { + match expr { + Expr::Register(r) => self.get_register(*r).clone(), + Expr::Value(v) => Box::new(VariableAbstraction::Value(*v)), + Expr::FreeInput(f) => { + if f.starts_with("$${") { + println!(" JavascriptExecute {}", expr); + } + Box::new(VariableAbstraction::FreeInput(f.into())) + }, + Expr::NameVariable(name) => Box::new(VariableAbstraction::Variable(name.into())), + e => { + // TODO: loop inside and replaces all Register into variable abstraction + // because we reuse Expr from zkasm, so let use this as NameVariable + Box::new(VariableAbstraction::Expr(self.rename_expr(e.clone()))) + } + } + } + + fn add_variable_value(&mut self, name: Box, value: &Box) { + if let VariableAbstraction::Variable(ref n) = *name { + self.context.variable_mapping.insert(n.clone(), value.clone()); + } else { + println!("Can only add variable value to variable label"); + unreachable!(); + } + } + fn get_variable(&mut self, name: &str) -> Option<&Box> { + self.context.variable_mapping.get(name) + } + + fn set_register(&mut self, register: Register, v: Box) { + self.context.register_mapping.insert(register.clone(), v); + } + fn get_register(&mut self, register: Register) -> &mut Box { + // try to get a variable, if it is not found + // then assume that it is an input and insert this assumption in + // else return the variable abstraction + match self.context.register_mapping.get(®ister) { + None => { + let var = if register.is_special() { + let name = register.name(); + Box::new(VariableAbstraction::Variable(name)) + } else { + let name = self.vargen.gen("input"); + println!(" let {} = reg:{}", name, register); + Box::new(VariableAbstraction::Variable(name)) + }; + self.context.register_mapping.insert( + register.clone(), var, + ); + } + _ => {} + }; + self.context.register_mapping.get_mut(®ister).unwrap() + } + fn new_variable(&mut self, register: Register, prefix: &str) -> &mut Box { + // ignore whatever the register is currently holding and replaces + // the register with a new variable + + // However, there are few cases where we would not want to create + // new labels, those are special registers and should be remain as it + let var = if register.is_special() { + let name = register.name(); + Box::new(VariableAbstraction::Variable(name)) + } else { + let name = self.vargen.gen(prefix); + Box::new(VariableAbstraction::Variable(name)) + }; + match self.context.register_mapping.get_mut(®ister) { + Some(v) => { + *v = var; + } + None => { + self.context.register_mapping.insert(register.clone(), var); + } + }; + self.context.register_mapping.get_mut(®ister).unwrap() + } + + fn has_imm(&self) -> bool { + self.context.immediate_result.is_some() + } + fn get_immediate(&self) -> Box { + self.context.immediate_result.as_ref().unwrap().1.clone() + } + fn get_immediate_register(&self) -> Option { + self.context.immediate_result.as_ref().map(|imm| imm.0).flatten() + } + + // for dealing with + // - RR = zkPC + 1 : JMP(FUNCTION) + // - CALL(FUNCTION) + pub fn step_function(&mut self, function: &str) { + println!(" {}() {{", function); + match function { + "abs" => { + let a = self.get_register(Register::A).clone(); + let b = self.new_variable(Register::B, "local").clone(); + println!(" {} = {} < 0", b, a); + println!(" {} = abs({})", a, a); + } + "mulARITH" => { + println!(" arithRes1: arithA*arithB"); + println!(" mulArithOverflowValue: overflow value"); + println!(" mulArithOverflowFlag: 1 if overflow") + } + "divARITH" => { + println!(" arithRes1: arithA/arithB"); + println!(" arithRes2: arithA%arithB"); + } + "utilMULMOD" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + let out = self.new_variable(Register::C, "local").clone(); + println!(" {} = ({} * {}) % {}", out, a, b, c); + } + "expAD" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + let out = self.new_variable(Register::A, "local").clone(); + println!(" {} = {} ** {}", out, a, d); + } + "SHRarith" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + let out = self.new_variable(Register::A, "local").clone(); + println!(" {} = ({}) >> ({}*8)", out, a, d); + } + "SHLarith" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + let out = self.new_variable(Register::A, "local").clone(); + println!(" {} = ({}) << ({}*8)", out, a, d); + } + "SHRarithBit" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + let out = self.new_variable(Register::A, "local").clone(); + println!(" {} = ({}) >> ({})", out, a, d); + } + "SHLarithBit" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + let out = self.new_variable(Register::A, "local").clone(); + println!(" {} = ({}) << ({})", out, a, d); + } + "offsetUtil" => { + let a = self.get_register(Register::A).clone(); + let out1 = self.new_variable(Register::E, "local").clone(); + let out2 = self.new_variable(Register::C, "local").clone(); + println!(" {} = ({}) / 32", out1, a); + println!(" {} = ({}) % 32", out2, a); + } + "addBatchHashData" => { + let a = self.get_register(Register::A).clone(); + let d = self.get_register(Register::D).clone(); + println!(" APPEND BATCH HASH content={} len={}", a, d); + } + "addHashTxBegin" => { + let d = self.get_register(Register::D).clone(); + let a = self.new_variable(Register::A, "tx_pointer"); + println!(" APPEND TX HASH RLP-length"); + println!(" getTxs(len={}) -> {}", d, a); + } + "addHashTx" => { + let d = self.get_register(Register::D).clone(); + let a = self.new_variable(Register::A, "tx_pointer"); + println!(" APPEND TX HASH"); + println!(" getTxs(len={}) -> {}", d, a); + } + "(@exp_num + RR)" => { + let b = self.new_variable(Register::B, "const"); + println!(" {} = 1 << RR", b); + } + "readFromCalldataOffset" => { + println!(" in: readXFromCalldataOffset"); + println!(" in: readXFromCalldataLength"); + println!(" out: readXFromCalldataResult"); + } + "MLOADX" => { + let c = self.get_register(Register::C).clone(); + let e = self.get_register(Register::E).clone(); + let value = self.new_variable(Register::A, "local").clone(); + let offset = self.new_variable(Register::E, "local").clone(); + println!(" {} = MEMORY[{}:{}+{}]", value, e, e, c); + println!(" {} = {} + {}", offset, e, c); + } + "MLOAD32" => { + let e = self.get_register(Register::E).clone(); + let value = self.new_variable(Register::A, "local").clone(); + let offset = self.new_variable(Register::E, "local").clone(); + println!(" {} = MEMORY[{}:{}+32]", value, e, e); + println!(" {} = {} + 32", offset, e); + } + "maskAddress" => { + let a = self.get_register(Register::A).clone(); + println!(" {} = masked({})", a, a); + } + "isColdAddress" => { + let a = self.get_register(Register::A).clone(); + let out = self.new_variable(Register::D, "local").clone(); + println!(" {} = isColdAddress({})", out, a); + } + "opEXTCODECOPYLoadBytecode" => { + let a = self.get_register(Register::A).clone(); + println!(" Storage[tmpContractHashId] = SmartContractCodeAt({})", a); + } + _ => { + println!(" -- no heuristics yet (add if necessary)"); + } + }; + println!(" }}"); + } + + // for SLOAD and SSTORE function + fn get_leaf_type(&mut self) -> &str { + let b = self.get_register(Register::B).clone(); + let leaf_type = { + if let VariableAbstraction::Variable(ref label) = *b { + self.get_variable(label).map(|value| { + if let VariableAbstraction::Variable(ref const_str) = **value { + match const_str.as_str() { + "%SMT_KEY_BALANCE" => 0, + "%SMT_KEY_NONCE" => 1, + "%SMT_KEY_SC_CODE" => 2, + "%SMT_KEY_SC_STORAGE" => 3, + "%SMT_KEY_SC_LENGTH" => 4, + "%SMT_KEY_TOUCHED_ADDR" => 5, + "%SMT_KEY_TOUCHED_SLOTS" => 6, + "%SMT_KEY_BLOCK_HEADER_PARAM" => 7, + "%SMT_KEY_BLOCK_HEADER_TRANSACTION_HASH" => 8, + "%SMT_KEY_BLOCK_HEADER_STATUS" => 9, + "%SMT_KEY_BLOCK_HEADER_CUMULATIVE_GAS_USED" => 10, + "%SMT_KEY_BLOCK_HEADER_LOGS" => 11, + "%SMT_KEY_BLOCK_HEADER_EFFECTIVE_PERCENTAGE" => 12, + _ => { + unreachable!(); + }, + } + } else { + unreachable!(); // not found? + } + }) + } else if let VariableAbstraction::Value(n) = *b { + Some(n) + } else { + // it should still be reached, but human-code so maybe can't + // on weird condition only, like in block-info, + // finalConsolidateBlockInfoTree sets the registers then + // immediately followed by writeBlockInfoRoot + None + } + }; + if leaf_type.is_none() { + return "LeafType(Reg::B)"; + } + match leaf_type.unwrap() { + 0 => { + "Account::Balance" + } + 1 => { + "Account::Nonce" + } + 2 => { + "Smartcontract::Code" + } + 3 => { + "Smartcontract::Storage" + } + 4 => { + "Smartcontract::Length" + } + 5 => { + "TouchedTree::Addr" + } + 6 => { + "TouchedTree::Slots" + } + 7 => { + "BlockHeader::Param" + } + 8 => { + "BlockHeader::TransactionHash" + } + 9 => { + "BlockHeader::Status" + } + 10 => { + "BlockHeader::CumulativeGasUsed" + } + 11 => { + "BlockHeader::Logs" + } + 12 => { + "BlockHeader::EffectivePercentage" + } + _ => { + println!("Leaf type storage not found"); + unreachable!(); + } + } + } + + + pub fn step_opcode(&mut self, opcode: &InstructionOpcode) { + // TODO: add resolve assignment variable + + let params: Vec = opcode.params.iter().map(|param| { + match param { + InstructionOpcodeParam::Accessor(mem, expr) => { + let e = self.rename_expr(expr.clone()); + format!("{}:{}", mem, e) + } + InstructionOpcodeParam::Calculated(expr) => { + let e = self.rename_expr(expr.clone()); + format!("{}", e) + } + InstructionOpcodeParam::NameVariable(name) => { + format!("{}", name) + } + } + }).collect(); + let get_param = |i: usize| params.get(i).map(|x| x.clone()).unwrap_or("".into()); + let single_param = get_param(0); + + match opcode.name.as_str() { + ///// ARITHMETIC + "ADD" | "SUB" | "AND" | "OR" | "XOR" => { + // requires freeinput + let freeinput_register = self.context.freeinput_holder; + let op = opcode.name.as_str(); + let opsign = match op { + "ADD" => "+", + "SUB" => "-", + "AND" => "&", + "OR" => "|", + "XOR" => "^", + _ => unreachable!(), + }; + match freeinput_register { + Some(out_register) => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let out = self.new_variable(out_register, "local"); + println!(" {} = {} {} {}", out, a, opsign, b); + self.context.immediate_result = + Some((Some(out_register), out.clone())); + } + None => { + // it should be stored as immediate_result + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + println!(" imm = {} {} {}", a, opsign, b); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + } + "ARITH_BN254_MULFP2" => { + // TODO + println!(" // (ARITH_BN254_MULFP2)"); + } + ///// COMPARISION + "LT" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "local"); + println!(" {} = {} < {} // unsigned", out, a, b); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + println!(" imm = {} < {} // unsigned", a, b); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + "SLT" => { + // requires freeinput + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "local"); + println!(" {} = {} < {} // signed", out, a, b); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + println!(" imm = {} < {} // signed", a, b); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + "EQ" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "local"); + println!(" {} = {} == {}", out, a, b); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + println!(" imm = {} == {}", a, b); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + "LT4" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "local"); + println!(" {} = {} < {} // split 4 part compare", out, a, b); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + println!(" imm = {} == {} // split 4 part compare", a, b); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + ///// ARITH + // ARITH is also hardcoded when checking instruction + "ARITH" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + let d = self.get_register(Register::D).clone(); + let imm = self.get_immediate(); + println!(" {}*2^256 + {} == {} * {} + {} // ARITH assertion", d, imm, a, b, c); + } + "ARITH_BN254_ADDFP2" => { + println!(" // ARITH_BN254_ADDFP2"); + } + "ARITH_BN254_SUBFP2" => { + println!(" // ARITH_BN254_SUBFP2") + } + "ARITH_ECADD_SAME" => { + println!(" // ARITH_ECADD_SAME") + } + "ARITH_ECADD_DIFFERENT" => { + println!(" // ARITH_ECADD_DIFFERENT"); + } + ///// STORAGE + "SLOAD" => { + let a = self.get_register(Register::A).clone(); + let c = self.get_register(Register::C).clone(); + if let Some(freeinput_register) = self.context.freeinput_holder { + let leaf = self.get_leaf_type().to_string(); + let out = self.new_variable(freeinput_register, "state"); + println!(" {} = State.load({}, {}, capacity={})", out, a, leaf, c); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } + } + "SSTORE" => { + let a = self.get_register(Register::A).clone(); + let c = self.get_register(Register::C).clone(); + let d = self.get_register(Register::D).clone(); + let leaf = self.get_leaf_type().to_string(); + println!(" State.set({}, {}, capacity={}, value={})", a, leaf, c, d); + self.context.immediate_result = None; + } + ///// MEMORY + "MSTORE" => { + let imm = self.get_immediate(); + if let Some(reg) = self.get_immediate_register() { + println!( + " Storage[{}] = {} // register {}", + opcode.get_single(), + self.get_register(reg), + reg + ); + } else { + println!(" Storage[{}] = {}", opcode.get_single(), imm); + } + } + "MLOAD" => { + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "mem"); + println!(" {} = Storage[{}]", out, single_param); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + println!(" imm = Storage[{}]", single_param); + self.context.immediate_result = + Some((None, Box::new(VariableAbstraction::FreeInput("imm".into())))); + } + } + "MEM_ALIGN_RD" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + println!(" (MEM_ALIGN_RD)",); + println!(" Mem slot m0 {}", a); + println!(" Mem slot m1 {}", b); + println!(" Offset {}", c); + } + "MEM_ALIGN_WR" => { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + let d = self.get_register(Register::D).clone(); + let e = self.get_register(Register::E).clone(); + println!(" (MEM_ALIGN_WR)",); + println!(" Mem slot m0 {}", a); + println!(" Mem slot m1 {}", b); + println!(" Offset {}", c); + println!(" w0 {}", d); + println!(" w1 {}", e); + println!(" op {}", self.get_immediate()); + } + "MEM_ALIGN_WR8" => { + // TODO: not finished + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + let d = self.get_register(Register::D).clone(); + let e = self.get_register(Register::E).clone(); + println!(" (MEM_ALIGN_WR8)",); + println!(" Mem slot m0 {}", a); + println!(" Mem slot m1 {}", b); + println!(" Offset {}", c); + println!(" w0 {}", d); + println!(" w1 {}", e); + println!(" op {}", self.get_immediate()); + } + ///// CONTROL FLOW + "JMP" => { + let reg = match self.get_immediate_register() { + Some(r) => format!("{}", r), + None => "imm".into(), + }; + if reg == "RR" { + // heuristics for RR = zkPC + 1, equals to invoke function + if let InstructionOpcodeParam::NameVariable(method) = opcode.get_single() { + println!(" call {}", method); + self.step_function(method.as_str()); + } + self.context.immediate_result = None; + } else if self.has_imm() { + let imm = self.get_immediate(); + println!(" // register {} = {}", reg, imm); + println!(" goto {}", single_param); + self.context.immediate_result = None; + } else { + println!(" goto {}", single_param); + self.context.immediate_result = None; + } + } + "JMPN" => { + println!(" if ({} < 0) goto {}", self.get_immediate(), single_param); + let elsebranch = get_param(1); + if elsebranch != "" { + println!(" else goto {}", elsebranch); + } + self.context.immediate_result = None; + } + "JMPC" => { + println!(" if ({} == true) goto {}", self.get_immediate(), single_param); + let elsebranch = get_param(1); + if elsebranch != "" { + println!(" else goto {}", elsebranch); + } + self.context.immediate_result = None; + } + "JMPNC" => { + println!(" if ({} == false) goto {}", self.get_immediate(), single_param); + let elsebranch = get_param(1); + if elsebranch != "" { + println!(" else goto {}", elsebranch); + } + self.context.immediate_result = None; + } + "JMPZ" => { + if let Some(reg) = self.get_immediate_register() { + println!( + " if (/*reg:{}*/{} == 0) goto {}", + reg, + self.get_immediate(), + single_param + ); + } else { + println!( + " if ({} == 0) goto {}", + self.get_immediate(), + single_param + ); + } + let elsebranch = get_param(1); + if elsebranch != "" { + println!(" else goto {}", elsebranch); + } + self.context.immediate_result = None; + } + "JMPNZ" => { + if let Some(reg) = self.get_immediate_register() { + println!( + " if (/*reg:{}*/{} != 0) goto {}", + reg, + self.get_immediate(), + single_param + ); + } else { + println!( + " if ({} != 0) goto {}", + self.get_immediate(), + single_param + ); + } + let elsebranch = get_param(1); + if elsebranch != "" { + println!(" else goto {}", elsebranch); + } + self.context.immediate_result = None; + } + "ASSERT" => { + // compare with register A + let a = self.get_register(Register::A).clone(); + if let Some(reg) = self.get_immediate_register() { + println!( + " (ASSERT) stop if {} != {} // compare reg:A with reg:{}", + a, + self.get_register(reg), + reg + ); + } else if self.has_imm() { + println!( + " (ASSERT) stop if {} != {} // compare reg:A with imm", + a, + self.get_immediate(), + ); + } else { + println!(" (ASSERT) compare to what?"); + unreachable!(); + } + } + "CALL" => { + self.step_function(&single_param); + } + "RETURN" => { + // find variables that have not been used once and return + println!(" // TODO: filter var was not used as potential output"); + println!(" return"); + self.fallthrough = false; + } + ///// HASH + // These functions are not only for Hashing, they implement a form of + // ByteArray storage that can be freely indexed. + // These functions have their corresponding left hand side, + // + // - Assignment, equivalent to GET bytes at slot, [HASHPOS..D] + // - Expression, equivalent to PUT bytes at slot, [HASHPOS..D] = expr[:D] + "HASHP" => { + let d = self.get_register(Register::D).clone(); + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = PoseidonSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}..{}]", out, hashpos, d); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot[{}..{}] = {}[:{}]", hashpos, d, imm, d); + // increment hashpos + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr( + Expr::Binary(Box::new(hashpos.as_expr().clone()), BinaryOp::Add, Box::new(d.as_expr().clone())))); + } + } + "HASHP1" => { + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = PoseidonSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}]", out, hashpos); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot[{}] = {}[0]", hashpos, imm); + // increment hashpos + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr( + Expr::Binary(Box::new(hashpos.as_expr().clone()), BinaryOp::Add, Box::new(Expr::Value(1))))); + } + } + "HASHPLEN" => { + let imm = match self.get_immediate_register() { + Some(Register::HASHPOS) => { + // HASHPOS can be reset by using HASH LEN + let out = self.new_variable(Register::HASHPOS, "hashpos").clone(); + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr(out.clone().as_expr())); + "HASHPOS".into() + }, + Some(_) => format!("{}", self.get_immediate()), + None => "".into(), + }; + println!(" slot = PoseidonSlots[{}]", single_param); + println!(" {} = slot.len()", imm); + } + "HASHPDIGEST" => { + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = PoseidonSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}].digest()", out, hashpos); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot = {}.content()", imm); + } + } + "HASHK" => { + let d = self.get_register(Register::D).clone(); + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = KeccakSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}..{}]", out, hashpos, d); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot[{}..{}] = {}[:{}]", hashpos, d, imm, d); + // increment hashpos + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr( + Expr::Binary(Box::new(hashpos.as_expr().clone()), BinaryOp::Add, Box::new(d.as_expr().clone())))); + } + } + "HASHK1" => { + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = KeccakSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}]", out, hashpos); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot[{}] = {}[0]", hashpos, imm); + // increment hashpos + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr( + Expr::Binary(Box::new(hashpos.as_expr().clone()), BinaryOp::Add, Box::new(Expr::Value(1))))); + } + } + "HASHKLEN" => { + let imm = match self.get_immediate_register() { + Some(Register::HASHPOS) => { + // HASHPOS can be reset by using HASH LEN + let out = self.new_variable(Register::HASHPOS, "hashpos").clone(); + let hashpos_mut = self.get_register(Register::HASHPOS); + *hashpos_mut = Box::new(VariableAbstraction::Expr(out.clone().as_expr())); + "HASHPOS".into() + }, + Some(_) => format!("{}", self.get_immediate()), + None => "".into(), + }; + println!(" slot = KeccakSlots[{}]", single_param); + println!(" {} = slot.len()", imm); + } + "HASHKDIGEST" => { + let hashpos = self.get_register(Register::HASHPOS).clone(); + println!(" slot = KeccakSlots[{}]", single_param); + if let Some(freeinput_register) = self.context.freeinput_holder { + let out = self.new_variable(freeinput_register, "hashp"); + println!(" {} = slot[{}].digest()", out, hashpos); + self.context.immediate_result = Some((Some(freeinput_register), out.clone())); + } else { + let imm = self.get_immediate(); + println!(" slot[{}] = {}.content()", single_param, imm); + } + } + "HASHS" => { + println!(" HASH WTF-is-S-suffix"); + } + "HASHSLEN" => { + println!(" HASH WTF-is-S-suffix LENTH"); + println!(" -- address {}", single_param); + println!(" -- $ => {}", self.get_immediate()); + } + "HASHSDIGEST" => { + println!(" HASH WTF-is-S-suffix DIGEST"); + println!(" -- address {}", single_param); + println!(" -- $ => {}", self.get_immediate()); + } + "HASH0" => { + println!(" HASH WTF-is-0-suffix"); + } + "HASH1" => { + println!(" HASH WTF-is-1-suffix"); + } + "CLIMB_RKEY" => { + println!(" CLIMB_RKEY"); + } + "CLIMB_SIBLING_RKEY_N" => { + println!(" CLIMB_SIBLING_RKEY_N") + } + "CLIMB_SIBLING_RKEY" => { + println!(" CLIMB_SIBLING_RKEY") + } + "LATCH_GET" => { + println!(" LATCH GET?"); + } + "LATCH_SET" => { + println!(" LATCH SET?"); + } + inst => { + println!("TODO: instruction {}", inst); + unreachable!() + } + } + // reset freeinput + self.context.freeinput_holder = None; + self.context.has_freeinput = false; + } + + pub fn step(&mut self, inst: &Instruction) { + match inst { + Instruction::Assignment(expr, registers) => { + let evaluated = self.eval(&expr); + for reg in registers { + if evaluated.is_freeinput() { + match *evaluated { + VariableAbstraction::FreeInput(ref f) if f.len() > 1 => { + // print out freeinput that is not simple + println!( + " // ctx for freeinput: {:?}", + self.context.register_mapping + ); + self.context.freeinput_holder = None; + // fallthrough to assign this freeinput to a register + } + _ => { + // simple freeinput $ => REG + // should be used by an instruction opcode + // for fast reference, + // we use a freeinput_holder, saying which register + self.context.freeinput_holder = Some(reg.clone()); + continue; + } + } + } + if let VariableAbstraction::Value(_) = *evaluated { + // set a register to a concrete value, does not need to + // make new variable or change register reference + self.set_register(*reg, evaluated.clone()); + } else if reg == &Register::RR { + println!(" RR = {} // potential call next inst", evaluated); + } else if reg == &Register::SP { + // print but do not set SP, SP is SP + println!(" {} = {}", reg, evaluated); + } else if reg == &Register::GAS { + // print but do not set SP, SP is SP + println!(" {} = {}", reg, evaluated); + } else { + // every assignment should be assigned a new variable at register + let v = self.new_variable(*reg, "local"); + println!(" {} = {}", v, evaluated); + let x = v.clone(); // XXX: rust lifetime + self.add_variable_value(x, &evaluated); + } + self.context.immediate_result = Some((Some(*reg), self.get_register(*reg).to_owned())); + } + } + Instruction::Expr(expr) => { + if let Expr::Register(reg) = expr { + self.context.immediate_result = Some((Some(*reg), self.eval(&expr))); + } else { + let evaluated = self.eval(&expr); + match *evaluated { + VariableAbstraction::FreeInput(ref f) if f == "$" => { + self.context.has_freeinput = true; + } + _ => { + } + } + self.context.immediate_result = Some((None, evaluated)); + } + } + Instruction::Opcode(opcode) => { + self.step_opcode(opcode); + } + Instruction::Compound(lhs, rhs) => { + // work around for freeinput $ + // $ is used followed by an instruction that yields the result + // and save in the freeinput + // usually it is easy to follow, unless the lhs is an assignment + // if assignment is performed first, then the freeinput is undefined + // and it can only be defined after the instruction is stepped through + // so rather than that, we check if the lhs contains freeinput + // then perform the rhs, storing the result to the freeinput, + // then step into the lhs with freeinput evalulated + // COPIUM hoping that it does not clashes with below + + + match &rhs[0] { + Instruction::Opcode(InstructionOpcode { name: n, .. }) + if n == "MEM_ALIGN_WR" => + { + self.step(lhs); + self.step(&rhs[1]); // this should be MLOAD to load freeinput + self.step(&rhs[0]); // then use the freeinput in MEM_ALIGN_WR + } + Instruction::Opcode(InstructionOpcode { name: n, .. }) + if n == "ARITH" => + { + let a = self.get_register(Register::A).clone(); + let b = self.get_register(Register::B).clone(); + let c = self.get_register(Register::C).clone(); + let d = self.get_register(Register::D).clone(); + + self.step(lhs); + + let imm = self.get_immediate(); + println!(" (ARITH) {}*2^256 + {} ==? {} * {} + {}", d, imm, a, b, c); + if let Some(reg) = self.get_immediate_register() { + let out = self.new_variable(reg, "eval_result"); + println!( + " --> {} (register {})", out, reg, + ); + } + } + _ => { + self.step(lhs); + for r in rhs { + self.step(r); + } + } + } + } + } + } + + pub fn start(&mut self, function: &Subroutine) { + println!(""); // add space + println!("function {}() {{ // this could be a label", function.name); + let instructions = &function.instructions; + for inst in instructions.iter() { + self.step(inst); + } + println!( + " // ctx <- {:?}", + self.context.register_mapping + ); + if self.fallthrough { + println!(" // potential fallthrough below"); + } + println!("}}"); + println!(""); // add space + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6c931fa --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use std::fs; + +mod execution; +mod zkasm; + +use crate::zkasm::{parse_instruction_list, parse_expr, Definition, Program, Rule, Subroutine, ZkasmParser}; +use pest::Parser; + +fn main() { + let filepath = std::env::args().nth(1).expect("dude, pass a file to decompile"); + let input = fs::read_to_string(filepath).expect("cannot read file"); + + let program = ZkasmParser::parse(Rule::program, &input) + .expect("file format is wrong or the parser is wrong") + .next() + .expect("cannot parse input file as a zkasm program"); + + let definitions = program + .into_inner() + .find_map(|x| match x.as_rule() { + Rule::definition_list => Some(x), + _ => None, + }) + .unwrap(); + + let mut program_ast = Program { + constants: Vec::new(), + definitions: Vec::new(), + }; + + for definition in definitions.into_inner() { + match definition.as_rule() { + Rule::variable => { + let mut var = definition.into_inner(); + let vartype = var.next().unwrap(); + let varname = var.next().unwrap().as_str(); + if vartype.as_str() == "GLOBAL" { + program_ast + .definitions + .push(Definition::VariableGlobal(varname.into(), var.next().map(parse_expr))); + } else if vartype.as_str() == "CTX" { + program_ast + .definitions + .push(Definition::VariableCTX(varname.into(), var.next().map(parse_expr))); + } else { + unreachable!(); + } + } + Rule::constant => { + let mut var = definition.into_inner(); + let constid = var.next().unwrap().as_str(); + if let Some(constexpr) = var.next() { + program_ast.constants.push((constid.into(), format!("{}", parse_expr(constexpr)))); + } else { + program_ast.constants.push((constid.into(), "0".into())); + } + } + Rule::subroutine => { + let mut subroutine = definition.into_inner(); + let name = subroutine.next().unwrap().as_str(); + if let Some(instructions) = subroutine.next() { + let subroutine_ast = Subroutine { + name: name.to_string(), + instructions: parse_instruction_list(instructions), + }; + program_ast + .definitions + .push(Definition::Subroutine(subroutine_ast)); + } else { + // empty instruction list, or just a label that is alias to the next + let subroutine_ast = Subroutine { + name: name.to_string(), + instructions: Vec::new(), + }; + program_ast + .definitions + .push(Definition::Subroutine(subroutine_ast)); + } + } + Rule::include => { + let file = definition.into_inner().next().unwrap().as_str().to_string().replace("\"", ""); + program_ast.definitions.push(Definition::Include(file)); + } + _ => { + unreachable!(); + } + } + } + + program_ast.decompile(); +} diff --git a/src/zkasm.pest b/src/zkasm.pest new file mode 100644 index 0000000..14d3b91 --- /dev/null +++ b/src/zkasm.pest @@ -0,0 +1,269 @@ +identifier = @{ + ASCII_ALPHA ~ ("_" | ASCII_ALPHANUMERIC)* +} + +reference = @{ + "@" ~ identifier +} + +instruction_name = @{ + ASCII_ALPHA_UPPER ~ ("_" | ASCII_ALPHANUMERIC)* +} + +register = @{ + "CTX" + | "A" + | "B" + | "C" + | "D" + | "E" + | "F" + | "RR" + | "SR" + | "SP" + | "PC" + | "RCX" + | "GAS" + | "HASHPOS" + | "HASH_LEFT" + | "HASH_RIGHT" + | "OLD_ROOT" + | "NEW_ROOT" + | "VALUE_LOW" + | "VALUE_HIGH" + | "SIBLING_VALUE_HASH" + | "RKEY_BIT" + | "RKEY" + | "SIBLING_RKEY" + | "LEVEL" +} + +memory_scope = { + "MEM" + | "STACK" + | "SYS" +} +memory_access = { + memory_scope ~ ":" ~ expr +} + +instruction_right_param_atomic = { + memory_access + | expr +} + +instruction_right_param_list = _{ + instruction_right_param_atomic ~ "," ~ instruction_right_param_list + | instruction_right_param_atomic +} + +instruction_right = { + instruction_name ~ "(" ~ instruction_right_param_list? ~ ")" + | instruction_name +} + +assignment = { + expr ~ "=>" ~ register ~ ("," ~ register)* +} + +dotaccess = @{ + identifier ~ "." ~ identifier +} + +invocation_param_atomic = { + dotaccess + | identifier + | register +} + +params = { + invocation_param_atomic ~ ("," ~ invocation_param_atomic)* +} + +invocation = { + identifier ~ "(" ~ params? ~ ")" +} + +freeinput_param = { + invocation + | expr +} + +freeinput = { + + | "$${" ~ (!("}") ~ ANY)* ~ "}" + | "$0{" ~ (!("}") ~ ANY)* ~ "}" + | "${" ~ (!("}") ~ ANY)* ~ "}" + | "$" +} + +binop = _{ + power + | add + | sub + | mul + | div + | mod + | shl + | shr + | xor + | and + | or + | lt + | gt + | lte + | gte + | eq + | neq + | land + | lor +} +power = { "**" } +add = { "+" } +sub = { "-" } +mul = { "*" } +div = { "/" } +mod = { "%" } +shl = { "<<" } +shr = { ">>" } +xor = { "^" } +and = { "&" } +or = { "|" } +lt = { "<" } +gt = { ">" } +lte = { "<=" } +gte = { ">=" } +eq = { "==" } +neq = { "!=" } +land = { "&&" } +lor = { "||" } + +special_variable = @{ + !("VAR") ~ !("INCLUDE") ~ ("_" | ASCII_ALPHANUMERIC)+ ~ !(":") +} + +number = @{ + "0x" ~ ASCII_HEX_DIGIT+ ~ "n"? + | ASCII_DIGIT+ ~ "n"? +} + +atomic_operand = _{ + number + | constid + | freeinput + | special_variable + | reference + | register +} + +selfmodifying = _{ +increment | decrement +} +increment = {"++"} +decrement = {"--"} + + +exprfragment = _{ + atomic_operand ~ binop ~ atomic_operand + | atomic_operand ~ selfmodifying? + | negate_expr + | "(" ~ expr_all ~ ")" +} +expr = { + exprfragment ~ (binop ~ exprfragment)* +} + +not = { "!" } +negate = { "-" } +negate_expr = _{ + (not | negate) ~ expr +} + +tenary_expr = { + expr ~ "?" ~ expr ~ ":" ~ expr +} + +expr_all = _{ + tenary_expr + | negate_expr + | expr +} + +instruction_type1 = { + assignment + | expr +} + +instruction_type2 = { + instruction_right ~ ("," ~ instruction_right)* +} + +instruction_type3 = { + instruction_type1 ~ ":" ~ instruction_type2 +} + +instruction = _{ + instruction_type3 + | instruction_type1 + | ":" ~ instruction_type2 +} + +instruction_list = { + instruction ~ (NEWLINE* ~ instruction)* +} + +subroutine_id = @{ ("_" | ASCII_ALPHANUMERIC)* } + +subroutine = { + subroutine_id ~ ":" ~ NEWLINE* ~ instruction_list? +} + +definition = _{ + constant + | variable + | subroutine + | include +} + +definition_list = { + definition ~ (NEWLINE* ~ definition)* +} + +variable_type = { + "GLOBAL" + | "CTX" +} +variable = { + "VAR" ~ variable_type ~ identifier ~ ("[" ~ expr ~ "]")? +} + +constid = @{ + "%" ~ identifier +} +constant = { + ("CONSTL" | "CONST") ~ constid ~ ("=" ~ expr)? +} + +file_path_component = @{ + (ASCII_ALPHANUMERIC | "_" | "-" | ".")+ +} +file_path = @{ + file_path_component ~ ("/" ~ file_path_component)* +} +include_name = @{ + "\"" ~ file_path ~ "\"" +} +include = { + "INCLUDE" ~ include_name +} + +// allow newline between +program = { + SOI ~ NEWLINE* ~ definition_list ~ NEWLINE* ~ EOI +} + +WHITESPACE = _{ " " | "\t" } + +comment_type1 = _{ ";" ~ (!NEWLINE ~ ANY)* ~ NEWLINE? } +comment_type2 = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" } +COMMENT = _{ comment_type1 | comment_type2 } diff --git a/src/zkasm.rs b/src/zkasm.rs new file mode 100644 index 0000000..6e8d285 --- /dev/null +++ b/src/zkasm.rs @@ -0,0 +1,623 @@ +#[derive(pest_derive::Parser)] +#[grammar = "zkasm.pest"] +pub struct ZkasmParser; + +use pest::iterators::Pair; + +use std::fmt; + +use crate::execution::Execution; + +#[derive(Debug, Clone)] +pub enum UnaryOp { + Negate, + Not, +} + +impl fmt::Display for UnaryOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UnaryOp::Negate => write!(f, "-"), + UnaryOp::Not => write!(f, "!"), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOp { + Add, + Sub, + Mul, + Div, + Mod, + Lt, + Gt, + Le, + Ge, + Eq, + Neq, + LogicalXor, + LogicalAnd, + LogicalOr, + ArithAnd, + ArithOr, + ShiftLeft, + ShiftRight, + Power, +} + +impl fmt::Display for BinaryOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Lt => "<", + BinaryOp::Gt => ">", + BinaryOp::Le => "<=", + BinaryOp::Ge => ">=", + BinaryOp::Eq => "==", + BinaryOp::Neq => "!=", + BinaryOp::ArithOr => "|", + BinaryOp::ArithAnd => "&", + BinaryOp::LogicalOr => "||", + BinaryOp::LogicalAnd => "&&", + BinaryOp::ShiftLeft => "<<", + BinaryOp::ShiftRight => ">>", + BinaryOp::Power => "**", + BinaryOp::Mod => "%", + _ => { + unreachable!() + } + }; + write!(f, "{}", s) + } +} + +impl Into for String { + fn into(self) -> BinaryOp { + match self.as_str() { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + "%" => BinaryOp::Mod, + "<" => BinaryOp::Lt, + ">" => BinaryOp::Gt, + "<=" => BinaryOp::Le, + ">=" => BinaryOp::Ge, + "==" => BinaryOp::Eq, + "!=" => BinaryOp::Neq, + "|" => BinaryOp::ArithOr, + "&" => BinaryOp::ArithAnd, + "||" => BinaryOp::LogicalOr, + "&&" => BinaryOp::LogicalAnd, + "<<" => BinaryOp::ShiftLeft, + ">>" => BinaryOp::ShiftRight, + "**" => BinaryOp::Power, + _ => { + unreachable!() + } + } + } +} + +#[derive(Debug, Clone)] +pub enum Expr { + Unary(UnaryOp, Box), + Binary(Box, BinaryOp, Box), + Tenary(Box, Box, Box), + FreeInput(String), + Register(Register), + Value(u64), + ValueComplex(String), // for hex number + NameVariable(String), + Increment(Box), + Decrement(Box), +} + +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Expr::Unary(op, expr) => { + write!(f, "{}({})", op, expr) + } + Expr::Binary(lhs, op, rhs) => { + write!(f, "({} {} {})", lhs, op, rhs) + } + Expr::Tenary(expr, ifbranch, elsebranch) => { + write!(f, "({} ? {} : {})", expr, ifbranch, elsebranch) + } + Expr::Register(r) => { + write!(f, "{}", r.name()) + } + Expr::Value(v) => { + write!(f, "{}", v) + } + Expr::ValueComplex(v) => { + if v.starts_with("0x") { + match u64::from_str_radix(v.strip_prefix("0x").unwrap(), 16).ok() { + Some(s) => write!(f, "{}/* {} */", s, v), + None => write!(f, "{}", v), + } + } else { + write!(f, "{}", v) + } + } + Expr::NameVariable(v) => { + write!(f, "{}", v) + } + Expr::FreeInput(free) => { + write!(f, "{}", free) + } + Expr::Increment(e) => { + write!(f, "({})++", e) + } + Expr::Decrement(e) => { + write!(f, "({})--", e) + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum Register { + A, + B, + C, + D, + E, + SP, + RR, + SR, + PC, + CTX, + RCX, + GAS, + HASHPOS, + HASH_LEFT, + HASH_RIGHT, + OLD_ROOT, + NEW_ROOT, + VALUE_LOW, + VALUE_HIGH, + SIBLING_VALUE_HASH, + RKEY, + SIBLING_RKEY, + RKEY_BIT, + LEVEL, +} + +impl fmt::Display for Register { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + _ if self.is_special() => write!(f, "{}", self.name()), + Register::A => write!(f, "A"), + Register::B => write!(f, "B"), + Register::C => write!(f, "C"), + Register::D => write!(f, "D"), + Register::E => write!(f, "E"), + Register::SP => write!(f, "SP"), + Register::SR => write!(f, "SR"), + Register::RR => write!(f, "RR"), + Register::PC => write!(f, "PC"), + Register::GAS => write!(f, "GAS"), + Register::CTX => write!(f, "CTX"), + Register::RCX => write!(f, "RCX"), + Register::HASHPOS => write!(f, "HASHPOS"), + Register::HASH_LEFT => write!(f, "HASH_LEFT"), + Register::HASH_RIGHT => write!(f, "HASH_RIGHT"), + Register::OLD_ROOT => write!(f, "OLD_ROOT"), + Register::NEW_ROOT => write!(f, "NEW_ROOT"), + Register::VALUE_LOW => write!(f, "VALUE_LOW"), + Register::VALUE_HIGH => write!(f, "VALUE_HIGH"), + Register::SIBLING_VALUE_HASH => write!(f, "SIBLING_VALUE_HASH"), + Register::RKEY => write!(f, "RKEY"), + Register::SIBLING_RKEY => write!(f, "SIBLING_RKEY"), + Register::RKEY_BIT => write!(f, "RKEY_BIT"), + Register::LEVEL => write!(f, "LEVEL"), + } + } +} + +impl Register { + pub fn is_special(self) -> bool { + match self { + Register::A | Register::B | Register::C | Register::D | Register::E => false, + _ => true, + } + } + + pub fn name(self) -> String { + match self { + Register::SR => "StateRoot".to_string(), + Register::RR => "ReturnRegister".to_string(), + Register::GAS => "GAS".to_string(), + Register::HASHPOS => "HASHPOS".to_string(), + Register::CTX => "CONTEXT".to_string(), + Register::PC => "EVM(ProgramCounter)".to_string(), + Register::SP => "EVM(StackPointer)".to_string(), + _ => format!("{:?}", self) + } + } + + pub fn from_name(name: &str) -> Self { + match name { + "A" => Register::A, + "B" => Register::B, + "C" => Register::C, + "D" => Register::D, + "E" => Register::E, + "SP" => Register::SP, + "RR" => Register::RR, + "SR" => Register::SR, + "PC" => Register::PC, + "CTX" => Register::CTX, + "RCX" => Register::RCX, + "GAS" => Register::GAS, + "HASHPOS" => Register::HASHPOS, + "HASH_LEFT" => Register::HASH_LEFT, + "HASH_RIGHT" => Register::HASH_RIGHT, + "OLD_ROOT" => Register::OLD_ROOT, + "NEW_ROOT" => Register::NEW_ROOT, + "VALUE_LOW" => Register::VALUE_LOW, + "VALUE_HIGH" => Register::VALUE_HIGH, + "SIBLING_VALUE_HASH" => Register::SIBLING_VALUE_HASH, + "RKEY" => Register::RKEY, + "SIBLING_RKEY" => Register::SIBLING_RKEY, + "RKEY_BIT" => Register::RKEY_BIT, + "LEVEL" => Register::LEVEL, + _ => { + unreachable!() + } + } + } +} + +#[derive(Debug, Clone)] +pub enum AccessPlace { + Memory, + Stack, + System, +} + +impl fmt::Display for AccessPlace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AccessPlace::Memory => write!(f, "MEM"), + AccessPlace::Stack => write!(f, "STACK"), + AccessPlace::System => write!(f, "SYS"), + } + } +} + +#[derive(Debug, Clone)] +pub enum InstructionOpcodeParam { + NameVariable(String), + Accessor(AccessPlace, Expr), + Calculated(Expr), +} + +impl fmt::Display for InstructionOpcodeParam { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InstructionOpcodeParam::NameVariable(name) => { + write!(f, "{}", name) + } + InstructionOpcodeParam::Accessor(access, expr) => { + write!(f, "{}:{}", access, expr) + } + InstructionOpcodeParam::Calculated(expr) => { + write!(f, "{}", expr) + } + } + } +} + +#[derive(Debug)] +pub struct InstructionOpcode { + pub name: String, + pub params: Vec, +} + +impl InstructionOpcode { + pub fn get_single(&self) -> InstructionOpcodeParam { + self.params[0].clone() + } + + pub fn get_at(&self, idx: usize) -> Option { + self.params.get(idx).map(|x| x.clone()) + } +} + +#[derive(Debug)] +pub enum Instruction { + Expr(Expr), + Assignment(Expr, Vec), + Opcode(InstructionOpcode), + Compound(Box, Vec), +} + +#[derive(Debug)] +pub struct Subroutine { + pub name: String, + pub instructions: Vec, +} + +#[derive(Debug)] +pub enum Definition { + VariableGlobal(String, Option), + VariableCTX(String, Option), + Subroutine(Subroutine), + Include(String), +} + +// temporarily use display as a decompile printer +impl fmt::Display for Definition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Definition::VariableCTX(var, Some(array)) => { + write!(f, "VAR CTX {}[{}]", var, array) + } + Definition::VariableGlobal(var, Some(array)) => { + write!(f, "VAR GLOBAL {}[{}]", var, array) + } + Definition::VariableCTX(var, None) => { + write!(f, "VAR CTX {}", var) + } + Definition::VariableGlobal(var, None) => { + write!(f, "VAR GLOBAL {}", var) + } + Definition::Include(include) => { + write!(f, "INCLUDE {}", include) + } + Definition::Subroutine(subroutine) => { + // subroutine will be decompiled + let mut run = Execution::new(); + run.start(subroutine); + Ok(()) + } + } + } +} + +#[derive(Debug)] +pub struct Program { + pub constants: Vec<(String, String)>, + pub definitions: Vec, +} + +impl Program { + pub fn decompile(self) { + for c in self.constants { + println!("CONST {} = {}", c.0, c.1); + } + for def in self.definitions { + println!("{}", def); + } + } +} + +fn parse_long_expr(expr: Pair) -> Expr { + use pest::pratt_parser::PrattParser; + use pest::pratt_parser::{Assoc, Op}; + + assert!(expr.as_rule() == Rule::expr); + + let pratt = + PrattParser::new() + .op(Op::infix(Rule::r#mod, Assoc::Left)) + .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left)) + .op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left)) + .op(Op::infix(Rule::and, Assoc::Left) | Op::infix(Rule::or, Assoc::Left) | Op::infix(Rule::xor, Assoc::Left)) + .op(Op::infix(Rule::shl, Assoc::Left) | Op::infix(Rule::shr, Assoc::Left)) + .op(Op::infix(Rule::power, Assoc::Left)) + .op(Op::prefix(Rule::not) | Op::prefix(Rule::negate)) + .op(Op::postfix(Rule::increment) | Op::postfix(Rule::decrement)) + ; + + // println!("{:?}", expr); + + pratt + .map_primary(|primary| match primary.as_rule() { + Rule::expr => { + // ( expr ) + parse_expr(primary) + } + _ => { + parse_expr_atomic(primary) + } + }) + .map_prefix(|op, rhs| { + match op.as_str() { + "-" => { + Expr::Unary(UnaryOp::Negate, Box::new(rhs)) + } + "!" => { + Expr::Unary(UnaryOp::Not, Box::new(rhs)) + } + _ => unreachable!() + }}) + .map_infix(|lhs, op, rhs| { + let binop: BinaryOp = op.as_str().to_string().into(); + Expr::Binary(Box::new(lhs.clone()), binop, Box::new(rhs.clone())) + }) + .map_postfix(|expr, op| { + match op.as_rule() { + Rule::decrement => Expr::Decrement(Box::new(expr.clone())), + Rule::increment => Expr::Increment(Box::new(expr.clone())), + _ => unreachable!() + } + }) + .parse(expr.into_inner()) +} + +fn parse_expr_atomic(expr: Pair) -> Expr { + match expr.as_rule() { + Rule::register => Expr::Register(Register::from_name(expr.as_str())), + Rule::number => { + match expr.as_str().to_string().parse::().ok() { + Some(v) => Expr::Value(v), + None => Expr::ValueComplex(expr.as_str().into()), + } + } + Rule::freeinput => Expr::FreeInput(expr.as_str().into()), + Rule::special_variable => { + let v = expr.as_str(); + match v { + "A" | "B" | "C" | "D" | "E" | "SR" | "RR" | "SP" | "CTX" | "HASHPOS" => { + Expr::Register(Register::from_name(v)) + } + _ => Expr::NameVariable(v.into()), + } + } + Rule::constid => { + let v = expr.as_str(); + match v { + "A" | "B" | "C" | "D" | "E" | "SR" | "RR" | "SP" | "CTX" | "HASHPOS" => { + Expr::Register(Register::from_name(v)) + } + _ => Expr::NameVariable(v.into()), + } + } + Rule::reference => { + let v = expr.as_str(); + Expr::NameVariable(v.into()) + } + _ => { + println!("parse atomic {:?}", expr); + unreachable!(); + } + } +} + +pub fn parse_expr(inst: Pair) -> Expr { + match inst.as_rule() { + Rule::expr => { + parse_long_expr(inst) + // let mut peak = inst.clone().into_inner(); + + // if peak.len() > 1 { + // return parse_long_expr(inst); + // } + + // let expr = peak.next().unwrap(); + // parse_expr_atomic(expr) + } + Rule::negate_expr => { + let mut p = inst.into_inner(); + let op = match p.next().unwrap().as_str() { + "-" => UnaryOp::Negate, + "!" => UnaryOp::Not, + _ => unreachable!() + }; + let expr = parse_expr(p.next().unwrap()); + Expr::Unary(op, Box::new(expr)) + } + Rule::tenary_expr => { + let mut p = inst.into_inner(); + let condition = parse_expr(p.next().unwrap()); + let ifbranch = parse_expr(p.next().unwrap()); + let elsebranch = parse_expr(p.next().unwrap()); + Expr::Tenary(Box::new(condition), Box::new(ifbranch), Box::new(elsebranch)) + } + Rule::register | Rule::number | Rule::freeinput | Rule::special_variable | Rule::constid | Rule::reference => { + parse_expr_atomic(inst) + } + _ => { + unreachable!(); + } + } +} + +pub fn parse_param(param: Pair) -> InstructionOpcodeParam { + match param.as_rule() { + Rule::identifier => InstructionOpcodeParam::NameVariable(param.as_str().into()), + Rule::memory_access => { + let mut p = param.into_inner(); + let scope = match p.next().unwrap().as_str() { + "MEM" => AccessPlace::Memory, + "STACK" => AccessPlace::Stack, + "SYS" => AccessPlace::System, + _ => unreachable!(), + }; + let expr = parse_expr(p.next().unwrap()); + InstructionOpcodeParam::Accessor(scope, expr) + } + Rule::expr => { + let e = parse_expr(param); + if let Expr::NameVariable(name) = e { + InstructionOpcodeParam::NameVariable(name) + } else { + InstructionOpcodeParam::Calculated(e) + } + } + _ => { + unreachable!(); + } + } +} + +// parse a single instruction, could be type 1, 2 or 3 +pub fn parse_instruction(instruction: Pair) -> Instruction { + // println!("parsing inst {:?}", instruction); + match instruction.as_rule() { + Rule::instruction_type1 => { + let inst = instruction.into_inner().next().unwrap(); + if inst.as_rule() == Rule::expr { + Instruction::Expr(parse_expr(inst)) + } else if inst.as_rule() == Rule::negate_expr { + Instruction::Expr(parse_expr(inst)) + } else if inst.as_rule() == Rule::assignment { + let mut p = inst.into_inner(); + let expr = parse_expr(p.next().unwrap()); + let registers: Vec = p.map(|r| Register::from_name(r.as_str())).collect(); + Instruction::Assignment(expr, registers) + } else { + println!("parsing inst {:?}", inst); + unreachable!() + } + // println!("typ1 {:?}", inst) + } + // instruction_type2 is a list of instruction_right and will be resolved using parse_instruction_list + Rule::instruction_right => { + let mut inst = instruction.into_inner(); + let name = inst.next().unwrap().as_str(); + let mut params: Vec = Vec::new(); + while let Some(param) = inst.next() { + let mut p: Vec = param.into_inner().map(parse_param).collect(); + params.append(&mut p); + } + Instruction::Opcode(InstructionOpcode { + name: name.into(), + params, + }) + } + Rule::instruction_type3 => { + let mut insts = instruction.into_inner(); + let lhs = parse_instruction(insts.next().unwrap()); + let rhs = parse_instruction_list(insts.next().unwrap()); + Instruction::Compound(Box::new(lhs), rhs) + } + _ => { + unreachable!() + } + } +} + +// parse a list of instruction +pub fn parse_instruction_list(instruction_list: Pair) -> Vec { + let mut parsed_instructions = Vec::new(); + for instruction in instruction_list.into_inner() { + if instruction.as_rule() == Rule::instruction_type2 { + // could be many or 1 + for inst in instruction.into_inner() { + parsed_instructions.push(parse_instruction(inst)); + } + } else { + parsed_instructions.push(parse_instruction(instruction)); + } + } + return parsed_instructions; +} diff --git a/test.zkasm b/test.zkasm new file mode 100644 index 0000000..1724487 --- /dev/null +++ b/test.zkasm @@ -0,0 +1,43 @@ +/* +CONST %aa = 2**32**8 ; atomic ** atomic +CONST %aa = 2**32 ; atomic ** atomic +CONST %bb = 2**32-1 ; atomic ** atomic ~ rest +CONST %bb = (1+1)**32-1 ; expr ** atomic ~ rest +CONST %bb = 2**(31+1)-1 ; atomic ** expr ~ rest +CONST %bb = (1+1)**(31+1)-1 ; expr ** expr ~ rest +CONST %bb = (1+1)**(-32)-1 ; expr ** expr ~ rest +CONST %cc = 2**32-1-1 % 10 +CONST %dd = 2**(32-1) ; good +CONST %ee = (2**32)-1 ; good + + +CONSTL %P2_C0_EGX = %ECGX & 0xFFFF + +a: + -1 => B + +CONST %MAX_CNT_PADDING_PG_LIMIT = (%TOTAL_STEPS_LIMIT / 56) +*/ + + +; CONSTL %FPEC = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fn +; CONSTL %FPEC_MINUS_ONE = %FPEC - 1 +; CONSTL %FNEC_DIV_TWO = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0n +; CONSTL %FPEC_C2_256 = 0x1000003D1n +; CONSTL %FPEC_NON_SQRT = (1n << 256n) - 1n +; +; CONSTL %FNEC = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n +; CONSTL %FNEC_MINUS_ONE = %FNEC - 1 +; +; CONSTL %ECGX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n +; CONSTL %ECGY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n +; CONSTL %P2_160 = 2n ** 160n +; CONSTL %P2_96 = 2n ** 96n + +/* +Run: + 0 => HASH_LEFT, HASH_RIGHT, OLD_ROOT, NEW_ROOT, VALUE_LOW, VALUE_HIGH, SIBLING_VALUE_HASH, RKEY, SIBLING_RKEY, RKEY_BIT, LEVEL +*/ + +a: + A :MSTORE(SP--)