diff --git a/Cargo.lock b/Cargo.lock index 577b3b4e2..e97e909be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom", + "getrandom 0.2.11", "once_cell", "version_check", ] @@ -100,6 +100,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ark-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65be532f9dd1e98ad0150b037276cde464c6f371059e6dd02c0222395761f6aa" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ec" version = "0.3.0" @@ -130,7 +141,7 @@ dependencies = [ "num-traits", "paste", "rayon", - "rustc_version", + "rustc_version 0.3.3", "zeroize", ] @@ -199,10 +210,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", "rayon", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -325,6 +345,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "circom_algebra" +version = "2.1.4" +source = "git+https://github.com/iden3/circom.git?tag=v2.1.8#f0deda416abe91e5dd906c55507c737cd9986ab5" +dependencies = [ + "constant_tracking", + "num-bigint-dig", + "num-traits", +] + [[package]] name = "clap" version = "4.4.11" @@ -371,6 +401,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "constant_tracking" +version = "2.0.0" +source = "git+https://github.com/iden3/circom.git?tag=v2.1.8#f0deda416abe91e5dd906c55507c737cd9986ab5" + +[[package]] +name = "constraint_writers" +version = "2.1.8" +source = "git+https://github.com/iden3/circom.git?tag=v2.1.8#f0deda416abe91e5dd906c55507c737cd9986ab5" +dependencies = [ + "circom_algebra", + "json", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -403,7 +447,7 @@ version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ - "autocfg", + "autocfg 1.1.0", "cfg-if", "crossbeam-utils", "memoffset", @@ -603,6 +647,101 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -613,6 +752,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -621,7 +771,7 @@ checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -630,6 +780,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "groupmap" version = "0.1.0" @@ -637,7 +793,7 @@ source = "git+https://github.com/o1-labs/proof-systems?rev=a5d8883ddf649c22f38aa dependencies = [ "ark-ec", "ark-ff", - "rand", + "rand 0.8.5", ] [[package]] @@ -717,7 +873,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg", + "autocfg 1.1.0", "hashbrown 0.12.3", "serde", ] @@ -778,6 +934,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "kimchi" version = "0.1.0" @@ -802,8 +964,8 @@ dependencies = [ "o1-utils", "once_cell", "poly-commitment", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rmp-serde", "serde", @@ -814,12 +976,27 @@ dependencies = [ "turshi", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.0.1" @@ -855,7 +1032,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -910,7 +1087,7 @@ dependencies = [ "mina-curves", "o1-utils", "once_cell", - "rand", + "rand 0.8.5", "rayon", "serde", "serde_with 1.14.0", @@ -929,21 +1106,25 @@ dependencies = [ name = "noname" version = "0.7.0" dependencies = [ + "ark-bls12-381", "ark-ec", "ark-ff", "ark-serialize", "camino", "clap", + "constraint_writers", "dirs", "ena", "itertools", "kimchi", "miette", "num-bigint", + "num-bigint-dig", "num-traits", "once_cell", "regex", "rmp-serde", + "rstest", "serde", "serde_json", "serde_with 2.3.3", @@ -957,11 +1138,29 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ - "autocfg", + "autocfg 1.1.0", + "num-integer", + "num-traits", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "num-bigint-dig" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a" +dependencies = [ + "autocfg 0.1.8", + "byteorder", + "lazy_static", + "libm", "num-integer", + "num-iter", "num-traits", - "rand", + "rand 0.7.3", "serde", + "smallvec", ] [[package]] @@ -981,7 +1180,18 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg", + "autocfg 1.1.0", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg 1.1.0", + "num-integer", "num-traits", ] @@ -991,7 +1201,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -1008,8 +1218,8 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "serde", "serde_with 1.14.0", @@ -1055,6 +1265,18 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "poly-commitment" version = "0.1.0" @@ -1071,8 +1293,8 @@ dependencies = [ "mina-poseidon", "o1-utils", "once_cell", - "rand", - "rand_core", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rmp-serde", "serde", @@ -1110,6 +1332,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -1117,8 +1352,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1128,7 +1373,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1137,7 +1391,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1175,7 +1438,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom", + "getrandom 0.2.11", "libredox", "thiserror", ] @@ -1209,6 +1472,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "rmp" version = "0.8.12" @@ -1231,6 +1500,35 @@ dependencies = [ "serde", ] +[[package]] +name = "rstest" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5316d2a1479eeef1ea21e7f9ddc67c191d497abc8fc3ba2467857abbb68330" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04a9df72cc1f67020b0d63ad9bfe4a323e459ea7eb68e03bd9824db49f9a4c25" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn 2.0.39", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1243,7 +1541,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver", + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.22", ] [[package]] @@ -1286,6 +1593,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "semver-parser" version = "0.10.2" @@ -1396,12 +1709,33 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -1639,6 +1973,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 391f1786a..8692f3e47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,8 @@ description = "a programming language for writing zkapps" [dependencies] ark-ec = "0.3.0" # elliptic curve library -ark-ff = "0.3.0" # finite field library +ark-ff = "0.3.0" +ark-bls12-381 = "0.3.0" ark-serialize = "0.3.0" # serialization of arkworks types ena = "0.14.0" # union-find implementation for the wiring num-bigint = "0.4.3" # big int library @@ -28,3 +29,6 @@ serde_json = "1.0.85" # to (de)serialize JSON serde = "1.0.144" # to (de)serialize objects thiserror = "1.0.31" # helpful error traits toml = "0.8.8" # to parse manifest files +constraint_writers = { git = "https://github.com/iden3/circom.git", tag = "v2.1.8"} # to generate r1cs file +num-bigint-dig = "0.6.0" # to adapt for circom lib +rstest = "0.19.0" # for testing different backend cases diff --git a/src/backends/kimchi/mod.rs b/src/backends/kimchi/mod.rs index 7cdf13c13..19c5cad0a 100644 --- a/src/backends/kimchi/mod.rs +++ b/src/backends/kimchi/mod.rs @@ -243,8 +243,8 @@ impl Backend for KimchiVesta { builtin::poseidon } - fn witness_vars(&self) -> &HashMap> { - &self.witness_vars + fn witness_vars(&self, var: CellVar) -> &Value { + self.witness_vars.get(&var.index).unwrap() } fn new_internal_var(&mut self, val: Value, span: Span) -> CellVar { @@ -370,7 +370,7 @@ impl Backend for KimchiVesta { for (col, var) in row_of_vars.iter().enumerate() { let val = if let Some(var) = var { // if it's a public output, defer it's computation - if matches!(self.witness_vars()[&var.index], Value::PublicOutput(_)) { + if matches!(self.witness_vars(*var), Value::PublicOutput(_)) { public_outputs_vars .entry(*var) .or_default() diff --git a/src/backends/mod.rs b/src/backends/mod.rs index bb96de44e..a66770c33 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -1,31 +1,38 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use ark_ff::{Field, Zero}; +use num_bigint::BigUint; use crate::{ - circuit_writer::{DebugInfo, GateKind}, compiler::Sources, constants::Span, error::{Error, ErrorKind, Result}, - helpers::PrettyField, imports::FnHandle, var::{CellVar, Value, Var}, witness::WitnessEnv, }; +use self::{kimchi::KimchiVesta, r1cs::R1csBls12_381}; + pub mod kimchi; +pub mod r1cs; + +pub enum BackendKind { + KimchiVesta(KimchiVesta), + R1CS(R1csBls12_381), +} // TODO: should it be cloneable? It is now so because FnInfo needs to be cloneable. pub trait Backend: Clone { /// The circuit field / scalar field that the circuit is written on. - type Field: Field + PrettyField; + type Field: Field + FromStr + TryFrom + TryInto + Into; /// The generated witness type for the backend. Each backend may define its own witness format to be generated. type GeneratedWitness; /// This provides a standard way to access to all the internal vars. /// Different backends should be accessible in the same way by the variable index. - fn witness_vars(&self) -> &HashMap>; + fn witness_vars(&self, var: CellVar) -> &Value; // TODO: as the builtins grows, we might better change this to a crypto struct that holds all the builtin function pointers. /// poseidon crypto builtin function for different backends @@ -76,17 +83,24 @@ pub trait Backend: Clone { /// - The symbolic variables are stored in the witness_vars. /// - The computed values are stored in the cached_values. fn compute_var(&self, env: &mut WitnessEnv, var: CellVar) -> Result { - // fetch cache first - // TODO: if self was &mut, then we could use a Value::Cached(Field) to store things instead of that - if let Some(res) = env.cached_values.get(&var) { + self.compute_val(env, self.witness_vars(var), var.index) + } + + fn compute_val( + &self, + env: &mut WitnessEnv, + val: &Value, + var_index: usize, + ) -> Result { + if let Some(res) = env.cached_values.get(&var_index) { return Ok(*res); } - match &self.witness_vars()[&var.index] { + match &val { Value::Hint(func) => { let res = func(self, env) .expect("that function doesn't return a var (type checker error)"); - env.cached_values.insert(var, res); + env.cached_values.insert(var_index, res); Ok(res) } Value::Constant(c) => Ok(*c), @@ -95,20 +109,20 @@ pub trait Backend: Clone { for (coeff, var) in lc { res += self.compute_var(env, *var)? * *coeff; } - env.cached_values.insert(var, res); // cache + env.cached_values.insert(var_index, res); // cache Ok(res) } Value::Mul(lhs, rhs) => { let lhs = self.compute_var(env, *lhs)?; let rhs = self.compute_var(env, *rhs)?; let res = lhs * rhs; - env.cached_values.insert(var, res); // cache + env.cached_values.insert(var_index, res); // cache Ok(res) } Value::Inverse(v) => { let v = self.compute_var(env, *v)?; let res = v.inverse().unwrap_or_else(Self::Field::zero); - env.cached_values.insert(var, res); // cache + env.cached_values.insert(var_index, res); // cache Ok(res) } Value::External(name, idx) => Ok(env.get_external(name)[*idx]), diff --git a/src/backends/r1cs/builtin.rs b/src/backends/r1cs/builtin.rs new file mode 100644 index 000000000..b1072d506 --- /dev/null +++ b/src/backends/r1cs/builtin.rs @@ -0,0 +1,20 @@ +use ark_bls12_381::Fr; + +use crate::{ + circuit_writer::{CircuitWriter, VarInfo}, + constants::Span, + error::Result, + var::Var, +}; + +use super::R1csBls12_381; + +// todo: impl this +pub fn poseidon( + compiler: &mut CircuitWriter, + vars: &[VarInfo], + span: Span, +) -> Result>> { + // dummy for now + unimplemented!() +} diff --git a/src/backends/r1cs/mod.rs b/src/backends/r1cs/mod.rs new file mode 100644 index 000000000..7c323dfcc --- /dev/null +++ b/src/backends/r1cs/mod.rs @@ -0,0 +1,501 @@ +pub mod builtin; +pub mod snarkjs; + +use std::collections::{HashMap, HashSet}; + +use ark_bls12_381::Fr; +use ark_ff::{Field, Zero}; +use std::ops::Neg; + +use itertools::izip; +use kimchi::circuits::polynomials::foreign_field_add::witness; +use num_bigint_dig::{BigInt, Sign}; + +use crate::error::{Error, ErrorKind}; +use crate::{ + circuit_writer::DebugInfo, + var::{CellVar, Value}, +}; + +use super::Backend; + +/// Linear combination of variables and constants. +/// For example, the linear combination is represented as a * f_a + b * f_b + f_c +/// f_a and f_b are the coefficients of a and b respectively. +/// a and b are represented by CellVar. +/// The constant f_c is represented by the constant field, will always multiply with the variable at index 0 which is always 1. +#[derive(Clone, Debug)] +pub struct LinearCombination { + pub terms: HashMap, + pub constant: Fr, +} + +impl LinearCombination { + /// Evaluate the linear combination with the given witness. + fn evaluate(&self, witness: &[Fr]) -> Fr { + let mut sum = Fr::zero(); + + for (var, factor) in &self.terms { + sum += *witness.get(var.index).unwrap() * factor; + } + + sum += &self.constant; + + sum + } + + /// Create a linear combination to represent constant one. + fn one() -> Self { + LinearCombination { + terms: HashMap::new(), + constant: Fr::from(1), + } + } + + /// Create a linear combination to represent constant zero. + fn zero() -> Self { + LinearCombination { + terms: HashMap::new(), + constant: Fr::from(0), + } + } + + /// Create a linear combination from a list of vars + fn from_vars(vars: Vec) -> Self { + let terms = vars.into_iter().map(|var| (var, Fr::from(1))).collect(); + LinearCombination { + terms, + constant: Fr::from(0), + } + } + + /// Create a linear combination from a constant. + fn from_const(cst: Fr) -> Self { + LinearCombination { + terms: HashMap::new(), + constant: cst, + } + } +} + +/// an R1CS constraint +/// Each constraint comprises of 3 linear combinations from 3 matrices. +/// It represents a constraint in math: a * b = c. +#[derive(Clone, Debug)] +pub struct Constraint { + pub a: LinearCombination, + pub b: LinearCombination, + pub c: LinearCombination, +} + +impl Constraint { + /// Convert the 3 linear combinations to an array. + fn as_array(&self) -> [&LinearCombination; 3] { + [&self.a, &self.b, &self.c] + } +} + +/// R1CS backend with bls12_381 field. +#[derive(Clone)] +pub struct R1csBls12_381 { + /// Constraints in the r1cs. + constraints: Vec, + witness_vars: Vec>, + debug_info: Vec, + /// Record the public inputs for reordering the witness vector + public_inputs: Vec, + /// Record the public outputs for reordering the witness vector + public_outputs: Vec, + finalized: bool, +} + +impl R1csBls12_381 { + pub fn new() -> Self { + Self { + constraints: Vec::new(), + witness_vars: Vec::new(), + debug_info: Vec::new(), + public_inputs: Vec::new(), + public_outputs: Vec::new(), + finalized: false, + } + } + + // todo: how can we create this r1cs backend with different associated field types, but still using the same backend implementation? using macro? + /// So that we can get the associated field type, instead of passing it as a parameter here. + /// There are two fields supported by the snarkjs for r1cs: bn128 and ark_bls12_381. + /// Currently we are using ark_bls12_381 + fn prime(&self) -> BigInt { + BigInt::from_slice_native(Sign::Plus, Fr::characteristic()) + } + + /// Add an r1cs constraint that is 3 linear combinations. + /// This represents one constraint: a * b = c + fn add_constraint(&mut self, note: &str, c: Constraint, span: crate::constants::Span) { + let debug_info = DebugInfo { + note: note.to_string(), + span, + }; + self.debug_info.push(debug_info); + + self.constraints.push(c); + } + + /// Compute the number of private inputs + /// based on the number of all witness variables, public inputs and public outputs. + fn private_input_number(&self) -> usize { + self.witness_vars.len() - self.public_inputs.len() - self.public_outputs.len() + } +} + +#[derive(Debug)] +/// An intermediate struct for SnarkjsExporter to reorder the witness and convert to snarkjs format. +pub struct GeneratedWitness { + pub witness: Vec, +} + +impl Backend for R1csBls12_381 { + type Field = Fr; + + type GeneratedWitness = GeneratedWitness; + + fn witness_vars(&self, var: CellVar) -> &Value { + self.witness_vars.get(var.index).unwrap() + } + + fn poseidon() -> crate::imports::FnHandle { + builtin::poseidon + } + + fn new_internal_var( + &mut self, + val: crate::var::Value, + span: crate::constants::Span, + ) -> CellVar { + let var = CellVar::new(self.witness_vars.len(), span); + + // store it in the circuit_writer + self.witness_vars.insert(var.index, val); + + var + } + + fn add_constant( + &mut self, + //todo: do we need this? + label: Option<&'static str>, + value: Self::Field, + span: crate::constants::Span, + ) -> CellVar { + let x = self.new_internal_var(Value::Constant(value), span); + self.assert_eq_const(&x, value, span); + + x + } + + /// Final checks for generating the circuit. + /// todo: we might need to rethink about this interface + /// - private_input_indices are not needed in this r1cs backend. + /// - main_span could be better initialized with the backend, so it doesn't have to pass in here? + /// - we might just need the returned_cells argument, as the backend can record the public outputs itself? + fn finalize_circuit( + &mut self, + public_output: Option>, + returned_cells: Option>, + _private_input_indices: Vec, + _main_span: crate::constants::Span, + ) -> crate::error::Result<()> { + // store the return value in the public input that was created for that + if let Some(public_output) = public_output { + let cvars = &public_output.cvars; + + for (pub_var, ret_var) in cvars.clone().iter().zip(returned_cells.unwrap()) { + // replace the computation of the public output vars with the actual variables being returned here + let var_idx = pub_var.idx().unwrap(); + let prev = &self.witness_vars[var_idx]; + assert!(matches!(prev, Value::PublicOutput(None))); + self.witness_vars[var_idx] = Value::PublicOutput(Some(ret_var)); + } + } + + // for sanity check, we make sure that every cellvar created has ended up in an r1cs constraint + let mut written_vars = HashSet::new(); + for constraint in &self.constraints { + for lc in constraint.as_array() { + for var in lc.terms.keys() { + written_vars.insert(var.index); + } + } + } + + // check if every cell vars end up being a cell var in the circuit or public output + for (index, _) in self.witness_vars.iter().enumerate() { + assert!( + written_vars.contains(&index), + "there's a bug in the circuit_writer, some cellvar does not end up being a cellvar in the circuit!" + ); + } + + self.finalized = true; + + Ok(()) + } + + /// Generate the witnesses + /// This process should check if the constraints are satisfied. + fn generate_witness( + &self, + witness_env: &mut crate::witness::WitnessEnv, + ) -> crate::error::Result { + assert!(self.finalized, "the circuit is not finalized yet!"); + + // generate witness through witness vars vector + let mut witness = self + .witness_vars + .iter() + .enumerate() + .map(|(index, val)| { + match val { + // Defer calculation for output vars. + // The reasoning behind this is to avoid deep recursion potentially triggered by the public output var at the beginning. + Value::PublicOutput(_) => Ok(Fr::zero()), + _ => self.compute_val(witness_env, val, index), + } + }) + .collect::>>()?; + + // The original vars of public outputs are not part of the constraints + // so we need to compute them separately + for var in &self.public_outputs { + let val = self.compute_var(witness_env, *var)?; + witness[var.index] = val; + } + + for (index, (constraint, debug_info)) in + izip!(&self.constraints, &self.debug_info).enumerate() + { + // assert a * b = c + let ab = constraint.a.evaluate(&witness) * constraint.b.evaluate(&witness); + let c = constraint.c.evaluate(&witness); + + if ab != c { + return Err(Error::new( + "runtime", + ErrorKind::InvalidWitness(index), + debug_info.span, + )); + } + } + + Ok(GeneratedWitness { witness }) + } + + // todo: we can think of a format for r1cs for easier debugging + fn generate_asm(&self, sources: &crate::compiler::Sources, debug: bool) -> String { + todo!() + } + + fn neg(&mut self, x: &CellVar, span: crate::constants::Span) -> CellVar { + // To constrain: + // x + (-x) = 0 + // given: + // a * b = c + // then: + // a = x + (-x) + // b = 1 + // c = 0 + let one = Fr::from(1); + let zero = Fr::from(0); + + let x_neg = + self.new_internal_var(Value::LinearCombination(vec![(one.neg(), *x)], zero), span); + + let a = LinearCombination::from_vars(vec![*x, x_neg]); + let b = LinearCombination::one(); + let c = LinearCombination::zero(); + + self.add_constraint("neg constraint: x + (-x) = 0", Constraint { a, b, c }, span); + + x_neg + } + + fn add(&mut self, lhs: &CellVar, rhs: &CellVar, span: crate::constants::Span) -> CellVar { + // To constrain: + // lhs + rhs = res + // given: + // a * b = c + // then: + // a = lhs + rhs + // b = 1 + // c = res + let one = Fr::from(1); + let zero = Fr::from(0); + + let res = self.new_internal_var( + Value::LinearCombination(vec![(one, *lhs), (one, *rhs)], zero), + span, + ); + + // IMPORTANT: since terms use CellVar as key, + // HashMap automatically overrides it to single one term if the two vars are the same CellVar + let a = if lhs == rhs { + LinearCombination { + terms: HashMap::from_iter(vec![(*lhs, Fr::from(2))]), + constant: zero, + } + } else { + LinearCombination::from_vars(vec![*lhs, *rhs]) + }; + + let b = LinearCombination::one(); + let c = LinearCombination::from_vars(vec![res]); + + self.add_constraint( + "add constraint: lhs + rhs = res", + Constraint { a, b, c }, + span, + ); + + res + } + + fn add_const( + &mut self, + x: &CellVar, + cst: &Self::Field, + span: crate::constants::Span, + ) -> CellVar { + // To constrain: + // x + cst = res + // given: + // a * b = c + // then: + // a = x + cst + // b = 1 + // c = res + let one = Fr::from(1); + + let res = self.new_internal_var(Value::LinearCombination(vec![(one, *x)], *cst), span); + + let a = LinearCombination { + terms: HashMap::from_iter(vec![(*x, one)]), + constant: *cst, + }; + + let b = LinearCombination::one(); + let c = LinearCombination::from_vars(vec![res]); + + self.add_constraint( + "add constraint: x + cst = res", + Constraint { a, b, c }, + span, + ); + + res + } + + fn mul(&mut self, lhs: &CellVar, rhs: &CellVar, span: crate::constants::Span) -> CellVar { + // To constrain: + // lhs * rhs = res + // given: + // a * b = c + // then: + // a = lhs + // b = rhs + // c = res + + let res = self.new_internal_var(Value::Mul(*lhs, *rhs), span); + + let a = LinearCombination::from_vars(vec![*lhs]); + let b = LinearCombination::from_vars(vec![*rhs]); + let c = LinearCombination::from_vars(vec![res]); + + self.add_constraint( + "mul constraint: lhs * rhs = res", + Constraint { a, b, c }, + span, + ); + + res + } + + fn mul_const( + &mut self, + x: &CellVar, + cst: &Self::Field, + span: crate::constants::Span, + ) -> CellVar { + // To constrain: + // x * cst = res + // given: + // a * b = c + // then: + // a = x + // b = cst + // c = res + + let res = self.new_internal_var(Value::Scale(*cst, *x), span); + + let a = LinearCombination::from_vars(vec![*x]); + let b = LinearCombination::from_const(*cst); + let c = LinearCombination::from_vars(vec![res]); + + self.add_constraint( + "mul constraint: x * cst = res", + Constraint { a, b, c }, + span, + ); + + res + } + + fn assert_eq_const(&mut self, x: &CellVar, cst: Self::Field, span: crate::constants::Span) { + // To constrain: + // x = cst + // given: + // a * b = c + // then: + // a = x + // b = 1 + // c = cst + + let a = LinearCombination::from_vars(vec![*x]); + let b = LinearCombination::one(); + let c = LinearCombination::from_const(cst); + + self.add_constraint("eq constraint: x = cst", Constraint { a, b, c }, span); + } + + fn assert_eq_var(&mut self, lhs: &CellVar, rhs: &CellVar, span: crate::constants::Span) { + // To constrain: + // lhs = rhs + // given: + // a * b = c + // then: + // a = lhs + // b = 1 + // c = rhs + + let a = LinearCombination::from_vars(vec![*lhs]); + let b = LinearCombination::one(); + let c = LinearCombination::from_vars(vec![*rhs]); + + self.add_constraint("eq constraint: lhs = rhs", Constraint { a, b, c }, span); + } + + /// Adds the public input cell vars. + fn add_public_input(&mut self, val: Value, span: crate::constants::Span) -> CellVar { + let var = self.new_internal_var(val, span); + self.public_inputs.push(var); + + var + } + + /// Adds the public output cell vars. + fn add_public_output(&mut self, val: Value, span: crate::constants::Span) -> CellVar { + let var = self.new_internal_var(val, span); + self.public_outputs.push(var); + + var + } +} diff --git a/src/backends/r1cs/snarkjs.rs b/src/backends/r1cs/snarkjs.rs new file mode 100644 index 000000000..d0c29f099 --- /dev/null +++ b/src/backends/r1cs/snarkjs.rs @@ -0,0 +1,437 @@ +use ark_bls12_381::Fr; +use ark_ff::fields::PrimeField; + +use crate::error::Result; +use constraint_writers::r1cs_writer::{ConstraintSection, HeaderData, R1CSWriter}; +use itertools::Itertools; + +use std::collections::{HashMap, HashSet}; +use std::fs::{File, OpenOptions}; +use std::io::{BufWriter, Write}; +use std::io::{Seek, SeekFrom}; +use std::vec; + +// use ark_ff::BigInteger; + +use super::{GeneratedWitness, LinearCombination, R1csBls12_381}; +use num_bigint_dig::BigInt; + +#[derive(Debug)] +struct SnarkjsConstraint { + pub a: SnarkjsLinearCombination, + pub b: SnarkjsLinearCombination, + pub c: SnarkjsLinearCombination, +} + +#[derive(Debug)] +struct SnarkjsLinearCombination { + pub terms: HashMap, + pub constant: BigInt, +} + +impl SnarkjsLinearCombination { + fn to_hashmap(&self) -> HashMap { + let mut terms = self.terms.clone(); + + // add the constant term with var indexed at 0 + if terms.insert(0, self.constant.clone()).is_some() { + // sanity check + panic!("The first var should be preserved for constant term"); + } + + terms + } +} + +/// Calculate the number of bytes for the prime field. +fn field_size(prime: &BigInt) -> usize { + if prime.bits() % 64 == 0 { + prime.bits() / 8 + } else { + (prime.bits() / 64 + 1) * 8 + } +} + +/// A struct to export r1cs circuit and witness to the snarkjs formats. +pub struct SnarkjsExporter { + /// A R1CS backend with the circuit finalized. + r1cs_backend: R1csBls12_381, + /// A mapping between the witness vars' indexes in the backend and the new indexes arranged for the snarkjs format. + /// : The key (left) is the original index of the witness var in the backend, and the value (right) is the new index. + /// This mapping is used to re-arrange the witness values to align with the snarkjs format. + /// - The format assumes the public outputs and inputs are at the beginning of the witness vector. + /// - The variables in the constraints needs this mapping to reference to the new witness vector. + witness_map: HashMap, +} + +impl SnarkjsExporter { + /// During the initialization, the witness map is created to re-arrange the witness vector. + /// The reordering of witness vars: + /// 1. The first var is always reserved and valued as 1. + /// 2. The public outputs are stacked first. + /// 3. The public inputs are stacked next. + /// 4. The rest of the witness vars are stacked last. + pub fn new(r1cs_backend: R1csBls12_381) -> SnarkjsExporter { + let mut witness_map = HashMap::new(); + + // group all the public items together + // outputs are intended to be before inputs + let public_items = r1cs_backend + .public_outputs + .iter() + .chain(r1cs_backend.public_inputs.iter()); + + // keep track of the public vars index for easy lookup + let mut public_items_set: HashSet = HashSet::new(); + + for (index, var) in public_items.enumerate() { + // first var is fixed, so here we start from 1 + witness_map.insert(var.index, index + 1); + public_items_set.insert(var.index); + } + + // stack in the rest of the witness vars + for (index, _) in r1cs_backend.witness_vars.iter().enumerate() { + if public_items_set.contains(&index) { + continue; + } + witness_map.insert(index, witness_map.len() + 1); + } + + // witness_map should have all the witness vars + assert_eq!(r1cs_backend.witness_vars.len(), witness_map.len()); + + SnarkjsExporter { + r1cs_backend, + witness_map, + } + } + + /// Restructure the linear combination to align with the snarkjs format. + /// - use witness mapper to re-arrange the variables. + /// - convert the factors to BigInt. + fn restructure_lc(&self, lc: &LinearCombination) -> SnarkjsLinearCombination { + let terms = lc + .terms + .iter() + .map(|(cvar, factor)| { + let new_index: usize = *self.witness_map.get(&cvar.index).unwrap(); + let factor_bigint = Self::convert_to_bigint(factor); + + (new_index, factor_bigint) + }) + .collect(); + + let constant = Self::convert_to_bigint(&lc.constant); + + SnarkjsLinearCombination { terms, constant } + } + + /// Restructure the constraints to align with the snarkjs format. + fn restructure_constraints(&self) -> Vec { + let mut constraints = vec![]; + for constraint in &self.r1cs_backend.constraints { + constraints.push(SnarkjsConstraint { + a: self.restructure_lc(&constraint.a), + b: self.restructure_lc(&constraint.b), + c: self.restructure_lc(&constraint.c), + }); + } + constraints + } + + /// Restructure the witness vector + /// 1. add the first var that is always valued as 1. + /// 2. use witness mapper to re-arrange the variables. + /// 3. convert the witness values to BigInt. + /// 4. convert to a witness vector ordered by new index. + fn restructure_witness(&self, generated_witness: GeneratedWitness) -> Vec { + let mut restructured_witness_values = HashMap::new(); + + // add the first var that is always valued as 1 + restructured_witness_values.insert(0, BigInt::from(1)); + + for (id, value) in generated_witness.witness.iter().enumerate() { + let new_index = self.witness_map.get(&id).unwrap(); + let value_bigint = Self::convert_to_bigint(value); + + restructured_witness_values.insert(*new_index, value_bigint); + } + + // convert to vector ordered by new index + restructured_witness_values + .iter() + .sorted_by(|a, b| a.0.cmp(b.0)) + .map(|x| x.1.clone()) + .collect::>() + } + + /// Generate the r1cs file in snarkjs format. + /// It uses the circom rust library to generate the r1cs file. + /// The binary format spec: https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md + pub fn gen_r1cs_file(&self, file: &str) { + let prime = self.r1cs_backend.prime(); + let field_size = field_size(&prime); + + let r1cs = R1CSWriter::new(file.to_string(), field_size, false).unwrap(); + let mut constraint_section = R1CSWriter::start_constraints_section(r1cs).unwrap(); + + let restructure_constraints = self.restructure_constraints(); + + for constraint in &restructure_constraints { + ConstraintSection::write_constraint_usize( + &mut constraint_section, + &constraint.a.to_hashmap(), + &constraint.b.to_hashmap(), + &constraint.c.to_hashmap(), + ); + } + + let r1cs = constraint_section.end_section().unwrap(); + let mut header_section = R1CSWriter::start_header_section(r1cs).unwrap(); + let header_data = HeaderData { + // Snarkjs uses this meta data to determine which curve to use. + field: prime, + // Both the sizes of public inputs and outputs will be crucial for the snarkjs to determine + // where to extract the public inputs and outputs from the witness vector. + // Snarkjs prove command will generate a public.json file that contains the public inputs and outputs. + // So if the sizes are not correct, the public.json will not be generated correctly, which might potential contains the private inputs. + public_outputs: self.r1cs_backend.public_outputs.len(), + public_inputs: self.r1cs_backend.public_inputs.len(), + // There seems no use of this field in the snarkjs lib. It might be just a reference. + private_inputs: self.r1cs_backend.private_input_number(), + // Add one to take into account the first var that is only added during the witness formation for snarkjs. + total_wires: self.witness_map.len() + 1, + // This is for circom lang debugging, so we don't need it. + number_of_labels: 0, + number_of_constraints: restructure_constraints.len(), + }; + header_section.write_section(header_data); + + let r1cs = header_section.end_section().unwrap(); + R1CSWriter::finish_writing(r1cs); + } + + /// Generate the wtns file in snarkjs format. + pub fn gen_wtns_file(&self, file: &str, witness: GeneratedWitness) { + let restructured_witness = self.restructure_witness(witness); + + let mut witness_writer = WitnessWriter::new(file).unwrap(); + + witness_writer.write(restructured_witness, &self.r1cs_backend.prime()); + } + + fn convert_to_bigint(value: &Fr) -> BigInt { + BigInt::from_bytes_le( + num_bigint_dig::Sign::Plus, + &ark_ff::BigInteger::to_bytes_le(&value.into_repr()), + ) + } +} + +/// Witness writer for generating the wtns file in snarkjs format. +/// The circom rust lib seems not to have the API to generate the wtns file, so we create our own based on the snarkjs lib. +/// The implementation follows: https://github.com/iden3/snarkjs/blob/577b3f358016a486402050d3b7242876082c085f/src/wtns_utils.js#L25 +/// It uses the same binary format as the r1cs file relies on. +/// Although it is the same binary file protocol, but the witness file has simpler data points than the r1cs file. +struct WitnessWriter { + inner: BufWriter, + writing_section: Option, + section_size_position: u64, +} + +/// A struct to represent a section in the snarkjs binary format. +struct WritingSection; + +impl WitnessWriter { + // Initialize a FileWriter + fn new(path: &str) -> Result { + // file type for the witness file + let file_type = "wtns"; + // version of the file format + let version = 2u32; + // total number of sections + let n_sections = 2u32; + + let file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + let mut writer = BufWriter::new(file); + + // Write the file type (magic string) as bytes + let file_type_bytes = file_type.as_bytes(); + if file_type_bytes.len() != 4 { + panic!("File type must be 4 characters long"); + } + writer.write_all(file_type_bytes); + + // Write the version as a 32-bit unsigned integer in little endian + writer.write_all(&version.to_le_bytes()); + + // Write the number of sections as a 32-bit unsigned integer in little endian + writer.write_all(&n_sections.to_le_bytes()); + + let current_position = writer.stream_position().unwrap(); + + Ok(WitnessWriter { + inner: writer, + writing_section: None, + section_size_position: current_position, + }) + } + + /// Start a new section for writing. + fn start_write_section(&mut self, id_section: u32) { + // Write the section ID as ULE32 + self.inner.write_all(&id_section.to_le_bytes()); + // Get the current position + self.section_size_position = self.inner.stream_position().unwrap(); + // Temporarily write 0 as ULE64 for the section size + self.inner.write_all(&0u64.to_le_bytes()); + self.writing_section = Some(WritingSection); + } + + /// End the current section + fn end_write_section(&mut self) { + let current_pos = self.inner.stream_position().unwrap(); + // Calculate the size of the section + let section_size = current_pos - self.section_size_position - 8; + + // Move back to where the size needs to be written + self.inner.seek(SeekFrom::Start(self.section_size_position)); + // Write the actual section size + self.inner.write_all(§ion_size.to_le_bytes()); + // Return to the end of the section + self.inner.seek(SeekFrom::Start(current_pos)); + // Flush the buffer to ensure all data is written to the file + self.inner.flush(); + + self.writing_section = None; + } + + /// Write the witness to the file + /// It stores the two sections: + /// - Header section: describes the field size, prime field, and the number of witnesses. + /// - Witness section: contains the witness values. + fn write(&mut self, witness: Vec, prime: &BigInt) { + // Start writing the first section + self.start_write_section(1); + // Write field size in number of bytes + let field_n_bytes = field_size(prime); + self.inner.write_all(&(field_n_bytes as u32).to_le_bytes()); + // Write the prime field in bytes + self.write_big_int(prime.clone(), field_n_bytes); + // Write the number of witnesses + self.inner.write_all(&(witness.len() as u32).to_le_bytes()); + + self.end_write_section(); + + // Start writing the second section + self.start_write_section(2); + + /// Write the witness values to the file + /// Each witness value occupies the same number of bytes as the prime field + for value in witness { + self.write_big_int(value.clone(), field_n_bytes as usize); + } + self.end_write_section(); + } + + /// Write a BigInt to the file + fn write_big_int(&mut self, value: BigInt, size: usize) { + let bytes = value.to_bytes_le().1; + + let mut buffer = vec![0u8; size]; + buffer[..bytes.len()].copy_from_slice(&bytes); + self.inner.write_all(&buffer); + } +} + +#[cfg(test)] +mod tests { + use ark_bls12_381::Fr; + use num_bigint_dig::BigInt; + + use crate::{ + backends::{r1cs::R1csBls12_381, Backend}, + constants::Span, + var::Value, + witness::WitnessEnv, + }; + + #[test] + fn test_restructure_witness() { + let mut r1cs = R1csBls12_381::new(); + + // mock a constraint + let span = Span::default(); + let var1_val = 2; + let public_input_val = 3; + let sum_val = var1_val + public_input_val; + let var1 = r1cs.new_internal_var(Value::Constant(Fr::from(var1_val)), span); + let public_input_var = + r1cs.add_public_input(Value::Constant(Fr::from(public_input_val)), span); + let sum_var = r1cs.add(&var1, &public_input_var, span); + r1cs.add_public_output(Value::PublicOutput(Some(sum_var)), span); + let public_output_val = sum_val; + + // convert witness to snarkjs format + let mut snarkjs_exporter = super::SnarkjsExporter::new(r1cs); + assert_eq!( + snarkjs_exporter.witness_map.len(), + snarkjs_exporter.r1cs_backend.witness_vars.len() + ); + + // mock finalizing the circuit + snarkjs_exporter.r1cs_backend.finalized = true; + let mut witness_env = WitnessEnv::default(); + let generated_witness = snarkjs_exporter + .r1cs_backend + .generate_witness(&mut witness_env); + + let rearranged_witness = snarkjs_exporter.restructure_witness(generated_witness.unwrap()); + + assert_eq!( + rearranged_witness, + vec![ + // first var is always 1 + BigInt::from(1), + // output ordered before input + BigInt::from(public_output_val), + // public input + BigInt::from(public_input_val), + // private inputs (the rest of the witness vars) + BigInt::from(var1_val), + BigInt::from(sum_val), + ] + ); + + // instead, maybe refactor this test to check if the constraits are evaluated to zero with the reordered witness? + // so we just check the accrued results (to simplify this test) + let restructure_constraints = snarkjs_exporter.restructure_constraints(); + for (rc, oc) in restructure_constraints + .iter() + .zip(snarkjs_exporter.r1cs_backend.constraints.iter()) + { + // concat the terms from a b c + let all_original_terms = [oc.a.terms.clone(), oc.b.terms.clone(), oc.c.terms.clone()]; + let all_restructured_terms = + [rc.a.terms.clone(), rc.b.terms.clone(), rc.c.terms.clone()]; + + for (oterms, rterms) in all_original_terms.iter().zip(all_restructured_terms) { + assert_eq!(oterms.len(), rterms.len()); + for original_var in oterms.keys() { + // check if the original var is in the restructured terms after reordering + let new_index = snarkjs_exporter + .witness_map + .get(&original_var.index) + .unwrap(); + assert!(rterms.contains_key(new_index)); + } + } + } + } +} diff --git a/src/bin/noname.rs b/src/bin/noname.rs index e10aeb16f..aff2d3f4a 100644 --- a/src/bin/noname.rs +++ b/src/bin/noname.rs @@ -1,8 +1,8 @@ use clap::Parser as _; use miette::Result; use noname::cli::{ - cmd_build, cmd_check, cmd_init, cmd_new, cmd_prove, cmd_test, cmd_verify, CmdBuild, CmdCheck, - CmdInit, CmdNew, CmdProve, CmdTest, CmdVerify, + cmd_build, cmd_check, cmd_init, cmd_new, cmd_prove, cmd_run, cmd_test, cmd_verify, CmdBuild, + CmdCheck, CmdInit, CmdNew, CmdProve, CmdRun, CmdTest, CmdVerify, }; #[derive(clap::Parser)] @@ -33,10 +33,10 @@ enum Commands { // Remove the target directory. This command does not currently work //Clean, - /// Run the main function and produce a proof - Run(CmdProve), + /// Generate circuit and witness + Run(CmdRun), - /// An alias of the `--run` command. + /// Run the main function and produce a proof Prove(CmdProve), /// Verify a proof. This command does not currently work @@ -59,7 +59,7 @@ fn main() -> Result<()> { Commands::Check(args) => cmd_check(args), // Commands::Add => todo!(), // Commands::Clean => todo!(), - Commands::Run(args) => cmd_prove(args), + Commands::Run(args) => cmd_run(args), Commands::Prove(args) => cmd_prove(args), Commands::Verify(args) => cmd_verify(args), diff --git a/src/cli/cmd_build_and_check.rs b/src/cli/cmd_build_and_check.rs index 3eb59456f..37d95365c 100644 --- a/src/cli/cmd_build_and_check.rs +++ b/src/cli/cmd_build_and_check.rs @@ -7,6 +7,7 @@ use crate::{ prover::{ProverIndex, VerifierIndex}, KimchiVesta, }, + r1cs::{snarkjs::SnarkjsExporter, R1csBls12_381}, Backend, }, cli::packages::path_to_package, @@ -295,3 +296,73 @@ pub fn cmd_test(args: CmdTest) -> miette::Result<()> { Ok(()) } + +#[derive(clap::Parser)] +pub struct CmdRun { + /// noname file to run + #[clap(short, long, value_parser)] + path: Option, + + /// Backend to use for running the noname file. + /// supported backends: + /// - `snarkjs-r1cs` + #[clap(short, long, value_parser)] + backend: Option, + + /// JSON encoding of the public inputs. For example: `--public-inputs {"a": "1", "b": ["2", "3"]}`. + #[clap(long, value_parser, default_value = "{}")] + public_inputs: Option, + + /// JSON encoding of the private inputs. Similar to `--public-inputs` but for private inputs. + #[clap(long, value_parser, default_value = "{}")] + private_inputs: Option, +} + +pub fn cmd_run(args: CmdRun) -> miette::Result<()> { + let curr_dir = args + .path + .unwrap_or_else(|| std::env::current_dir().unwrap().try_into().unwrap()); + + let backend = args.backend.unwrap(); + + // parse inputs + let public_inputs = if let Some(s) = args.public_inputs { + parse_inputs(&s)? + } else { + JsonInputs::default() + }; + + let private_inputs = if let Some(s) = args.private_inputs { + parse_inputs(&s)? + } else { + JsonInputs::default() + }; + + match backend.as_str() { + "kimchi-vesta" => todo!(), + "snarkjs-r1cs" => { + // produce all TASTs + let (sources, tast) = produce_all_asts(&curr_dir)?; + println!("running noname file"); + + let r1cs = R1csBls12_381::new(); + let compiled_circuit = compile(&sources, tast, r1cs)?; + + let generated_witness = + compiled_circuit.generate_witness(public_inputs, private_inputs)?; + + let snarkjs_exporter = SnarkjsExporter::new(compiled_circuit.circuit.backend); + + snarkjs_exporter.gen_r1cs_file(&curr_dir.join("output.r1cs").into_string()); + + snarkjs_exporter.gen_wtns_file( + &curr_dir.join("output.wtns").into_string(), + generated_witness, + ); + } + _ => miette::bail!("unknown backend: `{}`", backend), + } + + // + Ok(()) +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 014abd89b..84fb80ee8 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,7 +4,9 @@ pub mod cmd_prove_and_verify; pub mod manifest; pub mod packages; -pub use cmd_build_and_check::{cmd_build, cmd_check, cmd_test, CmdBuild, CmdCheck, CmdTest}; +pub use cmd_build_and_check::{ + cmd_build, cmd_check, cmd_run, cmd_test, CmdBuild, CmdCheck, CmdRun, CmdTest, +}; pub use cmd_new_and_init::{cmd_init, cmd_new, CmdInit, CmdNew}; pub use cmd_prove_and_verify::{cmd_prove, cmd_verify, CmdProve, CmdVerify}; diff --git a/src/inputs.rs b/src/inputs.rs index 5477288f8..baaa95fdb 100644 --- a/src/inputs.rs +++ b/src/inputs.rs @@ -39,7 +39,7 @@ pub enum ParsingError { // /// An input is a name, and a list of field elements (in decimal). -#[derive(Default, serde::Deserialize)] +#[derive(Default, serde::Deserialize, Clone)] pub struct JsonInputs(pub HashMap); pub fn parse_inputs(s: &str) -> Result { diff --git a/src/tests/examples.rs b/src/tests/examples.rs index cc0fd37b8..168ac120d 100644 --- a/src/tests/examples.rs +++ b/src/tests/examples.rs @@ -1,7 +1,13 @@ use std::path::Path; +use rstest::rstest; + use crate::{ - backends::kimchi::{KimchiVesta, VestaField}, + backends::{ + kimchi::{KimchiVesta, VestaField}, + r1cs::R1csBls12_381, + BackendKind, + }, compiler::{compile, typecheck_next_file, Sources}, inputs::{parse_inputs, ExtField}, type_checker::TypeChecker, @@ -12,6 +18,7 @@ fn test_file( public_inputs: &str, private_inputs: &str, expected_public_output: Vec, + backend: BackendKind, ) -> miette::Result<()> { let version = env!("CARGO_MANIFEST_DIR"); let prefix = Path::new(version).join("examples"); @@ -19,80 +26,110 @@ fn test_file( // read noname file let code = std::fs::read_to_string(prefix.clone().join(format!("{file_name}.no"))).unwrap(); - // compile - let mut sources = Sources::new(); - let mut tast = TypeChecker::new(); - let this_module = None; - let _node_id = typecheck_next_file( - &mut tast, - this_module, - &mut sources, - file_name.to_string(), - code.clone(), - 0, - ) - .unwrap(); - - let kimchi_vesta = KimchiVesta::new(false); - - let compiled_circuit = compile(&sources, tast, kimchi_vesta)?; - - let (prover_index, verifier_index) = compiled_circuit.compile_to_indexes().unwrap(); - - // check compiled ASM only if it's not too large - if prover_index.len() < 100 { - let expected_asm = - std::fs::read_to_string(prefix.clone().join(format!("{file_name}.asm"))).unwrap(); - - let obtained_asm = prover_index.asm(&mut Sources::new(), false); - if obtained_asm != expected_asm { - eprintln!("obtained:"); - eprintln!("{obtained_asm}"); - eprintln!("expected:"); - eprintln!("{expected_asm}"); - panic!("Obtained ASM does not match expected ASM"); - } - } - // parse inputs let public_inputs = parse_inputs(public_inputs).unwrap(); let private_inputs = parse_inputs(private_inputs).unwrap(); - // create proof - let (proof, full_public_inputs, public_output) = - prover_index.prove(&sources, public_inputs, private_inputs, false)?; - - if public_output != expected_public_output { - eprintln!("obtained by executing the circuit:"); - public_output.iter().for_each(|x| eprintln!("- {x}")); - eprintln!("passed as output by the verifier:"); - expected_public_output - .iter() - .for_each(|x| eprintln!("- {x}")); - panic!("Obtained output does not match expected output"); + match backend { + BackendKind::KimchiVesta(kimchi_vesta) => { + // compile + let mut sources = Sources::new(); + let mut tast = TypeChecker::new(); + let this_module = None; + let _node_id = typecheck_next_file( + &mut tast, + this_module, + &mut sources, + file_name.to_string(), + code.clone(), + 0, + ) + .unwrap(); + + let compiled_circuit = compile(&sources, tast, kimchi_vesta)?; + + let (prover_index, verifier_index) = compiled_circuit.compile_to_indexes().unwrap(); + + // check compiled ASM only if it's not too large + if prover_index.len() < 100 { + let expected_asm = + std::fs::read_to_string(prefix.clone().join(format!("{file_name}.asm"))) + .unwrap(); + + let obtained_asm = prover_index.asm(&mut Sources::new(), false); + if obtained_asm != expected_asm { + eprintln!("obtained:"); + eprintln!("{obtained_asm}"); + eprintln!("expected:"); + eprintln!("{expected_asm}"); + panic!("Obtained ASM does not match expected ASM"); + } + } + + // create proof + let (proof, full_public_inputs, public_output) = prover_index.prove( + &sources, + public_inputs.clone(), + private_inputs.clone(), + false, + )?; + + if public_output != expected_public_output { + eprintln!("obtained by executing the circuit:"); + public_output.iter().for_each(|x| eprintln!("- {x}")); + eprintln!("passed as output by the verifier:"); + expected_public_output + .iter() + .for_each(|x| eprintln!("- {x}")); + panic!("Obtained output does not match expected output"); + } + + // verify proof + verifier_index.verify(full_public_inputs, proof).unwrap(); + } + BackendKind::R1CS(r1cs) => { + // compile + let mut sources = Sources::new(); + let mut tast = TypeChecker::new(); + let this_module = None; + let _node_id = typecheck_next_file( + &mut tast, + this_module, + &mut sources, + file_name.to_string(), + code.clone(), + 0, + ) + .unwrap(); + + let compiled_circuit = compile(&sources, tast, r1cs)?; + + // this should check the constraints + compiled_circuit + .generate_witness(public_inputs.clone(), private_inputs.clone()) + .unwrap(); + } } - // verify proof - verifier_index.verify(full_public_inputs, proof).unwrap(); - Ok(()) } -#[test] -fn test_arithmetic() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_arithmetic(#[case] backend: BackendKind) -> miette::Result<()> { let public_inputs = r#"{"public_input": "1"}"#; let private_inputs = r#"{"private_input": "1"}"#; - println!("public inputs: {:?}", public_inputs); - println!("private inputs: {:?}", private_inputs); - - test_file("arithmetic", public_inputs, private_inputs, vec![])?; + test_file("arithmetic", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_public_output() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_public_output(#[case] backend: BackendKind) -> miette::Result<()> { let public_inputs = r#"{"public_input": "1"}"#; let private_inputs = r#"{"private_input": "1"}"#; @@ -101,13 +138,16 @@ fn test_public_output() -> miette::Result<()> { public_inputs, private_inputs, vec![8u32.into()], + backend, )?; Ok(()) } -#[test] -fn test_poseidon() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +//todo: #[case::r1cs(BackendKind::R1CS(R1CS::new()))] +fn test_poseidon(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{"private_input": ["1", "1"]}"#; let private_input = [1.into(), 1.into()]; let digest = crate::helpers::poseidon(private_input.clone()); @@ -119,73 +159,87 @@ fn test_poseidon() -> miette::Result<()> { let public_inputs = &format!(r#"{{"public_input": "{digest_dec}"}}"#); - test_file("poseidon", public_inputs, private_inputs, vec![])?; + test_file("poseidon", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_bool() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_bool(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{"private_input": false}"#; let public_inputs = r#"{"public_input": true}"#; - test_file("bool", public_inputs, private_inputs, vec![])?; + test_file("bool", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_mutable() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_mutable(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{"xx": "2", "yy": "3"}"#; let public_inputs = r#"{}"#; - test_file("mutable", public_inputs, private_inputs, vec![])?; + test_file("mutable", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_for_loop() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_for_loop(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{"private_input": ["2", "3", "4"]}"#; let public_inputs = r#"{"public_input": "9"}"#; - test_file("for_loop", public_inputs, private_inputs, vec![])?; + test_file("for_loop", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_array() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_array(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"public_input": ["1", "2"]}"#; - test_file("array", public_inputs, private_inputs, vec![])?; + test_file("array", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_equals() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_equals(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": ["3", "3"]}"#; - test_file("equals", public_inputs, private_inputs, vec![])?; + test_file("equals", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_types() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_types(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": "1", "yy": "2"}"#; - test_file("types", public_inputs, private_inputs, vec![])?; + test_file("types", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_const() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_const(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"player": "1"}"#; let expected_public_output = vec![VestaField::from(2)]; @@ -195,43 +249,58 @@ fn test_const() -> miette::Result<()> { public_inputs, private_inputs, expected_public_output, + backend, )?; Ok(()) } -#[test] -fn test_functions() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_functions(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"one": "1"}"#; - test_file("functions", public_inputs, private_inputs, vec![])?; + test_file("functions", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_methods() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_methods(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": "1"}"#; - test_file("methods", public_inputs, private_inputs, vec![])?; + test_file("methods", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_types_array() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_types_array(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": "1", "yy": "4"}"#; - test_file("types_array", public_inputs, private_inputs, vec![])?; + test_file( + "types_array", + public_inputs, + private_inputs, + vec![], + backend, + )?; Ok(()) } -#[test] -fn test_iterate() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_iterate(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"bedroom_holes": "2"}"#; let expected_public_output = vec![VestaField::from(4)]; @@ -241,37 +310,44 @@ fn test_iterate() -> miette::Result<()> { public_inputs, private_inputs, expected_public_output, + backend, )?; Ok(()) } -#[test] -fn test_assignment() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_assignment(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": "2"}"#; - test_file("assignment", public_inputs, private_inputs, vec![])?; + test_file("assignment", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_if_else() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_if_else(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{}"#; let public_inputs = r#"{"xx": "1"}"#; - test_file("if_else", public_inputs, private_inputs, vec![])?; + test_file("if_else", public_inputs, private_inputs, vec![], backend)?; Ok(()) } -#[test] -fn test_sudoku() -> miette::Result<()> { +#[rstest] +#[case::kimchi_vesta(BackendKind::KimchiVesta(KimchiVesta::new(false)))] +#[case::r1cs(BackendKind::R1CS(R1csBls12_381::new()))] +fn test_sudoku(#[case] backend: BackendKind) -> miette::Result<()> { let private_inputs = r#"{"solution": { "inner": ["9", "5", "3", "6", "2", "1", "7", "8", "4", "1", "4", "8", "7", "5", "9", "2", "6", "3", "2", "7", "6", "8", "3", "4", "9", "5", "1", "3", "6", "9", "2", "7", "5", "4", "1", "8", "4", "8", "5", "9", "1", "6", "3", "7", "2", "7", "1", "2", "3", "4", "8", "6", "9", "5", "6", "3", "7", "1", "8", "2", "5", "4", "9", "5", "2", "1", "4", "9", "7", "8", "3", "6", "8", "9", "4", "5", "6", "3", "1", "2", "7"] }}"#; let public_inputs = r#"{"grid": { "inner": ["0", "5", "3", "6", "2", "1", "7", "8", "4", "0", "4", "8", "7", "5", "9", "2", "6", "3", "2", "7", "6", "8", "3", "4", "9", "5", "1", "3", "6", "9", "2", "7", "0", "4", "1", "8", "4", "8", "5", "9", "1", "6", "3", "7", "2", "0", "1", "2", "3", "4", "8", "6", "9", "5", "6", "3", "0", "1", "8", "2", "5", "4", "9", "5", "2", "1", "4", "9", "0", "8", "3", "6", "8", "9", "4", "5", "6", "3", "1", "2", "7"] }}"#; - test_file("sudoku", public_inputs, private_inputs, vec![])?; + test_file("sudoku", public_inputs, private_inputs, vec![], backend)?; Ok(()) } diff --git a/src/witness.rs b/src/witness.rs index 9ee11abc0..5d163f90b 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -11,7 +11,6 @@ use crate::{ error::{Error, ErrorKind, Result}, inputs::JsonInputs, type_checker::FnInfo, - var::CellVar, }; #[derive(Debug, Default)] @@ -21,7 +20,7 @@ where { pub var_values: HashMap>, - pub cached_values: HashMap, + pub cached_values: HashMap, } impl WitnessEnv {