commit ef8ef3fc612c7ae04e4093fe9a5ae9dd83d0414b Author: gozzy Date: Sun Aug 28 18:48:50 2022 +0000 initialise diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1cefb07 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,714 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bellman_ce" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e1a2edf80a8ed042463f8888946f70fcd901f1615711bb253b7dc32b9c9fe73" +dependencies = [ + "bit-vec", + "byteorder", + "cfg-if", + "crossbeam", + "futures", + "num_cpus", + "pairing_ce", + "rand", +] + +[[package]] +name = "bit-vec" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4523a10839ffae575fb08aa3423026c8cb4687eef43952afb956229d4f246f7" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap-v3" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfac055d61c39ace5061621530f7f55651a261a4fba296ce1bad06d41a8de65e" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "clap_derive-v3", + "indexmap", + "lazy_static", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive-v3" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6dd675567eb3e35787bd2583d129e85fabc7503b0a093d08c51198a307e2091" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + +[[package]] +name = "ff_ce" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ced6646e4e98a53da162e38ffe9c865edbd7a2f9ff197067b0a8bf1114bf8a" +dependencies = [ + "byteorder", + "ff_derive_ce", + "hex", + "rand", +] + +[[package]] +name = "ff_derive_ce" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c052fa6d4c2f12305ec364bfb8ef884836f3f61ea015b202372ff996d1ac4b" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" + +[[package]] +name = "futures-executor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" + +[[package]] +name = "futures-sink" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" + +[[package]] +name = "futures-task" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" + +[[package]] +name = "futures-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "slab", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hex-literal" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" +dependencies = [ + "hex-literal-impl", + "proc-macro-hack", +] + +[[package]] +name = "hex-literal-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "indexmap" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +dependencies = [ + "autocfg", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memoffset" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "pairing_ce" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55ca3bd80245b5d43dd4467bc9ab5daf869bd76c6cd3ca54c4499b41923657d" +dependencies = [ + "byteorder", + "ff_ce", + "rand", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" + +[[package]] +name = "proc-macro-error" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zkutil" +version = "0.5.0" +dependencies = [ + "bellman_ce", + "byteorder", + "cfg-if", + "clap-v3", + "exitcode", + "hex-literal", + "itertools", + "num-bigint", + "num-traits", + "rand", + "serde", + "serde_json", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..17253bc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,69 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "zkutil" +version = "0.5.0" +authors = ["Roman Semenov "] +description = "Library for working with circom circuits" +homepage = "https://github.com/poma/zkutil" +documentation = "https://docs.rs/zkutil" +license = "MIT" +repository = "https://github.com/poma/zkutil" + +[lib] +crate-type = ["cdylib", "lib"] + +[[bin]] +name = "zkutil" +path = "src/main.rs" +[dependencies.bellman_ce] +version = "0.3.4" +default-features = false + +[dependencies.byteorder] +version = "1" + +[dependencies.cfg-if] +version = "0.1.10" + +[dependencies.clap] +version = "3.0.0-beta.1" +package = "clap-v3" + +[dependencies.exitcode] +version = "1.1.2" + +[dependencies.hex-literal] +version = "0.2.1" + +[dependencies.itertools] +version = "0.8.1" + +[dependencies.num-bigint] +version = "0.2.3" + +[dependencies.num-traits] +version = "0.2.8" + +[dependencies.rand] +version = "0.4" + +[dependencies.serde] +version = "1.0" +features = ["derive"] + +[dependencies.serde_json] +version = "1.0" + +[features] +default = ["bellman_ce/multicore"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..00ac429 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,34 @@ +[package] +name = "zkutil" +version = "0.5.0" +authors = ["Roman Semenov "] +description = "Library for working with circom circuits" +documentation = "https://docs.rs/zkutil" +homepage = "https://github.com/poma/zkutil" +license = "MIT" +repository = "https://github.com/poma/zkutil" +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] + +[[bin]] +name = "zkutil" +path = "src/main.rs" + +[dependencies] +rand = "0.4" +byteorder = "1" +exitcode = "1.1.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +num-bigint = "0.2.3" +num-traits = "0.2.8" +itertools = "0.8.1" +cfg-if = "0.1.10" +hex-literal = "0.2.1" +clap = { package = "clap-v3", version = "3.0.0-beta.1" } # todo: replace with official v3 when it's released to crates.io +bellman_ce = { version = "0.3.4", default-features = false } # active features depend on build type + +[features] +default = ["bellman_ce/multicore"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6626e22 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# zkUtil ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/poma/zkutil/Rust) [![Crates.io](https://img.shields.io/crates/v/zkutil.svg)](https://crates.io/crates/zkutil) + +A tool to work with zkSNARK circuits generated by circom compiler. Based on circom import code by @kobigurk + +**The generated keys are currently incompatible with upstream Websnark and require using a custom fork** + +## Usage examples: + +```shell script +# Getting help +> zkutil --help +zkutil +A tool to work with SNARK circuits generated by circom + +USAGE: + zkutil + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + export-keys Export proving and verifying keys compatible with snarkjs/websnark + generate-verifier Generate verifier smart contract + help Prints this message or the help of the given subcommand(s) + prove Generate a SNARK proof + setup Generate trusted setup parameters + verify Verify a SNARK proof + +# Getting help for a subcommand +> zkutil prove --help +zkutil-prove +Generate a SNARK proof + +USAGE: + zkutil prove [OPTIONS] + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -c, --circuit Circuit R1CS or JSON file [default: circuit.r1cs] + -p, --params Snark trusted setup parameters file [default: params.bin] + -r, --proof Output file for proof JSON [default: proof.json] + -i, --public Output file for public inputs JSON [default: public.json] + -w, --witness Witness JSON file [default: witness.json] + +# Suppose we have circuit file and a sample inputs +> ls +circuit.circom input.json + +# Compile the circuit +> circom -rw circuit.circom +Constraints: 10000 +Constraints: 20000 +Constraints: 30000 +Constraints: 40000 +Constraints: 50000 + +# Generate a local trusted setup +> zkutil setup +Loading circuit... +Generating trusted setup parameters... +Has generated 28296 points +Writing to file... +Done! + +# Calculate witness from the input.json +# At the moment we still need to calculate witness using snarkjs +> snarkjs calculatewitness + +# Generate a snark proof +> zkutil prove +Loading circuit... +Proving... +Saved proof.json and public.json + +# Verify the proof +> zkutil verify +Proof is correct + +# Generate a solidity verifier contract +> zkutil generate-verifier +Created verifier.sol + +# Export keys to snarkjs/websnark compatible format +> zkutil export-keys +Exporting params.bin... +Created proving_key.json and verification_key.json + +# Verify the same proof with snarkjs +> snarkjs verify +OK + +# Here's a list of files that we have after this +> ls +circuit.circom circuit.r1cs circuit.wasm input.json params.bin proof.json public.json Verifier.sol proving_key.json verifying_key.json witness.json +``` + +Also see `test.sh` for example + +# Installation + +Install Rust + +```shell script +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Install zkutil globally + +```shell script +cargo install zkutil +# Make sure `~/.cargo/bin` is in $PATH (should be added automatically during Rust installation) +``` + +Or alternatively you can compile and run it instead: + +```shell script +git clone https://github.com/poma/zkutil +cd zkutil +cargo run --release -- prove --help +``` \ No newline at end of file diff --git a/circuit.circom b/circuit.circom new file mode 100644 index 0000000..de461e6 --- /dev/null +++ b/circuit.circom @@ -0,0 +1,15 @@ +template Num2Bits(n) { + signal input in; + signal output out[n]; + var lc1=0; + + for (var i = 0; i> i) & 1; + out[i] * (out[i] -1 ) === 0; + lc1 += out[i] * 2**i; + } + + lc1 === in; +} + +component main = Num2Bits(8); \ No newline at end of file diff --git a/src/circom_circuit.rs b/src/circom_circuit.rs new file mode 100644 index 0000000..df52c17 --- /dev/null +++ b/src/circom_circuit.rs @@ -0,0 +1,572 @@ +#![allow(clippy::needless_range_loop)] +extern crate bellman_ce; +extern crate rand; + +use std::str; +use std::fs::{self, OpenOptions, File}; +use std::io::{BufReader, Read, Seek}; +use std::collections::BTreeMap; +use std::iter::repeat; +use std::sync::Arc; +use itertools::Itertools; +use rand::{Rng, OsRng}; + +use bellman_ce::{ + Circuit, + SynthesisError, + Variable, + Index, + ConstraintSystem, + LinearCombination, + source::QueryDensity, + groth16::{ + Parameters, + Proof, + generate_random_parameters as generate_random_parameters2, + prepare_verifying_key, + create_random_proof, + verify_proof, + prepare_prover, + }, + pairing::{ + Engine, + CurveAffine, + ff::PrimeField, + ff::ScalarEngine, + bn256::{ + Bn256, + Fq, + Fq2, + G1Affine, + G2Affine, + } + } +}; + +use crate::utils::{ + repr_to_big, + proof_to_hex, + p1_to_vec, + p2_to_vec, + pairing_to_vec, +}; + +#[derive(Serialize, Deserialize)] +struct CircuitJson { + pub constraints: Vec>>, + #[serde(rename = "nPubInputs")] + pub num_inputs: usize, + #[serde(rename = "nOutputs")] + pub num_outputs: usize, + #[serde(rename = "nVars")] + pub num_variables: usize, +} + +#[derive(Serialize, Deserialize)] +struct ProofJson { + pub protocol: String, + pub proof: Option, + pub pi_a: Vec, + pub pi_b: Vec>, + pub pi_c: Vec, +} + +#[derive(Serialize, Deserialize)] +struct ProvingKeyJson { + #[serde(rename = "polsA")] + pub pols_a: Vec>, + #[serde(rename = "polsB")] + pub pols_b: Vec>, + #[serde(rename = "polsC")] + pub pols_c: Vec>, + #[serde(rename = "A")] + pub a: Vec>, + #[serde(rename = "B1")] + pub b1: Vec>, + #[serde(rename = "B2")] + pub b2: Vec>>, + #[serde(rename = "C")] + pub c: Vec>>, + pub vk_alfa_1: Vec, + pub vk_beta_1: Vec, + pub vk_delta_1: Vec, + pub vk_beta_2: Vec>, + pub vk_delta_2: Vec>, + #[serde(rename = "hExps")] + pub h: Vec>, + pub protocol: String, + #[serde(rename = "nPublic")] + pub n_public: usize, + #[serde(rename = "nVars")] + pub n_vars: usize, + #[serde(rename = "domainBits")] + pub domain_bits: usize, + #[serde(rename = "domainSize")] + pub domain_size: usize, +} + +#[derive(Serialize, Deserialize)] +struct VerifyingKeyJson { + #[serde(rename = "IC")] + pub ic: Vec>, + pub vk_alfa_1: Vec, + pub vk_alpha_1: Vec, + pub vk_beta_2: Vec>, + pub vk_gamma_2: Vec>, + pub vk_delta_2: Vec>, + pub vk_alfabeta_12: Vec>>, + pub vk_alphabeta_12: Vec>>, + pub curve: String, + pub protocol: String, + #[serde(rename = "nPublic")] + pub inputs_count: usize, +} + +pub type Constraint = ( + Vec<(usize, ::Fr)>, + Vec<(usize, ::Fr)>, + Vec<(usize, ::Fr)>, +); + +#[derive(Clone)] +pub struct R1CS { + pub num_inputs: usize, + pub num_aux: usize, + pub num_variables: usize, + pub constraints: Vec>, +} + +#[derive(Clone)] +pub struct CircomCircuit { + pub r1cs: R1CS, + pub witness: Option>, + pub wire_mapping: Option>, + // debug symbols +} + +impl<'a, E: Engine> CircomCircuit { + pub fn get_public_inputs(&self) -> Option> { + match &self.witness { + None => None, + Some(w) => match &self.wire_mapping { + None => Some(w[1..self.r1cs.num_inputs].to_vec()), + Some(m) => Some(m[1..self.r1cs.num_inputs].iter().map(|i| w[*i]).collect_vec()), + } + } + } + + pub fn get_public_inputs_json(&self) -> String { + let inputs = self.get_public_inputs(); + let inputs = match inputs { + None => return String::from("[]"), + Some(inp) => inp.iter().map(|x| repr_to_big(x.into_repr())).collect_vec(), + }; + serde_json::to_string_pretty(&inputs).unwrap() + } +} + +/// Our demo circuit implements this `Circuit` trait which +/// is used during paramgen and proving in order to +/// synthesize the constraint system. +impl<'a, E: Engine> Circuit for CircomCircuit { + //noinspection RsBorrowChecker + fn synthesize>( + self, + cs: &mut CS + ) -> Result<(), SynthesisError> + { + let witness = &self.witness; + let wire_mapping = &self.wire_mapping; + for i in 1..self.r1cs.num_inputs { + cs.alloc_input( + || format!("variable {}", i), + || { + Ok(match witness { + None => E::Fr::from_str("1").unwrap(), + Some(w) => match wire_mapping { + None => w[i], + Some(m) => w[m[i]], + } + }) + }, + )?; + } + + for i in 0..self.r1cs.num_aux { + cs.alloc( + || format!("aux {}", i), + || { + Ok(match witness { + None => E::Fr::from_str("1").unwrap(), + Some(w) => match wire_mapping { + None => w[i + self.r1cs.num_inputs], + Some(m) => w[m[i + self.r1cs.num_inputs]], + }, + }) + }, + )?; + } + + let make_index = |index| + if index < self.r1cs.num_inputs { + Index::Input(index) + } else { + Index::Aux(index - self.r1cs.num_inputs) + }; + let make_lc = |lc_data: Vec<(usize, E::Fr)>| + lc_data.iter().fold( + LinearCombination::::zero(), + |lc: LinearCombination, (index, coeff)| lc + (*coeff, Variable::new_unchecked(make_index(*index))) + ); + for (i, constraint) in self.r1cs.constraints.iter().enumerate() { + cs.enforce(|| format!("constraint {}", i), + |_| make_lc(constraint.0.clone()), + |_| make_lc(constraint.1.clone()), + |_| make_lc(constraint.2.clone()), + ); + } + Ok(()) + } +} + +pub fn prove(circuit: CircomCircuit, params: &Parameters, mut rng: R) -> Result, SynthesisError> { + let mut params2 = params.clone(); + filter_params(&mut params2); + create_random_proof(circuit, ¶ms2, &mut rng) +} + +pub fn generate_random_parameters(circuit: CircomCircuit, mut rng: R) -> Result, SynthesisError> { + generate_random_parameters2(circuit, &mut rng) +} + +pub fn verify_circuit(circuit: &CircomCircuit, params: &Parameters, proof: &Proof) -> Result { + let inputs = match circuit.get_public_inputs() { + None => return Err(SynthesisError::AssignmentMissing), + Some(inp) => inp, + }; + verify_proof(&prepare_verifying_key(¶ms.vk), proof, &inputs) +} + +pub fn verify(params: &Parameters, proof: &Proof, inputs: &[E::Fr]) -> Result { + verify_proof(&prepare_verifying_key(¶ms.vk), proof, &inputs) +} + +pub fn create_verifier_sol(params: &Parameters) -> String { + // TODO: use a simple template engine + let bytes = include_bytes!("verifier_groth.sol"); + let template = String::from_utf8_lossy(bytes); + + let p1_to_str = |p: &::G1Affine| { + if p.is_zero() { + // todo: throw instead + return String::from(""); + } + let xy = p.into_xy_unchecked(); + let x = repr_to_big(xy.0.into_repr()); + let y = repr_to_big(xy.1.into_repr()); + format!("uint256({}), uint256({})", x, y) + }; + let p2_to_str = |p: &::G2Affine| { + if p.is_zero() { + // todo: throw instead + return String::from(""); + } + let xy = p.into_xy_unchecked(); + let x_c0 = repr_to_big(xy.0.c0.into_repr()); + let x_c1 = repr_to_big(xy.0.c1.into_repr()); + let y_c0 = repr_to_big(xy.1.c0.into_repr()); + let y_c1 = repr_to_big(xy.1.c1.into_repr()); + format!("[uint256({}), uint256({})], [uint256({}), uint256({})]", x_c1, x_c0, y_c1, y_c0) + }; + + let template = template.replace("<%vk_alfa1%>", &*p1_to_str(¶ms.vk.alpha_g1)); + let template = template.replace("<%vk_beta2%>", &*p2_to_str(¶ms.vk.beta_g2)); + let template = template.replace("<%vk_gamma2%>", &*p2_to_str(¶ms.vk.gamma_g2)); + let template = template.replace("<%vk_delta2%>", &*p2_to_str(¶ms.vk.delta_g2)); + + let template = template.replace("<%vk_ic_length%>", &*params.vk.ic.len().to_string()); + let template = template.replace("<%vk_input_length%>", &*(params.vk.ic.len() - 1).to_string()); + + let mut vi = String::from(""); + for i in 0..params.vk.ic.len() { + vi = format!("{}{}vk.IC[{}] = Pairing.G1Point({});\n", vi, if vi.is_empty() { "" } else { " " }, i, &*p1_to_str(¶ms.vk.ic[i])); + } + template.replace("<%vk_ic_pts%>", &*vi) +} + +pub fn create_verifier_sol_file(params: &Parameters, filename: &str) -> std::io::Result<()> { + fs::write(filename, create_verifier_sol(params).as_bytes()) +} + +pub fn proof_to_json(proof: &Proof) -> Result { + serde_json::to_string_pretty(&ProofJson { + protocol: "groth".to_string(), + proof: Some(proof_to_hex(&proof)), + pi_a: p1_to_vec(&proof.a), + pi_b: p2_to_vec(&proof.b), + pi_c: p1_to_vec(&proof.c), + }) +} + +pub fn proof_to_json_file(proof: &Proof, filename: &str) -> std::io::Result<()> { + let str = proof_to_json(proof).unwrap(); // TODO: proper error handling + fs::write(filename, str.as_bytes()) +} + +pub fn load_params_file(filename: &str) -> Parameters { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + load_params(reader) +} + +pub fn load_params(reader: R) -> Parameters { + Parameters::read(reader, true).expect("unable to read params") +} + +pub fn load_inputs_json_file(filename: &str) -> Vec { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + load_inputs_json::>(BufReader::new(reader)) +} + +pub fn load_inputs_json(reader: R) -> Vec { + let inputs: Vec = serde_json::from_reader(reader).unwrap(); + inputs.into_iter().map(|x| E::Fr::from_str(&x).unwrap()).collect::>() +} + +pub fn load_proof_json_file(filename: &str) -> Proof { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + load_proof_json(BufReader::new(reader)) +} + +pub fn load_proof_json(reader: R) -> Proof { + let proof: ProofJson = serde_json::from_reader(reader).unwrap(); + Proof { + a: G1Affine::from_xy_checked( + Fq::from_str(&proof.pi_a[0]).unwrap(), + Fq::from_str(&proof.pi_a[1]).unwrap(), + ).unwrap(), + b: G2Affine::from_xy_checked( + Fq2 { + c0: Fq::from_str(&proof.pi_b[0][0]).unwrap(), + c1: Fq::from_str(&proof.pi_b[0][1]).unwrap(), + }, + Fq2 { + c0: Fq::from_str(&proof.pi_b[1][0]).unwrap(), + c1: Fq::from_str(&proof.pi_b[1][1]).unwrap(), + }, + ).unwrap(), + c: G1Affine::from_xy_checked( + Fq::from_str(&proof.pi_c[0]).unwrap(), + Fq::from_str(&proof.pi_c[1]).unwrap(), + ).unwrap(), + } +} + +pub fn filter_params(params: &mut Parameters) { + params.vk.ic = params.vk.ic.clone().into_iter().filter(|x| !x.is_zero()).collect::>(); + params.h = Arc::new((*params.h).clone().into_iter().filter(|x| !x.is_zero()).collect::>()); + params.a = Arc::new((*params.a).clone().into_iter().filter(|x| !x.is_zero()).collect::>()); + params.b_g1 = Arc::new((*params.b_g1).clone().into_iter().filter(|x| !x.is_zero()).collect::>()); + params.b_g2 = Arc::new((*params.b_g2).clone().into_iter().filter(|x| !x.is_zero()).collect::>()); +} + +pub fn proving_key_json(params: &Parameters, circuit: CircomCircuit) -> Result { + let mut pols_a: Vec> = vec![]; + let mut pols_b: Vec> = vec![]; + let mut pols_c: Vec> = vec![]; + for _ in 0..circuit.r1cs.num_aux + circuit.r1cs.num_inputs { + pols_a.push(BTreeMap::new()); + pols_b.push(BTreeMap::new()); + pols_c.push(BTreeMap::new()); + } + for c in 0..circuit.r1cs.constraints.len() { + for item in circuit.r1cs.constraints[c].0.iter() { + pols_a[item.0].insert(c.to_string(), repr_to_big(item.1.into_repr())); + } + for item in circuit.r1cs.constraints[c].1.iter() { + pols_b[item.0].insert(c.to_string(), repr_to_big(item.1.into_repr())); + } + for item in circuit.r1cs.constraints[c].2.iter() { + pols_c[item.0].insert(c.to_string(), repr_to_big(item.1.into_repr())); + } + } + + for i in 0..circuit.r1cs.num_inputs { + pols_a[i].insert((circuit.r1cs.constraints.len() + i).to_string(), String::from("1")); + } + + let domain_bits = log2_floor(circuit.r1cs.constraints.len() + circuit.r1cs.num_inputs) + 1; + let n_public = circuit.r1cs.num_inputs - 1; + let n_vars = circuit.r1cs.num_variables; + + let p = prepare_prover(circuit).unwrap().assignment; + let mut a_iter = params.a.iter(); + let mut b1_iter = params.b_g1.iter(); + let mut b2_iter = params.b_g2.iter(); + let zero1 = G1Affine::zero(); + let zero2 = G2Affine::zero(); + let a = repeat(true).take(params.vk.ic.len()) + .chain(p.a_aux_density.iter()) + .map(|item| if item { a_iter.next().unwrap() } else { &zero1 }) + .map(|e| p1_to_vec(e)) + .collect_vec(); + let b1 = p.b_input_density.iter() + .chain(p.b_aux_density.iter()) + .map(|item| if item { b1_iter.next().unwrap() } else { &zero1 }) + .map(|e| p1_to_vec(e)) + .collect_vec(); + let b2 = p.b_input_density.iter() + .chain(p.b_aux_density.iter()) + .map(|item| if item { b2_iter.next().unwrap() } else { &zero2 }) + .map(|e| p2_to_vec(e)) + .collect_vec(); + let c = repeat(None).take(params.vk.ic.len()) + .chain(params.l.iter().map(|e| Some(p1_to_vec(e)))) + .collect_vec(); + + let proving_key = ProvingKeyJson { + pols_a, + pols_b, + pols_c, + a, + b1, + b2, + c, + vk_alfa_1: p1_to_vec(¶ms.vk.alpha_g1), + vk_beta_1: p1_to_vec(¶ms.vk.beta_g1), + vk_delta_1: p1_to_vec(¶ms.vk.delta_g1), + vk_beta_2: p2_to_vec(¶ms.vk.beta_g2), + vk_delta_2: p2_to_vec(¶ms.vk.delta_g2), + h: params.h.iter().map(|e| p1_to_vec(e)).collect_vec(), + protocol: String::from("groth"), + n_public, + n_vars, + domain_bits, + domain_size: 1 << domain_bits, + }; + + serde_json::to_string(&proving_key) +} + +fn log2_floor(num: usize) -> usize { + assert!(num > 0); + let mut pow = 0; + while (1 << (pow + 1)) <= num { + pow += 1; + } + pow +} + +pub fn proving_key_json_file(params: &Parameters, circuit: CircomCircuit, filename: &str) -> std::io::Result<()> { + let str = proving_key_json(params, circuit).unwrap(); // TODO: proper error handling + fs::write(filename, str.as_bytes()) +} + +pub fn verification_key_json(params: &Parameters) -> Result { + let verification_key = VerifyingKeyJson { + ic: params.vk.ic.iter().map(|e| p1_to_vec(e)).collect_vec(), + vk_alfa_1: p1_to_vec(¶ms.vk.alpha_g1), + vk_alpha_1: p1_to_vec(¶ms.vk.alpha_g1), + vk_beta_2: p2_to_vec(¶ms.vk.beta_g2), + vk_gamma_2: p2_to_vec(¶ms.vk.gamma_g2), + vk_delta_2: p2_to_vec(¶ms.vk.delta_g2), + vk_alfabeta_12: pairing_to_vec(&Bn256::pairing(params.vk.alpha_g1, params.vk.beta_g2)), + vk_alphabeta_12: pairing_to_vec(&Bn256::pairing(params.vk.alpha_g1, params.vk.beta_g2)), + inputs_count: params.vk.ic.len() - 1, + curve: String::from("BN254"), + protocol: String::from("groth"), + }; + serde_json::to_string_pretty(&verification_key) +} + +pub fn verification_key_json_file(params: &Parameters, filename: &str) -> std::io::Result<()> { + let str = verification_key_json(params).unwrap(); // TODO: proper error handling + fs::write(filename, str.as_bytes()) +} + +pub fn witness_from_json_file(filename: &str) -> Vec { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + witness_from_json::>(BufReader::new(reader)) +} + +pub fn witness_from_json(reader: R) -> Vec { + let witness: Vec = serde_json::from_reader(reader).unwrap(); + witness.into_iter().map(|x| E::Fr::from_str(&x).unwrap()).collect::>() +} + +pub fn witness_from_bin_file(filename: &str) -> Result, std::io::Error> { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + witness_from_bin::>(BufReader::new(reader)) +} + +pub fn witness_from_bin(reader: R) -> Result, std::io::Error> { + let file = crate::wtns_reader::read::(reader)?; + Ok(file.witness) +} + +pub fn r1cs_from_json_file(filename: &str) -> R1CS { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + r1cs_from_json(BufReader::new(reader)) +} + +pub fn r1cs_from_json(reader: R) -> R1CS { + let circuit_json: CircuitJson = serde_json::from_reader(reader).unwrap(); + + let num_inputs = circuit_json.num_inputs + circuit_json.num_outputs + 1; + let num_aux = circuit_json.num_variables - num_inputs; + + let convert_constraint = |lc: &BTreeMap| { + lc.iter().map(|(index, coeff)| (index.parse().unwrap(), E::Fr::from_str(coeff).unwrap())).collect_vec() + }; + + let constraints = circuit_json.constraints.iter().map( + |c| (convert_constraint(&c[0]), convert_constraint(&c[1]), convert_constraint(&c[2])) + ).collect_vec(); + + R1CS { + num_inputs, + num_aux, + num_variables: circuit_json.num_variables, + constraints, + } +} + +pub fn r1cs_from_bin(reader: R) -> Result<(R1CS, Vec), std::io::Error> { + let file = crate::r1cs_reader::read(reader)?; + let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize; + let num_variables = file.header.n_wires as usize; + let num_aux = num_variables - num_inputs; + Ok(( + R1CS { num_aux, num_inputs, num_variables, constraints: file.constraints, }, + file.wire_mapping.iter().map(|e| *e as usize).collect_vec() + )) +} + +pub fn r1cs_from_bin_file(filename: &str) -> Result<(R1CS, Vec), std::io::Error> { + let reader = OpenOptions::new() + .read(true) + .open(filename) + .expect("unable to open."); + r1cs_from_bin(BufReader::new(reader)) +} + +pub fn create_rng() -> Box { + Box::new(OsRng::new().unwrap()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6e282ca --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +#[macro_use] +extern crate serde; +#[macro_use] +extern crate hex_literal; +extern crate bellman_ce; +extern crate rand; +extern crate itertools; +extern crate byteorder; +extern crate num_bigint; +extern crate num_traits; + +pub mod utils; +pub mod circom_circuit; +pub mod r1cs_reader; +pub mod wtns_reader; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..17c9a38 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,253 @@ +extern crate clap; +extern crate bellman_ce; +extern crate zkutil; + +use std::fs; +use std::fs::File; +use std::path::Path; +use clap::Clap; +use bellman_ce::pairing::{ + Engine, + bn256::Bn256 +}; +use zkutil::circom_circuit::{ + prove as prove2, + verify as verify2, + create_rng, + load_params_file, + proof_to_json_file, + r1cs_from_json_file, + r1cs_from_bin_file, + witness_from_json_file, + witness_from_bin_file, + load_proof_json_file, + load_inputs_json_file, + create_verifier_sol_file, + proving_key_json_file, + verification_key_json_file, + generate_random_parameters, + CircomCircuit, + R1CS, +}; + +/// A tool to work with SNARK circuits generated by circom +#[derive(Clap)] +struct Opts { + #[clap(subcommand)] + command: SubCommand, +} + +#[derive(Clap)] +enum SubCommand { + /// Generate a SNARK proof + Prove(ProveOpts), + /// Verify a SNARK proof + Verify(VerifyOpts), + /// Generate trusted setup parameters + Setup(SetupOpts), + /// Generate verifier smart contract + GenerateVerifier(GenerateVerifierOpts), + /// Export proving and verifying keys compatible with snarkjs/websnark + ExportKeys(ExportKeysOpts), +} + +/// A subcommand for generating a SNARK proof +#[derive(Clap)] +struct ProveOpts { + /// Snark trusted setup parameters file + #[clap(short = "p", long = "params", default_value = "params.bin")] + params: String, + /// Circuit R1CS or JSON file [default: circuit.r1cs|circuit.json] + #[clap(short = "c", long = "circuit")] + circuit: Option, + /// Witness JSON file [default: witness.wtns|witness.json] + #[clap(short = "w", long = "witness")] + witness: Option, + /// Output file for proof JSON + #[clap(short = "r", long = "proof", default_value = "proof.json")] + proof: String, + /// Output file for public inputs JSON + #[clap(short = "o", long = "public", default_value = "public.json")] + public: String, +} + +/// A subcommand for verifying a SNARK proof +#[derive(Clap)] +struct VerifyOpts { + /// Snark trusted setup parameters file + #[clap(short = "p", long = "params", default_value = "params.bin")] + params: String, + /// Proof JSON file + #[clap(short = "r", long = "proof", default_value = "proof.json")] + proof: String, + /// Public inputs JSON file + #[clap(short = "i", long = "public", default_value = "public.json")] + public: String, +} + +/// A subcommand for generating a trusted setup parameters +#[derive(Clap)] +struct SetupOpts { + /// Snark trusted setup parameters file + #[clap(short = "p", long = "params", default_value = "params.bin")] + params: String, + /// Circuit R1CS or JSON file [default: circuit.r1cs|circuit.json] + #[clap(short = "c", long = "circuit")] + circuit: Option, +} + +/// A subcommand for generating a Solidity verifier smart contract +#[derive(Clap)] +struct GenerateVerifierOpts { + /// Snark trusted setup parameters file + #[clap(short = "p", long = "params", default_value = "params.bin")] + params: String, + /// Output smart contract name + #[clap(short = "v", long = "verifier", default_value = "Verifier.sol")] + verifier: String, +} + +/// A subcommand for exporting proving and verifying keys compatible with snarkjs/websnark +#[derive(Clap)] +struct ExportKeysOpts { + /// Snark trusted setup parameters file + #[clap(short = "p", long = "params", default_value = "params.bin")] + params: String, + /// Circuit R1CS or JSON file [default: circuit.r1cs|circuit.json] + #[clap(short = "c", long = "circuit")] + circuit: Option, + /// Output proving key file + #[clap(short = "r", long = "pk", default_value = "proving_key.json")] + pk: String, + /// Output verifying key file + #[clap(short = "v", long = "vk", default_value = "verification_key.json")] + vk: String, +} + +fn main() { + let opts: Opts = Opts::parse(); + match opts.command { + SubCommand::Prove(o) => { + prove(o); + } + SubCommand::Verify(o) => { + verify(o); + } + SubCommand::Setup(o) => { + setup(o); + } + SubCommand::GenerateVerifier(o) => { + generate_verifier(o); + } + SubCommand::ExportKeys(o) => { + export_keys(o); + } + } +} + +fn load_r1cs(filename: &str) -> R1CS { + if filename.ends_with("json") { + r1cs_from_json_file(filename) + } else { + let (r1cs, _wire_mapping) = r1cs_from_bin_file(filename).unwrap(); + r1cs + } +} + +fn resolve_circuit_file(filename: Option) -> String { + match filename { + Some(s) => s, + None => if Path::new("circuit.r1cs").exists() || !Path::new("circuit.json").exists() { + "circuit.r1cs".to_string() + } else { + "circuit.json".to_string() + } + } +} + +fn load_witness(filename: &str) -> Vec { + if filename.ends_with("json") { + witness_from_json_file::(filename) + } else { + witness_from_bin_file::(filename).unwrap() + } +} + +fn resolve_witness_file(filename: Option) -> String { + match filename { + Some(s) => s, + None => if Path::new("witness.wtns").exists() || !Path::new("witness.json").exists() { + "witness.wtns".to_string() + } else { + "witness.json".to_string() + } + } +} + +fn prove(opts: ProveOpts) { + let rng = create_rng(); + let params = load_params_file(&opts.params); + let circuit_file = resolve_circuit_file(opts.circuit); + let witness_file = resolve_witness_file(opts.witness); + println!("Loading circuit from {}...", circuit_file); + let circuit = CircomCircuit { + r1cs: load_r1cs(&circuit_file), + witness: Some(load_witness::(&witness_file)), + wire_mapping: None, + }; + println!("Proving..."); + let proof = prove2(circuit.clone(), ¶ms, rng).unwrap(); + proof_to_json_file(&proof, &opts.proof).unwrap(); + fs::write(&opts.public, circuit.get_public_inputs_json().as_bytes()).unwrap(); + println!("Saved {} and {}", opts.proof, opts.public); +} + +fn verify(opts: VerifyOpts) { + let params = load_params_file(&opts.params); + let proof = load_proof_json_file::(&opts.proof); + let inputs = load_inputs_json_file::(&opts.public); + let correct = verify2(¶ms, &proof, &inputs).unwrap(); + if correct { + println!("Proof is correct"); + } else { + println!("Proof is invalid!"); + std::process::exit(400); + } +} + +fn setup(opts: SetupOpts) { + let circuit_file = resolve_circuit_file(opts.circuit); + println!("Loading circuit from {}...", circuit_file); + let rng = create_rng(); + let circuit = CircomCircuit { + r1cs: load_r1cs(&circuit_file), + witness: None, + wire_mapping: None, + }; + println!("Generating trusted setup parameters..."); + let params = generate_random_parameters(circuit, rng).unwrap(); + println!("Writing to file..."); + let writer = File::create(&opts.params).unwrap(); + params.write(writer).unwrap(); + println!("Saved parameters to {}", opts.params); +} + +fn generate_verifier(opts: GenerateVerifierOpts) { + let params = load_params_file(&opts.params); + create_verifier_sol_file(¶ms, &opts.verifier).unwrap(); + println!("Created {}", opts.verifier); +} + +fn export_keys(opts: ExportKeysOpts) { + println!("Exporting {}...", opts.params); + let params = load_params_file(&opts.params); + let circuit_file = resolve_circuit_file(opts.circuit); + let circuit = CircomCircuit { + r1cs: load_r1cs(&circuit_file), + witness: None, + wire_mapping: None, + }; + proving_key_json_file(¶ms, circuit, &opts.pk).unwrap(); + verification_key_json_file(¶ms, &opts.vk).unwrap(); + println!("Created {} and {}.", opts.pk, opts.vk); +} diff --git a/src/r1cs_reader.rs b/src/r1cs_reader.rs new file mode 100644 index 0000000..1441c92 --- /dev/null +++ b/src/r1cs_reader.rs @@ -0,0 +1,227 @@ +#![allow(unused_variables, dead_code)] +use byteorder::{ReadBytesExt, LittleEndian}; +use std::{collections::HashMap, io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}}; +use bellman_ce::pairing::{ + Engine, + bn256::Bn256, + ff::{ + Field, PrimeField, PrimeFieldRepr, + } +}; +use crate::circom_circuit::Constraint; +#[cfg(test)] +use std::io::{BufReader, Cursor}; + +pub struct Header { + pub field_size: u32, + pub prime_size: Vec, + pub n_wires: u32, + pub n_pub_out: u32, + pub n_pub_in: u32, + pub n_prv_in: u32, + pub n_labels: u64, + pub n_constraints: u32, +} + +pub struct R1CSFile { + pub version: u32, + pub header: Header, + pub constraints: Vec>, + pub wire_mapping: Vec, +} + +fn read_field(mut reader: R) -> Result { + let mut repr = E::Fr::zero().into_repr(); + repr.read_le(&mut reader)?; + let fr = E::Fr::from_repr(repr) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + Ok(fr) +} + +fn read_header(mut reader: R, size: u64) -> Result
{ + let field_size = reader.read_u32::()?; + let mut prime_size = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime_size)?; + if size != 32 + field_size as u64 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid header section size")) + } + + Ok(Header { + field_size, + prime_size, + n_wires: reader.read_u32::()?, + n_pub_out: reader.read_u32::()?, + n_pub_in: reader.read_u32::()?, + n_prv_in: reader.read_u32::()?, + n_labels: reader.read_u64::()?, + n_constraints: reader.read_u32::()?, + }) +} + +fn read_constraint_vec(mut reader: R, header: &Header) -> Result> { + let n_vec = reader.read_u32::()? as usize; + let mut vec = Vec::with_capacity(n_vec); + for _ in 0..n_vec { + vec.push(( + reader.read_u32::()? as usize, + read_field::<&mut R, E>(&mut reader)?, + )); + } + Ok(vec) +} + +fn read_constraints(mut reader: R, size: u64, header: &Header) -> Result>> { + // todo check section size + let mut vec = Vec::with_capacity(header.n_constraints as usize); + for _ in 0..header.n_constraints { + vec.push(( + read_constraint_vec::<&mut R, E>(&mut reader, &header)?, + read_constraint_vec::<&mut R, E>(&mut reader, &header)?, + read_constraint_vec::<&mut R, E>(&mut reader, &header)?, + )); + } + Ok(vec) +} + +fn read_map(mut reader: R, size: u64, header: &Header) -> Result> { + if size != header.n_wires as u64 * 8 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid map section size")) + } + let mut vec = Vec::with_capacity(header.n_wires as usize); + for _ in 0..header.n_wires { + vec.push(reader.read_u64::()?); + } + if vec[0] != 0 { + return Err(Error::new(ErrorKind::InvalidData, "Wire 0 should always be mapped to 0")) + } + Ok(vec) +} + +pub fn read(mut reader: R) -> Result> { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + if magic != [0x72, 0x31, 0x63, 0x73] { // magic = "r1cs" + return Err(Error::new(ErrorKind::InvalidData, "Invalid magic number")) + } + + let version = reader.read_u32::()?; + if version != 1 { + return Err(Error::new(ErrorKind::InvalidData, "Unsupported version")) + } + + let num_sections = reader.read_u32::()?; + + // section type -> file offset + let mut sec_offsets = HashMap::::new(); + let mut sec_sizes = HashMap::::new(); + + // get file offset of each section + for _ in 0..num_sections { + let sec_type = reader.read_u32::()?; + let sec_size = reader.read_u64::()?; + let offset = reader.seek(SeekFrom::Current(0))?; + sec_offsets.insert(sec_type, offset); + sec_sizes.insert(sec_type, sec_size); + reader.seek(SeekFrom::Current(sec_size as i64))?; + } + + let header_type = 1; + let constraint_type = 2; + let wire2label_type = 3; + + reader.seek(SeekFrom::Start(*sec_offsets.get(&header_type).unwrap()))?; + let header = read_header(&mut reader, *sec_sizes.get(&header_type).unwrap())?; + if header.field_size != 32 { + return Err(Error::new(ErrorKind::InvalidData, "This parser only supports 32-byte fields")) + } + if header.prime_size != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") { + return Err(Error::new(ErrorKind::InvalidData, "This parser only supports bn256")) + } + + reader.seek(SeekFrom::Start(*sec_offsets.get(&constraint_type).unwrap()))?; + let constraints = read_constraints::<&mut R, Bn256>(&mut reader, *sec_sizes.get(&constraint_type).unwrap(), &header)?; + + reader.seek(SeekFrom::Start(*sec_offsets.get(&wire2label_type).unwrap()))?; + let wire_mapping = read_map(&mut reader, *sec_sizes.get(&wire2label_type).unwrap(), &header)?; + + + Ok(R1CSFile { version, header, constraints, wire_mapping }) +} + +#[test] +fn sample() { + let data = hex!(" + 72316373 + 01000000 + 03000000 + 01000000 40000000 00000000 + 20000000 + 010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430 + 07000000 + 01000000 + 02000000 + 03000000 + e8030000 00000000 + 03000000 + 02000000 88020000 00000000 + 02000000 + 05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 06000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 00000000 02000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 14000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 0C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 + 00000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 07000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 01000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 04000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 + 03000000 2C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 06000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 00000000 + 01000000 + 06000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 + 00000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 02000000 0B000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 01000000 + 06000000 58020000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 + 03000000 38000000 00000000 + 00000000 00000000 + 03000000 00000000 + 0a000000 00000000 + 0b000000 00000000 + 0c000000 00000000 + 0f000000 00000000 + 44010000 00000000 + "); + + use bellman_ce::pairing::ff; + let reader = BufReader::new(Cursor::new(&data[..])); + let file = read(reader).unwrap(); + assert_eq!(file.version, 1); + + assert_eq!(file.header.field_size, 32); + assert_eq!(file.header.prime_size, &hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430")); + assert_eq!(file.header.n_wires, 7); + assert_eq!(file.header.n_pub_out, 1); + assert_eq!(file.header.n_pub_in, 2); + assert_eq!(file.header.n_prv_in, 3); + assert_eq!(file.header.n_labels, 0x03e8); + assert_eq!(file.header.n_constraints, 3); + + assert_eq!(file.constraints.len(), 3); + assert_eq!(file.constraints[0].0.len(), 2); + assert_eq!(file.constraints[0].0[0].0, 5); + assert_eq!(file.constraints[0].0[0].1, ff::from_hex("0x03").unwrap()); + assert_eq!(file.constraints[2].1[0].0, 0); + assert_eq!(file.constraints[2].1[0].1, ff::from_hex("0x06").unwrap()); + assert_eq!(file.constraints[1].2.len(), 0); + + assert_eq!(file.wire_mapping.len(), 7); + assert_eq!(file.wire_mapping[1], 3); +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..5c51d7a --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,102 @@ +extern crate bellman_ce; +extern crate rand; +extern crate byteorder; +extern crate num_bigint; +extern crate num_traits; + +use std::fmt::Display; +use itertools::Itertools; +use num_bigint::BigUint; +use num_traits::Num; +use bellman_ce::{ + groth16::Proof, + pairing::{ + ff::PrimeField, + CurveAffine, + bn256::{ + G1Affine, + G2Affine, + Fq12, + Bn256, + }, + }, +}; + +pub fn repr_to_big(r: T) -> String { + BigUint::from_str_radix(&format!("{}", r)[2..], 16).unwrap().to_str_radix(10) +} + +pub fn repr_to_hex(r: T) -> String { + format!("{}", r)[2..].to_string() +} + +pub fn proof_to_hex(proof: &Proof) -> String { + let a = proof.a.into_xy_unchecked(); + let b = proof.b.into_xy_unchecked(); + let c = proof.c.into_xy_unchecked(); + [a.0, a.1, b.0.c1, b.0.c0, b.1.c1, b.1.c0, c.0, c.1] + .iter() + .map(|e| repr_to_hex(e.into_repr())) + .join("") +} + +pub fn p1_to_vec(p: &G1Affine) -> Vec { + let xy = p.into_xy_unchecked(); + vec![ + repr_to_big(xy.0.into_repr()), + repr_to_big(xy.1.into_repr()), + if p.is_zero() { "0".to_string() } else { "1".to_string() } + ] +} + +pub fn p2_to_vec(p: &G2Affine) -> Vec> { + let xy = p.into_xy_unchecked(); + vec![ + vec![ + repr_to_big(xy.0.c0.into_repr()), + repr_to_big(xy.0.c1.into_repr()), + ], + vec![ + repr_to_big(xy.1.c0.into_repr()), + repr_to_big(xy.1.c1.into_repr()), + ], + if p.is_zero() { + vec!["0".to_string(), "0".to_string()] + } else { + vec!["1".to_string(), "0".to_string()] + }, + ] +} + +pub fn pairing_to_vec(p: &Fq12) -> Vec>> { + vec![ + vec![ + vec![ + repr_to_big(p.c0.c0.c0.into_repr()), + repr_to_big(p.c0.c0.c1.into_repr()), + ], + vec![ + repr_to_big(p.c0.c1.c0.into_repr()), + repr_to_big(p.c0.c1.c1.into_repr()), + ], + vec![ + repr_to_big(p.c0.c2.c0.into_repr()), + repr_to_big(p.c0.c2.c1.into_repr()), + ], + ], + vec![ + vec![ + repr_to_big(p.c1.c0.c0.into_repr()), + repr_to_big(p.c1.c0.c1.into_repr()), + ], + vec![ + repr_to_big(p.c1.c1.c0.into_repr()), + repr_to_big(p.c1.c1.c1.into_repr()), + ], + vec![ + repr_to_big(p.c1.c2.c0.into_repr()), + repr_to_big(p.c1.c2.c1.into_repr()), + ], + ], + ] +} diff --git a/src/verifier_groth.sol b/src/verifier_groth.sol new file mode 100644 index 0000000..fe3ae34 --- /dev/null +++ b/src/verifier_groth.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +library Pairing { + uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + struct G1Point { + uint256 X; + uint256 Y; + } + + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint256[2] X; + uint256[2] Y; + } + + /* + * @return The negation of p, i.e. p.plus(p.negate()) should be zero + */ + function negate(G1Point memory p) internal pure returns (G1Point memory) { + // The prime q in the base field F_q for G1 + if (p.X == 0 && p.Y == 0) { + return G1Point(0, 0); + } else { + return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q)); + } + } + + /* + * @return r the sum of two points of G1 + */ + function plus( + G1Point memory p1, + G1Point memory p2 + ) internal view returns (G1Point memory r) { + uint256[4] memory input = [ + p1.X, p1.Y, + p2.X, p2.Y + ]; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) + // Use "invalid" to make gas estimation work + switch success case 0 { invalid() } + } + + require(success, "pairing-add-failed"); + } + + /* + * @return r the product of a point on G1 and a scalar, i.e. + * p == p.scalarMul(1) and p.plus(p) == p.scalarMul(2) for all + * points p. + */ + function scalarMul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { + uint256[3] memory input = [p.X, p.Y, s]; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) + // Use "invalid" to make gas estimation work + switch success case 0 { invalid() } + } + + require(success, "pairing-mul-failed"); + } + + /* @return The result of computing the pairing check + * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + * For example, + * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. + */ + function pairing( + G1Point memory a1, + G2Point memory a2, + G1Point memory b1, + G2Point memory b2, + G1Point memory c1, + G2Point memory c2, + G1Point memory d1, + G2Point memory d2 + ) internal view returns (bool) { + uint256[24] memory input = [ + a1.X, a1.Y, a2.X[0], a2.X[1], a2.Y[0], a2.Y[1], + b1.X, b1.Y, b2.X[0], b2.X[1], b2.Y[0], b2.Y[1], + c1.X, c1.Y, c2.X[0], c2.X[1], c2.Y[0], c2.Y[1], + d1.X, d1.Y, d2.X[0], d2.X[1], d2.Y[0], d2.Y[1] + ]; + uint256[1] memory out; + bool success; + + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 8, input, mul(24, 0x20), out, 0x20) + // Use "invalid" to make gas estimation work + switch success case 0 { invalid() } + } + + require(success, "pairing-opcode-failed"); + return out[0] != 0; + } +} + +contract Verifier { + uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + using Pairing for *; + + struct VerifyingKey { + Pairing.G1Point alfa1; + Pairing.G2Point beta2; + Pairing.G2Point gamma2; + Pairing.G2Point delta2; + Pairing.G1Point[<%vk_ic_length%>] IC; + } + + function verifyingKey() internal pure returns (VerifyingKey memory vk) { + vk.alfa1 = Pairing.G1Point(<%vk_alfa1%>); + vk.beta2 = Pairing.G2Point(<%vk_beta2%>); + vk.gamma2 = Pairing.G2Point(<%vk_gamma2%>); + vk.delta2 = Pairing.G2Point(<%vk_delta2%>); + <%vk_ic_pts%> + } + + /* + * @returns Whether the proof is valid given the hardcoded verifying key + * above and the public inputs + */ + function verifyProof( + bytes memory proof, + uint256[<%vk_input_length%>] memory input + ) public view returns (bool) { + uint256[8] memory p = abi.decode(proof, (uint256[8])); + for (uint8 i = 0; i < p.length; i++) { + // Make sure that each element in the proof is less than the prime q + require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q"); + } + Pairing.G1Point memory proofA = Pairing.G1Point(p[0], p[1]); + Pairing.G2Point memory proofB = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]); + Pairing.G1Point memory proofC = Pairing.G1Point(p[6], p[7]); + + VerifyingKey memory vk = verifyingKey(); + // Compute the linear combination vkX + Pairing.G1Point memory vkX = vk.IC[0]; + for (uint256 i = 0; i < input.length; i++) { + // Make sure that every input is less than the snark scalar field + require(input[i] < SNARK_SCALAR_FIELD, "verifier-input-gte-snark-scalar-field"); + vkX = Pairing.plus(vkX, Pairing.scalarMul(vk.IC[i + 1], input[i])); + } + + return Pairing.pairing( + Pairing.negate(proofA), + proofB, + vk.alfa1, + vk.beta2, + vkX, + vk.gamma2, + proofC, + vk.delta2 + ); + } +} + diff --git a/src/wtns_reader.rs b/src/wtns_reader.rs new file mode 100644 index 0000000..ee78faa --- /dev/null +++ b/src/wtns_reader.rs @@ -0,0 +1,94 @@ +use byteorder::{ReadBytesExt, LittleEndian}; +use std::io::{Read, Result, ErrorKind, Error}; +use bellman_ce::pairing::{ + Engine, + ff::{ + Field, PrimeField, PrimeFieldRepr, + } +}; + +pub struct Header { + pub field_size: u32, + pub prime_size: Vec, + pub witness_len: u32, +} + +pub struct WTNSFile { + pub version: u32, + pub header: Header, + pub witness: Vec, +} + +fn read_field(mut reader: R) -> Result { + let mut repr = E::Fr::zero().into_repr(); + repr.read_le(&mut reader)?; + let fr = E::Fr::from_repr(repr) + .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; + Ok(fr) +} + +fn read_header(mut reader: R, size: u64) -> Result
{ + let field_size = reader.read_u32::()?; + let mut prime_size = vec![0u8; field_size as usize]; + reader.read_exact(&mut prime_size)?; + //if size != 32 + field_size as u64 { + if size != 4 + 32 + 4 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid header section size")) + } + + Ok(Header { + field_size, + prime_size, + witness_len: reader.read_u32::()?, + }) +} + +fn read_witness(mut reader: R, size: u64, header: &Header) -> Result> { + if size != (header.witness_len * header.field_size) as u64 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid witness section size")); + } + let mut result = Vec::with_capacity(header.witness_len as usize); + for _ in 0..header.witness_len { + result.push(read_field::<&mut R, E>(&mut reader)?); + } + Ok(result) +} + +pub fn read(mut reader: R) -> Result> { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + if magic != [119, 116, 110, 115] { // magic = "wtns" + return Err(Error::new(ErrorKind::InvalidData, "Invalid magic number")) + } + + let version = reader.read_u32::()?; + if version > 2 { + return Err(Error::new(ErrorKind::InvalidData, "Unsupported version")) + } + + let _num_sections = reader.read_u32::()?; + + // todo: rewrite this to support different section order and unknown sections + // todo: handle sec_size correctly + let sec_type = reader.read_u32::()?; + if sec_type != 1 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid section type")); + } + let sec_size = reader.read_u64::()?; + let header = read_header(&mut reader, sec_size)?; + if header.field_size != 32 { + return Err(Error::new(ErrorKind::InvalidData, "This parser only supports 32-byte fields")) + } + if header.prime_size != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") { + return Err(Error::new(ErrorKind::InvalidData, "This parser only supports bn256")) + } + + let sec_type = reader.read_u32::()?; + if sec_type != 2 { + return Err(Error::new(ErrorKind::InvalidData, "Invalid section type")); + } + let sec_size = reader.read_u64::()?; + let witness = read_witness::<&mut R, E>(&mut reader, sec_size, &header)?; + + Ok(WTNSFile { version, header, witness }) +} diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..14f5f7c --- /dev/null +++ b/test.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +# Compile the circuit +npx circom -rw + +# Do a local trusted setup, generate params.bin +cargo run --release setup + +# Export proving and verifying keys compatible with snarkjs and websnark +cargo run --release export-keys + +# generate solidity verifier +cargo run --release generate-verifier + +# generate and verify proof +npx snarkjs calculatewitness # witness is still generated by snarkjs +cargo run --release prove +cargo run --release verify + +# Double check by verifying the same proof with snarkjs +npx snarkjs verify \ No newline at end of file