diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e39cff4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +*.png \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d753b97 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1157 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "derive_builder" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" +dependencies = [ + "darling", + "derive_builder_core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + +[[package]] +name = "ff" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "fibonacci" +version = "0.1.0" +dependencies = [ + "halo2_proofs", + "plotters", + "tabbycat", +] + +[[package]] +name = "float-ord" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-kit" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c9a156ec38864999bc9c4156e5f3b50224d4a5578028a64e5a3875caa9ee28" +dependencies = [ + "bitflags", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "servo-fontconfig", + "walkdir", + "winapi", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "freetype" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gif" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "group" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" +dependencies = [ + "byteorder", + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "git+https://github.com/zcash/halo2.git?rev=a898d65ae3ad3d41987666f6a03cfc15edae01c4#a898d65ae3ad3d41987666f6a03cfc15edae01c4" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "pasta_curves", + "plotters", + "rand_core", + "rayon", + "tabbycat", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[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.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "pasta_curves" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369d7785168ad7ff0cbe467d968ca3e19a927d8536b11ef9c21b4e454b15ba42" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "plotters" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" + +[[package]] +name = "plotters-bitmap" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21362fa905695e5618aefd169358f52e0e8bc4a8e05333cf780fda8cddc00b54" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabbycat" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45590f0f859197b4545be1b17b2bc3cc7bb075f7d1cc0ea1dc6521c0bf256a3" +dependencies = [ + "anyhow", + "derive_builder", + "regex", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "ttf-parser" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b1ec261 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fibonacci" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "halo2_examples" +path = "src/lib.rs" + +[features] +dev-graph = ["halo2_proofs/dev-graph", "plotters"] + +[dependencies] +halo2_proofs = { git = "https://github.com/zcash/halo2.git", rev = "a898d65ae3ad3d41987666f6a03cfc15edae01c4"} +plotters = { version = "0.3.0", optional = true } +tabbycat = { version = "0.1", features = ["attributes"], optional = true } diff --git a/README.md b/README.md new file mode 100644 index 0000000..29e663f --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Halo2 Examples + +This repo includes a few simple examples to illustrate how to write circuit in Halo2. + +## Instruction + +Compile the repo + +``` +cargo build +``` + +Run examples +``` +cargo test -- --nocapture test_example1 +cargo test -- --nocapture test_example2 +cargo test -- --nocapture test_example3 +``` + +Plot the circuit layout +``` +cargo test --all-features -- --nocapture test_merkle_v2 +cargo test --all-features -- --nocapture plot_fibo1 +cargo test --all-features -- --nocapture plot_fibo2 +``` diff --git a/src/fibonacci.rs b/src/fibonacci.rs new file mode 100644 index 0000000..25fa575 --- /dev/null +++ b/src/fibonacci.rs @@ -0,0 +1,4 @@ +mod example1; +mod example2; +mod example3; +mod example4; \ No newline at end of file diff --git a/src/fibonacci/example1.rs b/src/fibonacci/example1.rs new file mode 100644 index 0000000..084af61 --- /dev/null +++ b/src/fibonacci/example1.rs @@ -0,0 +1,219 @@ +use std::marker::PhantomData; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; + +#[derive(Debug, Clone)] +struct FibonacciConfig { + pub col_a: Column, + pub col_b: Column, + pub col_c: Column, + pub selector: Selector, + pub instance: Column, +} + +#[derive(Debug, Clone)] +struct FibonacciChip { + config: FibonacciConfig, + _marker: PhantomData, +} + +impl FibonacciChip { + pub fn construct(config: FibonacciConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure(meta: &mut ConstraintSystem) -> FibonacciConfig { + let col_a = meta.advice_column(); + let col_b = meta.advice_column(); + let col_c = meta.advice_column(); + let selector = meta.selector(); + let instance = meta.instance_column(); + + meta.enable_equality(col_a); + meta.enable_equality(col_b); + meta.enable_equality(col_c); + meta.enable_equality(instance); + + meta.create_gate("add", |meta| { + // + // col_a | col_b | col_c | selector + // a b c s + // + let s = meta.query_selector(selector); + let a = meta.query_advice(col_a, Rotation::cur()); + let b = meta.query_advice(col_b, Rotation::cur()); + let c = meta.query_advice(col_c, Rotation::cur()); + vec![s * (a + b - c)] + }); + + FibonacciConfig { + col_a, + col_b, + col_c, + selector, + instance, + } + } + + #[allow(clippy::type_complexity)] + pub fn assign_first_row( + &self, + mut layouter: impl Layouter, + ) -> Result<(AssignedCell, AssignedCell, AssignedCell), Error> { + layouter.assign_region( + || "first row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + let a_cell = region.assign_advice_from_instance( + || "f(0)", + self.config.instance, + 0, + self.config.col_a, + 0)?; + + let b_cell = region.assign_advice_from_instance( + || "f(1)", + self.config.instance, + 1, + self.config.col_b, + 0)?; + + let c_cell = region.assign_advice( + || "a + b", + self.config.col_c, + 0, + || a_cell.value().copied() + b_cell.value(), + )?; + + Ok((a_cell, b_cell, c_cell)) + }, + ) + } + + pub fn assign_row( + &self, + mut layouter: impl Layouter, + prev_b: &AssignedCell, + prev_c: &AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "next row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + // Copy the value from b & c in previous row to a & b in current row + prev_b.copy_advice( + || "a", + &mut region, + self.config.col_a, + 0, + )?; + prev_c.copy_advice( + || "b", + &mut region, + self.config.col_b, + 0, + )?; + + let c_cell = region.assign_advice( + || "c", + self.config.col_c, + 0, + || prev_b.value().copied() + prev_c.value(), + )?; + + Ok(c_cell) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: &AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } +} + +#[derive(Default)] +struct MyCircuit(PhantomData); + +impl Circuit for MyCircuit { + type Config = FibonacciConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + FibonacciChip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = FibonacciChip::construct(config); + + let (_, mut prev_b, mut prev_c) = + chip.assign_first_row(layouter.namespace(|| "first row"))?; + + for _i in 3..10 { + let c_cell = + chip.assign_row(layouter.namespace(|| "next row"), &prev_b, &prev_c)?; + + prev_b = prev_c; + prev_c = c_cell; + } + + chip.expose_public(layouter.namespace(|| "out"), &prev_c, 2)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use super::MyCircuit; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn fibonacci_example1() { + let k = 4; + + let a = Fp::from(1); // F[0] + let b = Fp::from(1); // F[1] + let out = Fp::from(55); // F[9] + + let circuit = MyCircuit(PhantomData); + + let mut public_input = vec![a, b, out]; + + let prover = MockProver::run(k, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + } + + #[cfg(feature = "dev-graph")] + #[test] + fn plot_fibonacci1() { + use plotters::prelude::*; + + let root = BitMapBackend::new("fib-1-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Fib 1 Layout", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit::(PhantomData); + halo2_proofs::dev::CircuitLayout::default() + .render(4, &circuit, &root) + .unwrap(); + } +} diff --git a/src/fibonacci/example2.rs b/src/fibonacci/example2.rs new file mode 100644 index 0000000..950f07d --- /dev/null +++ b/src/fibonacci/example2.rs @@ -0,0 +1,188 @@ +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; +use std::marker::PhantomData; + +#[derive(Debug, Clone)] +struct ACell(AssignedCell); + +#[derive(Debug, Clone)] +struct FibonacciConfig { + advice: Column, + selector: Selector, + instance: Column, +} + +#[derive(Debug, Clone)] +struct FibonacciChip { + config: FibonacciConfig, + _marker: PhantomData, +} + +impl FibonacciChip { + pub fn construct(config: FibonacciConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure( + meta: &mut ConstraintSystem, + advice: Column, + instance: Column, + ) -> FibonacciConfig { + let selector = meta.selector(); + + meta.enable_equality(advice); + meta.enable_equality(instance); + + meta.create_gate("add", |meta| { + // + // advice | selector + // a | s + // b | + // c | + // + let s = meta.query_selector(selector); + let a = meta.query_advice(advice, Rotation::cur()); + let b = meta.query_advice(advice, Rotation::next()); + let c = meta.query_advice(advice, Rotation(2)); + vec![s * (a + b - c)] + }); + + FibonacciConfig { + advice, + selector, + instance, + } + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + nrows: usize, + ) -> Result, Error> { + layouter.assign_region( + || "entire fibonacci table", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + self.config.selector.enable(&mut region, 1)?; + + let mut a_cell = region.assign_advice_from_instance( + || "1", + self.config.instance, + 0, + self.config.advice, + 0, + )?; + let mut b_cell = region.assign_advice_from_instance( + || "1", + self.config.instance, + 1, + self.config.advice, + 1, + )?; + + for row in 2..nrows { + if row < nrows - 2 { + self.config.selector.enable(&mut region, row)?; + } + + let c_cell = region.assign_advice( + || "advice", + self.config.advice, + row, + || a_cell.value().copied() + b_cell.value(), + )?; + + a_cell = b_cell; + b_cell = c_cell; + } + + Ok(b_cell) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } +} + +#[derive(Default)] +struct MyCircuit(PhantomData); + +impl Circuit for MyCircuit { + type Config = FibonacciConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let advice = meta.advice_column(); + let instance = meta.instance_column(); + FibonacciChip::configure(meta, advice, instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = FibonacciChip::construct(config); + + let out_cell = chip.assign(layouter.namespace(|| "entire table"), 10)?; + + chip.expose_public(layouter.namespace(|| "out"), out_cell, 2)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::MyCircuit; + use std::marker::PhantomData; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn fibonacci_example2() { + let k = 4; + + let a = Fp::from(1); // F[0] + let b = Fp::from(1); // F[1] + let out = Fp::from(55); // F[9] + + let circuit = MyCircuit(PhantomData); + + let mut public_input = vec![a, b, out]; + + let prover = MockProver::run(k, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + + public_input[2] += Fp::one(); + let _prover = MockProver::run(k, &circuit, vec![public_input]).unwrap(); + // uncomment the following line and the assert will fail + // _prover.assert_satisfied(); + } + + #[cfg(feature = "dev-graph")] + #[test] + fn plot_fibo2() { + use plotters::prelude::*; + let root = BitMapBackend::new("fib-2-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Fib 2 Layout", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit::(PhantomData); + halo2_proofs::dev::CircuitLayout::default() + .render(4, &circuit, &root) + .unwrap(); + } +} diff --git a/src/fibonacci/example3.rs b/src/fibonacci/example3.rs new file mode 100644 index 0000000..a4ee8b6 --- /dev/null +++ b/src/fibonacci/example3.rs @@ -0,0 +1,133 @@ +use crate::is_zero::{IsZeroChip, IsZeroConfig}; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; + +#[derive(Debug, Clone)] +struct FunctionConfig { + selector: Selector, + a: Column, + b: Column, + c: Column, + a_equals_b: IsZeroConfig, + output: Column, +} + +#[derive(Debug, Clone)] +struct FunctionChip { + config: FunctionConfig, +} + +impl FunctionChip { + pub fn construct(config: FunctionConfig) -> Self { + Self { config } + } + + pub fn configure(meta: &mut ConstraintSystem) -> FunctionConfig { + let selector = meta.selector(); + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + let output = meta.advice_column(); + + let is_zero_advice_column = meta.advice_column(); + let a_equals_b = IsZeroChip::configure( + meta, + |meta| meta.query_selector(selector), + |meta| meta.query_advice(a, Rotation::cur()) - meta.query_advice(b, Rotation::cur()), + is_zero_advice_column, + ); + + meta.create_gate("f(a, b, c) = if a == b {c} else {a - b}", |meta| { + let s = meta.query_selector(selector); + let a = meta.query_advice(a, Rotation::cur()); + let b = meta.query_advice(b, Rotation::cur()); + let c = meta.query_advice(c, Rotation::cur()); + let output = meta.query_advice(output, Rotation::cur()); + vec![ + s.clone() * (a_equals_b.expr() * (output.clone() - c)), + s * (Expression::Constant(F::one()) - a_equals_b.expr()) * (output - (a - b)), + ] + }); + + FunctionConfig { + selector, + a, + b, + c, + a_equals_b, + output, + } + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + a: F, + b: F, + c: F, + ) -> Result, Error> { + let is_zero_chip = IsZeroChip::construct(self.config.a_equals_b.clone()); + + layouter.assign_region( + || "f(a, b, c) = if a == b {c} else {a - b}", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + region.assign_advice(|| "a", self.config.a, 0, || Value::known(a))?; + region.assign_advice(|| "b", self.config.b, 0, || Value::known(b))?; + region.assign_advice(|| "c", self.config.c, 0, || Value::known(c))?; + is_zero_chip.assign(&mut region, 0, Value::known(a - b))?; + + let output = if a == b { c } else { a - b }; + region.assign_advice(|| "output", self.config.output, 0, || Value::known(output)) + }, + ) + } +} + +#[derive(Default)] +struct FunctionCircuit { + a: F, + b: F, + c: F, +} + +impl Circuit for FunctionCircuit { + type Config = FunctionConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + FunctionChip::configure(meta) + } + + fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> { + let chip = FunctionChip::construct(config); + chip.assign(layouter, self.a, self.b, self.c)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn test_example3() { + let circuit = FunctionCircuit { + a: Fp::from(10), + b: Fp::from(12), + c: Fp::from(15), + }; + + let prover = MockProver::run(4, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + } +} diff --git a/src/fibonacci/example4.rs b/src/fibonacci/example4.rs new file mode 100644 index 0000000..ce6bfff --- /dev/null +++ b/src/fibonacci/example4.rs @@ -0,0 +1,274 @@ +use std::marker::PhantomData; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; + +#[derive(Debug, Clone)] +struct FibonacciConfig { + pub advice: [Column; 3], + pub s_add: Selector, + pub s_xor: Selector, + pub xor_table: [TableColumn; 3], + pub instance: Column, +} + +#[derive(Debug, Clone)] +struct FibonacciChip { + config: FibonacciConfig, + _marker: PhantomData, +} + +impl FibonacciChip { + pub fn construct(config: FibonacciConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure(meta: &mut ConstraintSystem) -> FibonacciConfig { + let col_a = meta.advice_column(); + let col_b = meta.advice_column(); + let col_c = meta.advice_column(); + let s_add = meta.selector(); + let s_xor = meta.complex_selector(); + let instance = meta.instance_column(); + + let xor_table = [ + meta.lookup_table_column(), + meta.lookup_table_column(), + meta.lookup_table_column(), + ]; + + meta.enable_equality(col_a); + meta.enable_equality(col_b); + meta.enable_equality(col_c); + meta.enable_equality(instance); + + meta.create_gate("add", |meta| { + // + // col_a | col_b | col_c | selector + // a b c s + // + let s = meta.query_selector(s_add); + let a = meta.query_advice(col_a, Rotation::cur()); + let b = meta.query_advice(col_b, Rotation::cur()); + let c = meta.query_advice(col_c, Rotation::cur()); + vec![s * (a + b - c)] + }); + + meta.lookup(|meta| { + let s = meta.query_selector(s_xor); + let lhs = meta.query_advice(col_a, Rotation::cur()); + let rhs = meta.query_advice(col_b, Rotation::cur()); + let out = meta.query_advice(col_c, Rotation::cur()); + vec![ + (s.clone() * lhs, xor_table[0]), + (s.clone() * rhs, xor_table[1]), + (s * out, xor_table[2]), + ] + }); + + FibonacciConfig { + advice: [col_a, col_b, col_c], + s_add, + s_xor, + xor_table, + instance, + } + } + + fn load_table( + &self, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "xor_table", + |mut table| { + let mut idx = 0; + for lhs in 0..32 { + for rhs in 0..32 { + table.assign_cell( + || "lhs", + self.config.xor_table[0], + idx, + || Value::known(F::from(lhs)), + )?; + table.assign_cell( + || "rhs", + self.config.xor_table[1], + idx, + || Value::known(F::from(rhs)), + )?; + table.assign_cell( + || "lhs ^ rhs", + self.config.xor_table[2], + idx, + || Value::known(F::from(lhs ^ rhs)), + )?; + idx += 1; + } + } + Ok(()) + } + ) + } + + #[allow(clippy::type_complexity)] + pub fn assign( + &self, + mut layouter: impl Layouter, + nrows: usize, + ) -> Result, Error> { + layouter.assign_region( + || "entire circuit", + |mut region| { + self.config.s_add.enable(&mut region, 0)?; + + // assign first row + let a_cell = region.assign_advice_from_instance( + || "1", + self.config.instance, + 0, + self.config.advice[0], + 0, + )?; + let mut b_cell = region.assign_advice_from_instance( + || "1", + self.config.instance, + 1, + self.config.advice[1], + 0, + )?; + let mut c_cell = region.assign_advice( + || "add", + self.config.advice[2], + 0, + || a_cell.value().copied() + b_cell.value(), + )?; + + // assign the rest of rows + for row in 1..nrows { + b_cell.copy_advice( + || "a", + &mut region, + self.config.advice[0], + row, + )?; + c_cell.copy_advice( + || "b", + &mut region, + self.config.advice[1], + row, + )?; + + let new_c_cell = if row % 2 == 0 { + self.config.s_add.enable(&mut region, row)?; + region.assign_advice( + || "advice", + self.config.advice[2], + row, + || b_cell.value().copied() + c_cell.value(), + )? + } else { + self.config.s_xor.enable(&mut region, row)?; + region.assign_advice( + || "advice", + self.config.advice[2], + row, + || b_cell.value().and_then(|a| c_cell.value().map(|b| { + let a_val = a.get_lower_32() as u64; + let b_val = b.get_lower_32() as u64; + F::from(a_val ^ b_val) + })), + )? + }; + + b_cell = c_cell; + c_cell = new_c_cell; + } + + Ok(c_cell) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } +} + +#[derive(Default)] +struct MyCircuit(PhantomData); + +impl Circuit for MyCircuit { + type Config = FibonacciConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + FibonacciChip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = FibonacciChip::construct(config); + chip.load_table(layouter.namespace(|| "lookup table"))?; + let out_cell = chip.assign(layouter.namespace(|| "entire table"), 8)?; + chip.expose_public(layouter.namespace(|| "out"), out_cell, 2)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::MyCircuit; + use std::marker::PhantomData; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn fibonacci_example4() { + let k = 11; + + let a = Fp::from(1); // F[0] + let b = Fp::from(1); // F[1] + let out = Fp::from(21); // F[9] + + let circuit = MyCircuit(PhantomData); + + let mut public_input = vec![a, b, out]; + + let prover = MockProver::run(k, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + + public_input[2] += Fp::one(); + let _prover = MockProver::run(k, &circuit, vec![public_input]).unwrap(); + // uncomment the following line and the assert will fail + // _prover.assert_satisfied(); + } + + #[cfg(feature = "dev-graph")] + #[test] + fn plot_fibonacci1() { + use plotters::prelude::*; + + let root = BitMapBackend::new("fib-1-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("Fib 1 Layout", ("sans-serif", 60)).unwrap(); + + let circuit = MyCircuit::(PhantomData); + halo2_proofs::dev::CircuitLayout::default() + .render(4, &circuit, &root) + .unwrap(); + } +} diff --git a/src/is_zero.rs b/src/is_zero.rs new file mode 100644 index 0000000..29280f6 --- /dev/null +++ b/src/is_zero.rs @@ -0,0 +1,65 @@ +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; + +#[derive(Clone, Debug)] +pub struct IsZeroConfig { + pub value_inv: Column, + pub is_zero_expr: Expression, +} + +impl IsZeroConfig { + pub fn expr(&self) -> Expression { + self.is_zero_expr.clone() + } +} + +pub struct IsZeroChip { + config: IsZeroConfig, +} + +impl IsZeroChip { + pub fn construct(config: IsZeroConfig) -> Self { + IsZeroChip { config } + } + + pub fn configure( + meta: &mut ConstraintSystem, + q_enable: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + value: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, + value_inv: Column, + ) -> IsZeroConfig { + let mut is_zero_expr = Expression::Constant(F::zero()); + + meta.create_gate("is_zero", |meta| { + // + // valid | value | value_inv | 1 - value * value_inv | value * (1 - value* value_inv) + // ------+-------+------------+------------------------+------------------------------- + // yes | x | 1/x | 0 | 0 + // no | x | 0 | 1 | x + // yes | 0 | 0 | 1 | 0 + // yes | 0 | y | 1 | 0 + // + let value = value(meta); + let q_enable = q_enable(meta); + let value_inv = meta.query_advice(value_inv, Rotation::cur()); + + is_zero_expr = Expression::Constant(F::one()) - value.clone() * value_inv; + vec![q_enable * value * is_zero_expr.clone()] + }); + + IsZeroConfig { + value_inv, + is_zero_expr, + } + } + + pub fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + value: Value, + ) -> Result<(), Error> { + let value_inv = value.map(|value| value.invert().unwrap_or(F::zero())); + region.assign_advice(|| "value inv", self.config.value_inv, offset, || value_inv)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fa08c47 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +mod fibonacci; +mod is_zero; +mod range_check; +mod mip; \ No newline at end of file diff --git a/src/mip.rs b/src/mip.rs new file mode 100644 index 0000000..7cf050e --- /dev/null +++ b/src/mip.rs @@ -0,0 +1,2 @@ +pub mod chips; +pub mod circuits; \ No newline at end of file diff --git a/src/mip/chips.rs b/src/mip/chips.rs new file mode 100644 index 0000000..542f6dd --- /dev/null +++ b/src/mip/chips.rs @@ -0,0 +1,3 @@ +pub mod merkle_v2; +pub mod hash_2; +pub mod my_mip_chip; \ No newline at end of file diff --git a/src/mip/chips/hash_2.rs b/src/mip/chips/hash_2.rs new file mode 100644 index 0000000..15f05d8 --- /dev/null +++ b/src/mip/chips/hash_2.rs @@ -0,0 +1,121 @@ +// MockHash: https://github.com/DrPeterVanNostrand/halo2-merkle/blob/main/src/main.rs +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; +use std::marker::PhantomData; + +#[derive(Debug, Clone)] +pub struct Hash2Config { + pub advice: [Column; 3], + pub instance: Column, + pub hash_selector: Selector, +} + +#[derive(Debug, Clone)] +pub struct Hash2Chip { + config: Hash2Config, + _marker: PhantomData, +} + +impl Hash2Chip { + pub fn construct(config: Hash2Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 3], + instance: Column, + ) -> Hash2Config { + let col_a = advice[0]; + let col_b = advice[1]; + let col_c = advice[2]; + let hash_selector = meta.selector(); + meta.enable_equality(col_a); + meta.enable_equality(col_b); + meta.enable_equality(col_c); + meta.enable_equality(instance); + + // Enforces our dummy hash function 2 * a = b. + meta.create_gate("hash", |meta| { + let s = meta.query_selector(hash_selector); + let a = meta.query_advice(col_a, Rotation::cur()); + let b = meta.query_advice(col_b, Rotation::cur()); + let c = meta.query_advice(col_c, Rotation::cur()); + vec![s * (a + b - c)] + }); + + Hash2Config { + advice: [col_a, col_b, col_c], + instance, + hash_selector, + } + } + + pub fn load_private( + &self, + mut layouter: impl Layouter, + input: Value, + ) -> Result, Error> { + layouter.assign_region( + || "load private", + |mut region| { + region.assign_advice(|| "private input", self.config.advice[0], 0, || input) + }, + ) + } + + pub fn load_constant( + &self, + mut layouter: impl Layouter, + constant: F, + ) -> Result, Error> { + layouter.assign_region( + || "load constant", + |mut region| { + region.assign_advice_from_constant( + || "constant value", + self.config.advice[0], + 0, + constant, + ) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + assigned_cell: AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(assigned_cell.cell(), self.config.instance, row) + } + + pub fn hash2( + &self, + mut layouter: impl Layouter, + input_a: AssignedCell, + input_b: AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "hash2", + |mut region| { + input_a.copy_advice(|| "input_a", &mut region, self.config.advice[0], 0)?; + input_b.copy_advice(|| "input_b", &mut region, self.config.advice[1], 0)?; + let output_cell = region.assign_advice( + || "output", + self.config.advice[2], + 0, + || { + input_a.value().map(|x| x.to_owned()) + + input_b.value().map(|x| x.to_owned()) + }, + )?; + self.config.hash_selector.enable(&mut region, 0)?; + Ok(output_cell) + }, + ) + } +} \ No newline at end of file diff --git a/src/mip/chips/merkle_v2.rs b/src/mip/chips/merkle_v2.rs new file mode 100644 index 0000000..53d17a1 --- /dev/null +++ b/src/mip/chips/merkle_v2.rs @@ -0,0 +1,165 @@ +use super::hash_2::{self, Hash2Chip, Hash2Config}; +use halo2_proofs::{ + arithmetic::{Field, FieldExt}, + circuit::*, + plonk::*, + poly::Rotation, +}; +use std::{marker::PhantomData, path}; + +#[derive(Debug, Clone)] +pub struct MerkleTreeV2Config { + pub advice: [Column; 3], + pub bool_selector: Selector, + pub swap_selector: Selector, + pub instance: Column, + pub hash2_config: Hash2Config, +} + +#[derive(Debug, Clone)] +pub struct MerkleTreeV2Chip { + config: MerkleTreeV2Config, + _marker: PhantomData, +} + +impl MerkleTreeV2Chip { + pub fn construct(config: MerkleTreeV2Config) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure( + meta: &mut ConstraintSystem, + advice: [Column; 3], + instance: Column, + ) -> MerkleTreeV2Config { + let col_a = advice[0]; + let col_b = advice[1]; + let col_c = advice[2]; + let bool_selector = meta.selector(); + let swap_selector = meta.selector(); + meta.enable_equality(col_a); + meta.enable_equality(col_b); + meta.enable_equality(col_c); + meta.enable_equality(instance); + + // Enforces that c is either a 0 or 1. + meta.create_gate("bool", |meta| { + let s = meta.query_selector(bool_selector); + let c = meta.query_advice(col_c, Rotation::cur()); + vec![s * c.clone() * (Expression::Constant(F::from(1)) - c.clone())] + }); + + // Enforces that if the swap bit is on, l=b and r=a. Otherwise, l=a and r=b. + meta.create_gate("swap", |meta| { + let s = meta.query_selector(swap_selector); + let a = meta.query_advice(col_a, Rotation::cur()); + let b = meta.query_advice(col_b, Rotation::cur()); + let c = meta.query_advice(col_c, Rotation::cur()); + let l = meta.query_advice(col_a, Rotation::next()); + let r = meta.query_advice(col_b, Rotation::next()); + vec![ + s * (c * Expression::Constant(F::from(2)) * (b.clone() - a.clone()) + - (l - a.clone()) + - (b.clone() - r)), + ] + }); + + MerkleTreeV2Config { + advice: [col_a, col_b, col_c], + bool_selector: bool_selector, + swap_selector: swap_selector, + instance: instance, + hash2_config: Hash2Chip::configure(meta, [col_a, col_b, col_c], instance), + } + } + + pub fn load_private( + &self, + mut layouter: impl Layouter, + input: Value, + ) -> Result, Error> { + layouter.assign_region( + || "load private", + |mut region| { + region.assign_advice(|| "private input", self.config.advice[0], 0, || input) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: &AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } + + pub fn merkle_prove( + &self, + mut layouter: impl Layouter, + leaf: &AssignedCell, + elements: &Vec>, + indices: &Vec>, + ) -> Result, Error> { + + let mut leaf_or_digest = self.merkle_prove_layer( + layouter.namespace(|| "merkle_prove_layer_0"), + leaf, + elements[0], + indices[0], + )?; + + for i in 1..elements.len() { + leaf_or_digest = self.merkle_prove_layer( + layouter.namespace(|| format!("merkle_prove_layer_{}", i)), + &leaf_or_digest, + elements[i], + indices[i], + )?; + } + Ok(leaf_or_digest) + } + + fn merkle_prove_layer( + &self, + mut layouter: impl Layouter, + digest: &AssignedCell, + element: Value, + index: Value, + ) -> Result, Error> { + + let (left, right) = layouter.assign_region( + || "merkle_prove_leaf", + |mut region| { + // Row 0 + digest.copy_advice(|| "digest", &mut region, self.config.advice[0], 0)?; + region.assign_advice(|| "element", self.config.advice[1], 0, || element)?; + region.assign_advice(|| "index", self.config.advice[2], 0, || index)?; + + self.config.bool_selector.enable(&mut region, 0)?; + self.config.swap_selector.enable(&mut region, 0)?; + + // Row 1 + let digest_value = digest.value().map(|x| x.to_owned()); + + let (mut l, mut r) = (digest_value, element); + index.map(|x| { + (l, r) = if x == F::zero() { (l, r) } else { (r, l) }; + }); + + let left = region.assign_advice(|| "left", self.config.advice[0], 1, || l)?; + let right = region.assign_advice(|| "right", self.config.advice[1], 1, || r)?; + + Ok((left, right)) + }, + )?; + + let hash2_chip = Hash2Chip::construct(self.config.hash2_config.clone()); + let digest = hash2_chip.hash2(layouter.namespace(|| "hash2"), left, right)?; + Ok(digest) + } +} \ No newline at end of file diff --git a/src/mip/chips/my_mip_chip.rs b/src/mip/chips/my_mip_chip.rs new file mode 100644 index 0000000..845f7fa --- /dev/null +++ b/src/mip/chips/my_mip_chip.rs @@ -0,0 +1,183 @@ +use std::marker::PhantomData; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; + +#[derive(Debug, Clone)] +pub struct MyMerkleConfig { + pub leaf: Column, + pub root: Column, + pub proof: Column, + pub hashed: Column, + pub instance: Column, + pub selector: Selector, +} + +#[derive(Debug, Clone)] +pub struct MyMIPChip { + config: MyMerkleConfig, + _marker: PhantomData, +} + +impl MyMIPChip { + pub fn construct(config: MyMerkleConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure(meta: &mut ConstraintSystem) -> MyMerkleConfig { + // let col_a = meta.advice_column(); + // let col_b = meta.advice_column(); + // let col_c = meta.advice_column(); + // let selector = meta.selector(); + // let instance = meta.instance_column(); + + // meta.enable_equality(col_a); + // meta.enable_equality(col_b); + // meta.enable_equality(col_c); + // meta.enable_equality(instance); + + // meta.create_gate("add", |meta| { + // // + // // col_a | col_b | col_c | selector + // // a b c s + // // + // let s = meta.query_selector(selector); + // let a = meta.query_advice(col_a, Rotation::cur()); + // let b = meta.query_advice(col_b, Rotation::cur()); + // let c = meta.query_advice(col_c, Rotation::cur()); + // vec![s * (a + b - c)] + // }); + + + // advice - private inputs + // instance - public inputs + // fixed - constants + // selector - control gates (boolean) + + // + // leaf | proof | hashed | root | selector + // l p H true + // p H true + // p H root true + // + + // + // leaf | proof | hashed | root | selector + // 1 1 2 true + // 2 1 3 true + // 3 1 4 4 true + // + + let leaf = meta.advice_column(); // public + let root = meta.advice_column(); // public + let proof = meta.advice_column(); // public + let hashed = meta.advice_column(); // public + + let instance = meta.instance_column(); // private + let selector = meta.selector(); + + meta.enable_equality(leaf); + meta.enable_equality(root); + meta.enable_equality(proof); + meta.enable_equality(hashed); + + meta.create_gate("hash", |meta| { + let s = meta.query_selector(selector); + let leaf = meta.query_advice(leaf, Rotation::cur()); + let proof = meta.query_advice(proof, Rotation::cur()); + let hashed = meta.query_advice(hashed, Rotation::cur()); + vec![s * (leaf + proof - hashed)] + }); + + MyMerkleConfig { + leaf, + root, + proof, + hashed, + instance, + selector + } + } + + #[allow(clippy::type_complexity)] + pub fn assign_first_row( + &self, + mut layouter: impl Layouter, + ) -> Result<(AssignedCell, AssignedCell, AssignedCell), Error> { + + layouter.assign_region( + || "first row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + let leaf = region.assign_advice_from_instance( + || "leaf", + self.config.instance, + 0, + self.config.leaf, + 0)?; + + let proof = region.assign_advice_from_instance( + || "proof", + self.config.instance, + 0, + self.config.proof, + 0)?; + + let hashed = region.assign_advice( + || "hashed", + self.config.hashed, + 0, + || leaf.value().copied() + proof.value())?; + + Ok((leaf, proof, hashed)) + }, + ) + } + + pub fn assign_row( + &self, + mut layouter: impl Layouter, + prev_hashed: &AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "next row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + prev_hashed.copy_advice( + || "leaf", + &mut region, + self.config.leaf, + 0, + )?; + + let proof = region.assign_advice_from_instance( + || "proof", + self.config.instance, + 0, + self.config.proof, + 0)?; + + let hashed = region.assign_advice( + || "hashed", + self.config.hashed, + 0, + || prev_hashed.value().copied() + proof.value() + )?; + + Ok(hashed) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: &AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } +} \ No newline at end of file diff --git a/src/mip/circuits.rs b/src/mip/circuits.rs new file mode 100644 index 0000000..4dd1c5d --- /dev/null +++ b/src/mip/circuits.rs @@ -0,0 +1,2 @@ +pub mod merkle_v2; +pub mod my_mip; \ No newline at end of file diff --git a/src/mip/circuits/NEW-merkle-inclusion.rs b/src/mip/circuits/NEW-merkle-inclusion.rs new file mode 100644 index 0000000..2bfdb72 --- /dev/null +++ b/src/mip/circuits/NEW-merkle-inclusion.rs @@ -0,0 +1,217 @@ +use std::marker::PhantomData; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; + +#[derive(Debug, Clone)] +struct MerkleConfig { + pub col_a: Column, + pub col_b: Column, + pub col_c: Column, + pub selector: Selector, + pub instance: Column, +} + +#[derive(Debug, Clone)] +struct MerkleChip { + config: MerkleConfig, + _marker: PhantomData, +} + +impl MerkleChip { + pub fn construct(config: MerkleConfig) -> Self { + Self { + config, + _marker: PhantomData, + } + } + + pub fn configure(meta: &mut ConstraintSystem) -> MerkleConfig { + + // advice - private inputs + // instance - public inputs + // fixed - constants + // selector - control gates (boolean) + + // + // leaf | proof | hashed | root | selector + // l p H true + // p H true + // p H root true + // + + let leaf = meta.instance_column(); // public + let root = meta.instance_column(); // public + + let proof = meta.advice_column(); // private + let hashed = meta.advice_column(); // private + + let selector = meta.selector(); + + // meta.enable_equality(col_a); + // meta.enable_equality(col_b); + // meta.enable_equality(col_c); + // meta.enable_equality(instance); + + meta.create_gate("hash", |meta| { + let sel = meta.query_selector(selector); + + let leaf = meta.query_advice(leaf, Rotation::cur()); + let proof = meta.query_advice(proof, Rotation::cur()); + + + // HASH SHOULD HAPPEN HERE ? + vec![s * (a + b - c)] + }); + + MerkleConfig { + col_a, + col_b, + selector, + instance, + } + } + + #[allow(clippy::type_complexity)] + pub fn assign_first_row( + &self, + mut layouter: impl Layouter, + ) -> Result<(AssignedCell, AssignedCell, AssignedCell), Error> { + layouter.assign_region( + || "first row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + let a_cell = region.assign_advice_from_instance( + || "f(0)", + self.config.instance, + 0, + self.config.col_a, + 0)?; + + let b_cell = region.assign_advice_from_instance( + || "f(1)", + self.config.instance, + 1, + self.config.col_b, + 0)?; + + let c_cell = region.assign_advice( + || "a + b", + self.config.col_c, + 0, + || a_cell.value().copied() + b_cell.value(), + )?; + + Ok((a_cell, b_cell, c_cell)) + }, + ) + } + + pub fn assign_row( + &self, + mut layouter: impl Layouter, + prev_b: &AssignedCell, + prev_c: &AssignedCell, + ) -> Result, Error> { + layouter.assign_region( + || "next row", + |mut region| { + self.config.selector.enable(&mut region, 0)?; + + // Copy the value from b & c in previous row to a & b in current row + prev_b.copy_advice( + || "a", + &mut region, + self.config.col_a, + 0, + )?; + prev_c.copy_advice( + || "b", + &mut region, + self.config.col_b, + 0, + )?; + + let c_cell = region.assign_advice( + || "c", + self.config.col_c, + 0, + || prev_b.value().copied() + prev_c.value(), + )?; + + Ok(c_cell) + }, + ) + } + + pub fn expose_public( + &self, + mut layouter: impl Layouter, + cell: &AssignedCell, + row: usize, + ) -> Result<(), Error> { + layouter.constrain_instance(cell.cell(), self.config.instance, row) + } +} + +#[derive(Default)] +struct MyCircuit(PhantomData); + +impl Circuit for MyCircuit { + type Config = MerkleConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MerkleChip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = MerkleChip::construct(config); + + for _i in 1..3 { + let c_cell = chip.assign_row(layouter.namespace(|| "hash"), &prev_b, &prev_c)?; + prev_b = prev_c; + prev_c = c_cell; + } + + chip.expose_public(layouter.namespace(|| "out"), &prev_c, 2)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use super::MyCircuit; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn mip_example() { + let k = 4; + + let a = Fp::from(1); // F[0] + let b = Fp::from(1); // F[1] + let out = Fp::from(55); // F[9] + + let circuit = MyCircuit(PhantomData); + + let mut public_input = vec![a, b, out]; + + let prover = MockProver::run(k, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + + public_input[2] += Fp::one(); + let _prover = MockProver::run(k, &circuit, vec![public_input]).unwrap(); + // uncomment the following line and the assert will fail + // _prover.assert_satisfied(); + } +} diff --git a/src/mip/circuits/merkle_tree.rs b/src/mip/circuits/merkle_tree.rs new file mode 100644 index 0000000..a8dc52d --- /dev/null +++ b/src/mip/circuits/merkle_tree.rs @@ -0,0 +1,674 @@ +use crate::poseidon::FieldHasherGadget; +use ark_ec::models::TEModelParameters; +use ark_ff::PrimeField; +use ark_std::marker::PhantomData; +use arkworks_native_gadgets::merkle_tree::Path; +use plonk_core::{constraint_system::StandardComposer, error::Error, prelude::Variable}; + +#[derive(Clone)] +pub struct PathGadget< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, +> { + path: [(Variable, Variable); N], + _field: PhantomData, + _te: PhantomData

, + _hg: PhantomData, +} + +impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > PathGadget +{ + pub fn from_native( + composer: &mut StandardComposer, + native: Path, + ) -> Self { + // Initialize the array + let mut path_vars = [(composer.zero_var(), composer.zero_var()); N]; + + for i in 0..N { + path_vars[i] = ( + composer.add_input(native.path[i].0), + composer.add_input(native.path[i].1), + ); + } + + PathGadget { + path: path_vars, + _field: PhantomData, + _te: PhantomData, + _hg: PhantomData, + } + } + + pub fn check_membership( + &self, + composer: &mut StandardComposer, + root_hash: &Variable, + leaf: &Variable, + hasher: &HG, + ) -> Result { + let computed_root = self.calculate_root(composer, leaf, hasher)?; + + Ok(composer.is_eq_with_output(computed_root, *root_hash)) + } + + pub fn calculate_root( + &self, + composer: &mut StandardComposer, + leaf: &Variable, + hash_gadget: &HG, + ) -> Result { + // Check levels between leaf level and root + let mut previous_hash = *leaf; + + for (left_hash, right_hash) in self.path.iter() { + // Check if previous_hash matches the correct current hash + let previous_is_left = composer.is_eq_with_output(previous_hash, *left_hash); + let left_or_right = + composer.conditional_select(previous_is_left, *left_hash, *right_hash); + composer.assert_equal(previous_hash, left_or_right); + + // Update previous_hash + previous_hash = hash_gadget.hash_two(composer, left_hash, right_hash)?; + } + + Ok(previous_hash) + } + + pub fn get_index( + &self, + composer: &mut StandardComposer, + root_hash: &Variable, + leaf: &Variable, + hasher: &HG, + ) -> Result { + // First check that leaf is on path + // let is_on_path = self.check_membership(composer, root_hash, leaf, hasher)?; + let one = composer.add_input(F::one()); + // composer.assert_equal(is_on_path, one); + + let mut index = composer.add_input(F::zero()); + let mut two_power = composer.add_input(F::one()); + let mut right_value: Variable; + + // Check the levels between leaf level and root + let mut previous_hash = *leaf; + + for (left_hash, right_hash) in self.path.iter() { + // Check if previous hash is a left node + let previous_is_left = composer.is_eq_with_output(previous_hash, *left_hash); + right_value = composer.arithmetic_gate(|gate| { + gate.witness(index, two_power, None).add(F::one(), F::one()) + }); + + // Assign index based on whether prev hash is left or right + index = composer.conditional_select(previous_is_left, index, right_value); + two_power = composer + .arithmetic_gate(|gate| gate.witness(two_power, one, None).mul(F::one().double())); + + previous_hash = hasher.hash_two(composer, left_hash, right_hash)?; + } + //This line confirms that the path is consistent with the given merkle root + composer.assert_equal(previous_hash, *root_hash); + + Ok(index) + } +} + +#[cfg(test)] +mod test { + use super::PathGadget; + use crate::poseidon::{FieldHasherGadget, PoseidonGadget}; + use ark_bn254::{Bn254, Fr as Bn254Fr}; + use ark_ec::TEModelParameters; + use ark_ed_on_bn254::{EdwardsParameters as JubjubParameters, Fq}; + use ark_ff::PrimeField; + use ark_poly::polynomial::univariate::DensePolynomial; + use ark_poly_commit::{kzg10::UniversalParams, sonic_pc::SonicKZG10, PolynomialCommitment}; + use ark_std::{test_rng, UniformRand}; + use arkworks_native_gadgets::{ + merkle_tree::SparseMerkleTree, + poseidon::{sbox::PoseidonSbox, Poseidon, PoseidonParameters}, + }; + use arkworks_utils::{ + bytes_matrix_to_f, bytes_vec_to_f, poseidon_params::setup_poseidon_params, Curve, + }; + use plonk_core::prelude::*; + + type PoseidonBn254 = Poseidon; + + pub fn setup_params(curve: Curve, exp: i8, width: u8) -> PoseidonParameters { + let pos_data = setup_poseidon_params(curve, exp, width).unwrap(); + + let mds_f = bytes_matrix_to_f(&pos_data.mds); + let rounds_f = bytes_vec_to_f(&pos_data.rounds); + + let pos = PoseidonParameters { + mds_matrix: mds_f, + round_keys: rounds_f, + full_rounds: pos_data.full_rounds, + partial_rounds: pos_data.partial_rounds, + sbox: PoseidonSbox(pos_data.exp), + width: pos_data.width, + }; + + pos + } + + struct TestCircuit< + 'a, + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > { + leaves: &'a [F], + empty_leaf: &'a [u8], + hasher: &'a HG::Native, + } + + impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > Circuit for TestCircuit<'_, F, P, HG, N> + { + const CIRCUIT_ID: [u8; 32] = [0xfe; 32]; + + fn gadget(&mut self, composer: &mut StandardComposer) -> Result<(), Error> { + let hasher_gadget = HG::from_native(composer, self.hasher.clone()); + + let smt = SparseMerkleTree::::new_sequential( + self.leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let path = smt.generate_membership_proof(0); + let root = path.calculate_root(&self.leaves[0], &self.hasher).unwrap(); + + let path_gadget = PathGadget::::from_native(composer, path); + let root_var = composer.add_input(root); + let leaf_var = composer.add_input(self.leaves[0]); + + let res = + path_gadget.check_membership(composer, &root_var, &leaf_var, &hasher_gadget)?; + let one = composer.add_input(F::one()); + composer.assert_equal(res, one); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + 1 << 13 + } + } + + #[test] + fn should_verify_path() { + let rng = &mut test_rng(); + let curve = Curve::Bn254; + + let params = setup_params(curve, 5, 3); + let poseidon = PoseidonBn254 { params }; + + let leaves = [Fq::rand(rng), Fq::rand(rng), Fq::rand(rng)]; + let empty_leaf = [0u8; 32]; + + // Create the test circuit + let mut test_circuit = TestCircuit:: { + leaves: &leaves, + empty_leaf: &empty_leaf, + hasher: &poseidon, + }; + + // Usual prover/verifier flow: + let u_params: UniversalParams = + SonicKZG10::>::setup(1 << 14, None, rng).unwrap(); + + let (pk, vd) = test_circuit + .compile::>>(&u_params) + .unwrap(); + + // PROVER + let (proof, pi) = test_circuit.gen_proof(&u_params, pk, b"SMT Test").unwrap(); + + // VERIFIER + let (vk, ..): ( + VerifierKey>>, + Vec, + ) = vd; + let verifier_data = VerifierData::new(vk, pi); + circuit::verify_proof::<_, JubjubParameters, _>( + &u_params, + verifier_data.key, + &proof, + &verifier_data.pi, + b"SMT Test", + ) + .unwrap(); + } + + struct IndexTestCircuit< + 'a, + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > { + index: u64, + leaves: &'a [F], + empty_leaf: &'a [u8], + hasher: &'a HG::Native, + } + + impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > Circuit for IndexTestCircuit<'_, F, P, HG, N> + { + const CIRCUIT_ID: [u8; 32] = [0xfd; 32]; + + fn gadget(&mut self, composer: &mut StandardComposer) -> Result<(), Error> { + let hasher_gadget = HG::from_native(composer, self.hasher.clone()); + + let smt = SparseMerkleTree::::new_sequential( + self.leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let root = smt.root(); + let path = smt.generate_membership_proof(self.index); + + let path_gadget = PathGadget::::from_native(composer, path); + let root_var = composer.add_input(root); + let leaf_var = composer.add_input(self.leaves[self.index as usize]); + + let res = path_gadget.get_index(composer, &root_var, &leaf_var, &hasher_gadget)?; + let index_var = composer.add_input(F::from(self.index)); + composer.assert_equal(res, index_var); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + 1 << 14 + } + } + + #[test] + fn should_verify_index() { + let rng = &mut test_rng(); + let curve = Curve::Bn254; + + let params = setup_params(curve, 5, 3); + let poseidon = PoseidonBn254 { params }; + + let leaves = [Fq::rand(rng), Fq::rand(rng), Fq::rand(rng)]; + let empty_leaf = [0u8; 32]; + let index = 2u64; + + let mut test_circuit = + IndexTestCircuit::<'_, Bn254Fr, JubjubParameters, PoseidonGadget, 3usize> { + index, + leaves: &leaves, + empty_leaf: &empty_leaf, + hasher: &poseidon, + }; + + // Usual prover/verifier flow: + let u_params: UniversalParams = + SonicKZG10::>::setup(1 << 15, None, rng).unwrap(); + + let (pk, vd) = test_circuit + .compile::>>(&u_params) + .unwrap(); + + // PROVER + let (proof, pi) = test_circuit + .gen_proof(&u_params, pk, b"SMTIndex Test") + .unwrap(); + + // VERIFIER + let (vk, ..): ( + VerifierKey>>, + Vec, + ) = vd; + let verifier_data = VerifierData::new(vk, pi); + circuit::verify_proof::<_, JubjubParameters, _>( + &u_params, + verifier_data.key, + &proof, + &verifier_data.pi, + b"SMTIndex Test", + ) + .unwrap(); + } + + // Something puzzling is that this BadIndexTestCircuit needs to be + // 4 times larger than the valid IndexTestCircuit above. Why would + // the invalidity of a circuit lead to higher degree polynomials? + struct BadIndexTestCircuit< + 'a, + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > { + index: u64, + leaves: &'a [F], + empty_leaf: &'a [u8], + hasher: &'a HG::Native, + } + + impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > Circuit for BadIndexTestCircuit<'_, F, P, HG, N> + { + const CIRCUIT_ID: [u8; 32] = [0xfd; 32]; + + fn gadget(&mut self, composer: &mut StandardComposer) -> Result<(), Error> { + let hasher_gadget = HG::from_native(composer, self.hasher.clone()); + + let smt = SparseMerkleTree::::new_sequential( + self.leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let path = smt.generate_membership_proof(self.index); + + let path_gadget = PathGadget::::from_native(composer, path); + + // Now create an invalid root to show that get_index detects this: + let bad_leaves = &self.leaves[0..1]; + let bad_smt = SparseMerkleTree::::new_sequential( + bad_leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let bad_root = bad_smt.root(); + let bad_root_var = composer.add_input(bad_root); + let leaf_var = composer.add_input(self.leaves[self.index as usize]); + + let res = path_gadget.get_index(composer, &bad_root_var, &leaf_var, &hasher_gadget)?; + let index_var = composer.add_input(F::from(self.index)); + composer.assert_equal(res, index_var); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + 1 << 16 + } + } + + #[test] + fn get_index_should_fail() { + let rng = &mut test_rng(); + let curve = Curve::Bn254; + + let params = setup_params(curve, 5, 3); + let poseidon = PoseidonBn254 { params }; + + let leaves = [Fq::rand(rng), Fq::rand(rng), Fq::rand(rng)]; + let empty_leaf = [0u8; 32]; + let index = 2u64; + + let mut test_circuit = + BadIndexTestCircuit::<'_, Bn254Fr, JubjubParameters, PoseidonGadget, 3usize> { + index, + leaves: &leaves, + empty_leaf: &empty_leaf, + hasher: &poseidon, + }; + + // Usual prover/verifier flow: + let u_params: UniversalParams = + SonicKZG10::>::setup(1 << 17, None, rng).unwrap(); + + let (pk, vd) = test_circuit + .compile::>>(&u_params) + .unwrap(); + + // PROVER + let (proof, pi) = test_circuit + .gen_proof(&u_params, pk, b"SMTIndex Test") + .unwrap(); + + // VERIFIER + let (vk, ..): ( + VerifierKey>>, + Vec, + ) = vd; + let verifier_data = VerifierData::new(vk, pi); + let res = circuit::verify_proof::<_, JubjubParameters, _>( + &u_params, + verifier_data.key, + &proof, + &verifier_data.pi, + b"SMTIndex Test", + ) + .unwrap_err(); + match res { + Error::ProofVerificationError => (), + err => panic!("Unexpected error: {:?}", err), + }; + } + + // Membership proof should fail due to invalid leaf input + struct BadLeafTestCircuit< + 'a, + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > { + leaves: &'a [F], + empty_leaf: &'a [u8], + hasher: &'a HG::Native, + } + + impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > Circuit for BadLeafTestCircuit<'_, F, P, HG, N> + { + const CIRCUIT_ID: [u8; 32] = [0xfe; 32]; + + fn gadget(&mut self, composer: &mut StandardComposer) -> Result<(), Error> { + let hasher_gadget = HG::from_native(composer, self.hasher.clone()); + + let smt = SparseMerkleTree::::new_sequential( + self.leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let path = smt.generate_membership_proof(0); + let root = path.calculate_root(&self.leaves[0], &self.hasher).unwrap(); + + let path_gadget = PathGadget::::from_native(composer, path); + let root_var = composer.add_input(root); + let leaf_var = composer.zero_var(); + + let res = + path_gadget.check_membership(composer, &root_var, &leaf_var, &hasher_gadget)?; + let one = composer.add_input(F::one()); + composer.assert_equal(res, one); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + 1 << 16 + } + } + + #[test] + fn bad_leaf_membership() { + let rng = &mut test_rng(); + let curve = Curve::Bn254; + + let params = setup_params(curve, 5, 3); + let poseidon = PoseidonBn254 { params }; + + let leaves = [Fq::rand(rng), Fq::rand(rng), Fq::rand(rng)]; + let empty_leaf = [0u8; 32]; + + // Create the test circuit + let mut test_circuit = + BadLeafTestCircuit:: { + leaves: &leaves, + empty_leaf: &empty_leaf, + hasher: &poseidon, + }; + + // Usual prover/verifier flow: + let u_params: UniversalParams = + SonicKZG10::>::setup(1 << 17, None, rng).unwrap(); + + let (pk, vd) = test_circuit + .compile::>>(&u_params) + .unwrap(); + + // PROVER + let (proof, pi) = test_circuit.gen_proof(&u_params, pk, b"SMT Test").unwrap(); + + // VERIFIER + let (vk, ..): ( + VerifierKey>>, + Vec, + ) = vd; + let verifier_data = VerifierData::new(vk, pi); + let res = circuit::verify_proof::<_, JubjubParameters, _>( + &u_params, + verifier_data.key, + &proof, + &verifier_data.pi, + b"SMTIndex Test", + ) + .unwrap_err(); + match res { + Error::ProofVerificationError => (), + err => panic!("Unexpected error: {:?}", err), + }; + } + + // Membership proof should fail due to invalid leaf input + struct BadRootTestCircuit< + 'a, + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > { + leaves: &'a [F], + empty_leaf: &'a [u8], + hasher: &'a HG::Native, + } + + impl< + F: PrimeField, + P: TEModelParameters, + HG: FieldHasherGadget, + const N: usize, + > Circuit for BadRootTestCircuit<'_, F, P, HG, N> + { + const CIRCUIT_ID: [u8; 32] = [0xfe; 32]; + + fn gadget(&mut self, composer: &mut StandardComposer) -> Result<(), Error> { + let hasher_gadget = HG::from_native(composer, self.hasher.clone()); + + let smt = SparseMerkleTree::::new_sequential( + self.leaves, + &self.hasher, + self.empty_leaf, + ) + .unwrap(); + let path = smt.generate_membership_proof(0); + + let path_gadget = PathGadget::::from_native(composer, path); + let root_var = composer.zero_var(); + let leaf_var = composer.add_input(self.leaves[0]); + + let res = + path_gadget.check_membership(composer, &root_var, &leaf_var, &hasher_gadget)?; + let one = composer.add_input(F::one()); + composer.assert_equal(res, one); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + 1 << 16 + } + } + + #[test] + fn bad_root_membership() { + let rng = &mut test_rng(); + let curve = Curve::Bn254; + + let params = setup_params(curve, 5, 3); + let poseidon = PoseidonBn254 { params }; + + let leaves = [Fq::rand(rng), Fq::rand(rng), Fq::rand(rng)]; + let empty_leaf = [0u8; 32]; + + // Create the test circuit + let mut test_circuit = + BadRootTestCircuit:: { + leaves: &leaves, + empty_leaf: &empty_leaf, + hasher: &poseidon, + }; + + // Usual prover/verifier flow: + let u_params: UniversalParams = + SonicKZG10::>::setup(1 << 17, None, rng).unwrap(); + + let (pk, vd) = test_circuit + .compile::>>(&u_params) + .unwrap(); + + // PROVER + let (proof, pi) = test_circuit.gen_proof(&u_params, pk, b"SMT Test").unwrap(); + + // VERIFIER + let (vk, ..): ( + VerifierKey>>, + Vec, + ) = vd; + let verifier_data = VerifierData::new(vk, pi); + let res = circuit::verify_proof::<_, JubjubParameters, _>( + &u_params, + verifier_data.key, + &proof, + &verifier_data.pi, + b"SMTIndex Test", + ) + .unwrap_err(); + match res { + Error::ProofVerificationError => (), + err => panic!("Unexpected error: {:?}", err), + }; + } +} diff --git a/src/mip/circuits/merkle_v2.rs b/src/mip/circuits/merkle_v2.rs new file mode 100644 index 0000000..46fcb5c --- /dev/null +++ b/src/mip/circuits/merkle_v2.rs @@ -0,0 +1,98 @@ +use super::super::chips::merkle_v2::{MerkleTreeV2Chip, MerkleTreeV2Config}; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; + +#[derive(Default)] +struct MerkleTreeV2Circuit { + pub leaf: Value, + pub elements: Vec>, + pub indices: Vec>, +} + +impl Circuit for MerkleTreeV2Circuit { + type Config = MerkleTreeV2Config; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let col_a = meta.advice_column(); + let col_b = meta.advice_column(); + let col_c = meta.advice_column(); + let instance = meta.instance_column(); + MerkleTreeV2Chip::configure(meta, [col_a, col_b, col_c], instance) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + + let chip = MerkleTreeV2Chip::construct(config); + + let leaf_cell = chip.load_private(layouter.namespace(|| "load leaf"), self.leaf)?; + + chip.expose_public(layouter.namespace(|| "public leaf"), &leaf_cell, 0)?; + + let digest = chip.merkle_prove( + layouter.namespace(|| "merkle_prove"), + &leaf_cell, + &self.elements, + &self.indices, + )?; + + chip.expose_public(layouter.namespace(|| "public root"), &digest, 1)?; + + + + Ok(()) + } +} + +mod tests { + use super::MerkleTreeV2Circuit; + use halo2_proofs::{circuit::Value, dev::MockProver, pasta::Fp}; + + #[test] + #[cfg(feature = "dev-graph")] + fn test_merkle_v2() { + let leaf = 1u64; + let elements = vec![1, 1, 1, 1]; + let indices = vec![0, 0, 0, 0]; + + let root: u64 = leaf + elements.iter().sum::(); + + let leaf_fp = Value::known(Fp::from(leaf)); + let elements_fp: Vec> = elements + .iter() + .map(|x| Value::known(Fp::from(x.to_owned()))) + .collect(); + let indices_fp: Vec> = indices + .iter() + .map(|x| Value::known(Fp::from(x.to_owned()))) + .collect(); + + let circuit = MerkleTreeV2Circuit { + leaf: leaf_fp, + elements: elements_fp, + indices: indices_fp, + }; + + let public_input = vec![Fp::from(leaf), Fp::from(root)]; + let prover = MockProver::run(5, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + + + // PLOT + use plotters::prelude::*; + let root = BitMapBackend::new("mip-v2-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root.titled("MIP v2 Layout", ("sans-serif", 60)).unwrap(); + + halo2_proofs::dev::CircuitLayout::default() + .render(4, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file diff --git a/src/mip/circuits/my_mip.rs b/src/mip/circuits/my_mip.rs new file mode 100644 index 0000000..449c7d4 --- /dev/null +++ b/src/mip/circuits/my_mip.rs @@ -0,0 +1,90 @@ +use std::{marker::PhantomData, println}; +use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; +use super::super::chips::my_mip_chip::{MyMerkleConfig, MyMIPChip}; + +#[derive(Default)] +#[derive(Debug)] +struct MyMIPCircuit { + pub leaf: F, + pub proof: Vec +} + +impl Circuit for MyMIPCircuit { + type Config = MyMerkleConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + MyMIPChip::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let chip = MyMIPChip::construct(config); + + let ( + mut prev_leaf, + mut prev_proof, + mut prev_hashed + ) = chip.assign_first_row(layouter.namespace(|| "first row"))?; + + for _i in 2..4 { + let cur_hashed = + chip.assign_row( + layouter.namespace(|| "next row"), + &prev_hashed + )?; + + prev_hashed = cur_hashed; + } + + chip.expose_public(layouter.namespace(|| "out"), &prev_hashed, 2)?; + + Ok(()) + } +} + +#[allow(dead_code)] +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use super::MyMIPCircuit; + use halo2_proofs::{dev::MockProver, pasta::Fp}; + + #[test] + fn mymip_1() { + let k = 3; + + let leaf = 1u64; + let proof = vec![1u64, 1u64, 1u64, 1u64, 1u64]; + + let leaf_fp = Fp::from(leaf); + let proof_fp: Vec = proof + .iter() + .map(|x| Fp::from(x.to_owned())) + .collect(); + let root_fp = Fp::from(4); + + let circuit = MyMIPCircuit { + leaf: leaf_fp, + proof: proof_fp + }; + + let public_input = vec![leaf_fp, root_fp]; + + let prover = MockProver::run(k, &circuit, vec![public_input.clone()]).unwrap(); + prover.assert_satisfied(); + + // public_input[2] += Fp::one(); + // let _prover = MockProver::run(k, &circuit, vec![public_input]).unwrap(); + // uncomment the following line and the assert will fail + // _prover.assert_satisfied(); + } +} diff --git a/src/mip/mip.rs b/src/mip/mip.rs new file mode 100644 index 0000000..7cf050e --- /dev/null +++ b/src/mip/mip.rs @@ -0,0 +1,2 @@ +pub mod chips; +pub mod circuits; \ No newline at end of file diff --git a/src/range_check.rs b/src/range_check.rs new file mode 100644 index 0000000..90fcdc9 --- /dev/null +++ b/src/range_check.rs @@ -0,0 +1,3 @@ +mod example1; +mod example2; +mod example3_broken; diff --git a/src/range_check/example1.rs b/src/range_check/example1.rs new file mode 100644 index 0000000..b3c490a --- /dev/null +++ b/src/range_check/example1.rs @@ -0,0 +1,174 @@ +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{AssignedCell, Layouter, Value}, + plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; + +/// This helper checks that the value witnessed in a given cell is within a given range. +/// +/// value | q_range_check +/// ------------------------------ +/// v | 1 +/// + +#[derive(Debug, Clone)] +/// A range-constrained value in the circuit produced by the RangeCheckConfig. +struct RangeConstrained(AssignedCell, F>); + +#[derive(Debug, Clone)] +struct RangeCheckConfig { + value: Column, + q_range_check: Selector, + _marker: PhantomData, +} + +impl RangeCheckConfig { + pub fn configure(meta: &mut ConstraintSystem, value: Column) -> Self { + let q_range_check = meta.selector(); + + meta.create_gate("range check", |meta| { + // value | q_range_check + // ------------------------------ + // v | 1 + + let q = meta.query_selector(q_range_check); + let value = meta.query_advice(value, Rotation::cur()); + + // Given a range R and a value v, returns the expression + // (v) * (1 - v) * (2 - v) * ... * (R - 1 - v) + let range_check = |range: usize, value: Expression| { + assert!(range > 0); + (1..range).fold(value.clone(), |expr, i| { + expr * (Expression::Constant(F::from(i as u64)) - value.clone()) + }) + }; + + Constraints::with_selector(q, [("range check", range_check(RANGE, value))]) + }); + + Self { + q_range_check, + value, + _marker: PhantomData, + } + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + value: Value>, + ) -> Result, Error> { + layouter.assign_region( + || "Assign value", + |mut region| { + let offset = 0; + + // Enable q_range_check + self.q_range_check.enable(&mut region, offset)?; + + // Assign value + region + .assign_advice(|| "value", self.value, offset, || value) + .map(RangeConstrained) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use halo2_proofs::{ + circuit::floor_planner::V1, + dev::{FailureLocation, MockProver, VerifyFailure}, + pasta::Fp, + plonk::{Any, Circuit}, + }; + + use super::*; + + #[derive(Default)] + struct MyCircuit { + value: Value>, + } + + impl Circuit for MyCircuit { + type Config = RangeCheckConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let value = meta.advice_column(); + RangeCheckConfig::configure(meta, value) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + config.assign(layouter.namespace(|| "Assign value"), self.value)?; + + Ok(()) + } + } + + #[test] + fn test_range_check_1() { + let k = 4; + const RANGE: usize = 8; // 3-bit value + + // Successful cases + for i in 0..RANGE { + let circuit = MyCircuit:: { + value: Value::known(Fp::from(i as u64).into()), + }; + + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + } + + // Out-of-range `value = 8` + { + let circuit = MyCircuit:: { + value: Value::known(Fp::from(RANGE as u64).into()), + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "range check").into(), 0, "range check").into(), + location: FailureLocation::InRegion { + region: (0, "Assign value").into(), + offset: 0 + }, + cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())] + }]) + ); + } + } + + #[cfg(feature = "dev-graph")] + #[test] + fn print_range_check_1() { + use plotters::prelude::*; + + let root = BitMapBackend::new("range-check-1-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Range Check 1 Layout", ("sans-serif", 60)) + .unwrap(); + + let circuit = MyCircuit:: { + value: Value::unknown(), + }; + halo2_proofs::dev::CircuitLayout::default() + .render(3, &circuit, &root) + .unwrap(); + } +} diff --git a/src/range_check/example2.rs b/src/range_check/example2.rs new file mode 100644 index 0000000..598ee22 --- /dev/null +++ b/src/range_check/example2.rs @@ -0,0 +1,236 @@ +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{AssignedCell, Layouter, Value}, + plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, + poly::Rotation, +}; + +mod table; +use table::*; + +/// This helper checks that the value witnessed in a given cell is within a given range. +/// Depending on the range, this helper uses either a range-check expression (for small ranges), +/// or a lookup (for large ranges). +/// +/// value | q_range_check | q_lookup | table_value | +/// ---------------------------------------------------------------- +/// v_0 | 1 | 0 | 0 | +/// v_1 | 0 | 1 | 1 | +/// + +#[derive(Debug, Clone)] +/// A range-constrained value in the circuit produced by the RangeCheckConfig. +struct RangeConstrained(AssignedCell, F>); + +#[derive(Debug, Clone)] +struct RangeCheckConfig { + q_range_check: Selector, + q_lookup: Selector, + value: Column, + table: RangeTableConfig, +} + +impl + RangeCheckConfig +{ + pub fn configure(meta: &mut ConstraintSystem, value: Column) -> Self { + let q_range_check = meta.selector(); + let q_lookup = meta.complex_selector(); + let table = RangeTableConfig::configure(meta); + + meta.create_gate("range check", |meta| { + // value | q_range_check + // ------------------------------ + // v | 1 + + let q = meta.query_selector(q_range_check); + let value = meta.query_advice(value, Rotation::cur()); + + // Given a range R and a value v, returns the expression + // (v) * (1 - v) * (2 - v) * ... * (R - 1 - v) + let range_check = |range: usize, value: Expression| { + assert!(range > 0); + (1..range).fold(value.clone(), |expr, i| { + expr * (Expression::Constant(F::from(i as u64)) - value.clone()) + }) + }; + + Constraints::with_selector(q, [("range check", range_check(RANGE, value))]) + }); + + meta.lookup(|meta| { + let q_lookup = meta.query_selector(q_lookup); + let value = meta.query_advice(value, Rotation::cur()); + + vec![(q_lookup * value, table.value)] + }); + + Self { + q_range_check, + q_lookup, + value, + table, + } + } + + pub fn assign_simple( + &self, + mut layouter: impl Layouter, + value: Value>, + ) -> Result, Error> { + layouter.assign_region( + || "Assign value for simple range check", + |mut region| { + let offset = 0; + + // Enable q_range_check + self.q_range_check.enable(&mut region, offset)?; + + // Assign value + region + .assign_advice(|| "value", self.value, offset, || value) + .map(RangeConstrained) + }, + ) + } + + pub fn assign_lookup( + &self, + mut layouter: impl Layouter, + value: Value>, + ) -> Result, Error> { + layouter.assign_region( + || "Assign value for lookup range check", + |mut region| { + let offset = 0; + + // Enable q_lookup + self.q_lookup.enable(&mut region, offset)?; + + // Assign value + region + .assign_advice(|| "value", self.value, offset, || value) + .map(RangeConstrained) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use halo2_proofs::{ + circuit::floor_planner::V1, + dev::{FailureLocation, MockProver, VerifyFailure}, + pasta::Fp, + plonk::{Any, Circuit}, + }; + + use super::*; + + #[derive(Default)] + struct MyCircuit { + value: Value>, + lookup_value: Value>, + } + + impl Circuit + for MyCircuit + { + type Config = RangeCheckConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let value = meta.advice_column(); + RangeCheckConfig::configure(meta, value) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + config.table.load(&mut layouter)?; + + config.assign_simple(layouter.namespace(|| "Assign simple value"), self.value)?; + config.assign_lookup( + layouter.namespace(|| "Assign lookup value"), + self.lookup_value, + )?; + + Ok(()) + } + } + + #[test] + fn test_range_check_2() { + let k = 9; + const RANGE: usize = 8; // 3-bit value + const LOOKUP_RANGE: usize = 256; // 8-bit value + + // Successful cases + for i in 0..RANGE { + for j in 0..LOOKUP_RANGE { + let circuit = MyCircuit:: { + value: Value::known(Fp::from(i as u64).into()), + lookup_value: Value::known(Fp::from(j as u64).into()), + }; + + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + } + } + + // Out-of-range `value = 8`, `lookup_value = 256` + { + let circuit = MyCircuit:: { + value: Value::known(Fp::from(RANGE as u64).into()), + lookup_value: Value::known(Fp::from(LOOKUP_RANGE as u64).into()), + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!( + prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "range check").into(), 0, "range check").into(), + location: FailureLocation::InRegion { + region: (1, "Assign value for simple range check").into(), + offset: 0 + }, + cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())] + }, + VerifyFailure::Lookup { + lookup_index: 0, + location: FailureLocation::InRegion { + region: (2, "Assign value for lookup range check").into(), + offset: 0 + } + } + ]) + ); + } + } + + #[cfg(feature = "dev-graph")] + #[test] + fn print_range_check_2() { + use plotters::prelude::*; + + let root = BitMapBackend::new("range-check-2-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Range Check 2 Layout", ("sans-serif", 60)) + .unwrap(); + + let circuit = MyCircuit:: { + value: Value::unknown(), + lookup_value: Value::unknown(), + }; + halo2_proofs::dev::CircuitLayout::default() + .render(9, &circuit, &root) + .unwrap(); + } +} diff --git a/src/range_check/example2/table.rs b/src/range_check/example2/table.rs new file mode 100644 index 0000000..43bd2b5 --- /dev/null +++ b/src/range_check/example2/table.rs @@ -0,0 +1,45 @@ +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{Layouter, Value}, + plonk::{ConstraintSystem, Error, TableColumn}, +}; + +/// A lookup table of values from 0..RANGE. +#[derive(Debug, Clone)] +pub(super) struct RangeTableConfig { + pub(super) value: TableColumn, + _marker: PhantomData, +} + +impl RangeTableConfig { + pub(super) fn configure(meta: &mut ConstraintSystem) -> Self { + let value = meta.lookup_table_column(); + + Self { + value, + _marker: PhantomData, + } + } + + pub(super) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "load range-check table", + |mut table| { + let mut offset = 0; + for value in 0..RANGE { + table.assign_cell( + || "num_bits", + self.value, + offset, + || Value::known(F::from(value as u64)), + )?; + offset += 1; + } + + Ok(()) + }, + ) + } +} diff --git a/src/range_check/example3_broken.rs b/src/range_check/example3_broken.rs new file mode 100644 index 0000000..d150d35 --- /dev/null +++ b/src/range_check/example3_broken.rs @@ -0,0 +1,200 @@ +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{AssignedCell, Layouter, Value}, + plonk::{ + Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector, + TableColumn, + }, + poly::Rotation, +}; + +mod table; +use table::*; + +/// This helper uses a lookup table to check that the value witnessed in a given cell is +/// within a given range. +/// +/// The lookup table is tagged by `num_bits` to give a strict range check. +/// +/// value | q_lookup | table_num_bits | table_value | +/// ------------------------------------------------------------- +/// v_0 | 0 | 1 | 0 | +/// v_1 | 1 | 1 | 1 | +/// ... | ... | 2 | 2 | +/// ... | ... | 2 | 3 | +/// ... | ... | 3 | 4 | +/// +/// We use a K-bit lookup table, that is tagged 1..=K, where the tag `i` marks an `i`-bit value. +/// + +#[derive(Debug, Clone)] +/// A range-constrained value in the circuit produced by the RangeCheckConfig. +struct RangeConstrained { + num_bits: AssignedCell, F>, + assigned_cell: AssignedCell, F>, +} + +#[derive(Debug, Clone)] +struct RangeCheckConfig { + q_lookup: Selector, + num_bits: Column, + value: Column, + table: RangeTableConfig, +} + +impl RangeCheckConfig { + pub fn configure( + meta: &mut ConstraintSystem, + num_bits: Column, + value: Column, + ) -> Self { + let q_lookup = meta.complex_selector(); + let table = RangeTableConfig::configure(meta); + + meta.lookup(|meta| { + let q_lookup = meta.query_selector(q_lookup); + let num_bits = meta.query_advice(num_bits, Rotation::cur()); + let value = meta.query_advice(value, Rotation::cur()); + + // THIS IS BROKEN!!!!!! + // Hint: consider the case where q_lookup = 0. What are our input expressions to the lookup argument then? + vec![ + (q_lookup.clone() * num_bits, table.num_bits), + (q_lookup * value, table.value), + ] + }); + + Self { + q_lookup, + num_bits, + value, + table, + } + } + + pub fn assign( + &self, + mut layouter: impl Layouter, + num_bits: Value, + value: Value>, + ) -> Result, Error> { + layouter.assign_region( + || "Assign value", + |mut region| { + let offset = 0; + + // Enable q_lookup + self.q_lookup.enable(&mut region, offset)?; + + // Assign num_bits + let num_bits = num_bits.map(|v| F::from(v as u64)); + let num_bits = region.assign_advice( + || "num_bits", + self.num_bits, + offset, + || num_bits.into(), + )?; + + // Assign value + let assigned_cell = + region.assign_advice(|| "value", self.value, offset, || value)?; + + Ok(RangeConstrained { + num_bits, + assigned_cell, + }) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use halo2_proofs::{ + circuit::floor_planner::V1, + dev::{FailureLocation, MockProver, VerifyFailure}, + pasta::Fp, + plonk::{Any, Circuit}, + }; + + use super::*; + + #[derive(Default)] + struct MyCircuit { + num_bits: Value, + value: Value>, + } + + impl Circuit + for MyCircuit + { + type Config = RangeCheckConfig; + type FloorPlanner = V1; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let num_bits = meta.advice_column(); + let value = meta.advice_column(); + RangeCheckConfig::configure(meta, num_bits, value) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + config.table.load(&mut layouter)?; + + config.assign( + layouter.namespace(|| "Assign value"), + self.num_bits, + self.value, + )?; + + Ok(()) + } + } + + #[test] + fn test_range_check_3() { + let k = 9; + const NUM_BITS: usize = 8; + const RANGE: usize = 256; // 8-bit value + + // Successful cases + for num_bits in 1u8..=NUM_BITS.try_into().unwrap() { + for value in (1 << (num_bits - 1))..(1 << num_bits) { + let circuit = MyCircuit:: { + num_bits: Value::known(num_bits), + value: Value::known(Fp::from(value as u64).into()), + }; + + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + } + } + } + + #[cfg(feature = "dev-graph")] + #[test] + fn print_range_check_3() { + use plotters::prelude::*; + + let root = BitMapBackend::new("range-check-3-layout.png", (1024, 3096)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Range Check 3 Layout", ("sans-serif", 60)) + .unwrap(); + + let circuit = MyCircuit:: { + num_bits: Value::unknown(), + value: Value::unknown(), + }; + halo2_proofs::dev::CircuitLayout::default() + .render(9, &circuit, &root) + .unwrap(); + } +} diff --git a/src/range_check/example3_broken/table.rs b/src/range_check/example3_broken/table.rs new file mode 100644 index 0000000..e2a6ce2 --- /dev/null +++ b/src/range_check/example3_broken/table.rs @@ -0,0 +1,80 @@ +use std::marker::PhantomData; + +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::{Layouter, Value}, + plonk::{ConstraintSystem, Error, TableColumn}, +}; + +/// A lookup table of values up to RANGE +/// e.g. RANGE = 256, values = [0..255] +/// This table is tagged by an index `k`, where `k` is the number of bits of the element in the `value` column. +#[derive(Debug, Clone)] +pub(super) struct RangeTableConfig { + pub(super) num_bits: TableColumn, + pub(super) value: TableColumn, + _marker: PhantomData, +} + +impl RangeTableConfig { + pub(super) fn configure(meta: &mut ConstraintSystem) -> Self { + assert_eq!(1 << NUM_BITS, RANGE); + + let num_bits = meta.lookup_table_column(); + let value = meta.lookup_table_column(); + + Self { + num_bits, + value, + _marker: PhantomData, + } + } + + pub(super) fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { + layouter.assign_table( + || "load range-check table", + |mut table| { + let mut offset = 0; + + // Assign (num_bits = 1, value = 0) + { + table.assign_cell( + || "assign num_bits", + self.num_bits, + offset, + || Value::known(F::one()), + )?; + table.assign_cell( + || "assign value", + self.value, + offset, + || Value::known(F::zero()), + )?; + + offset += 1; + } + + for num_bits in 1..=NUM_BITS { + for value in (1 << (num_bits - 1))..(1 << num_bits) { + table.assign_cell( + || "assign num_bits", + self.num_bits, + offset, + || Value::known(F::from(num_bits as u64)), + )?; + table.assign_cell( + || "assign value", + self.value, + offset, + || Value::known(F::from(value as u64)), + )?; + offset += 1; + } + + } + + Ok(()) + }, + ) + } +}