diff --git a/zkevm-circuits/src/blake2b_circuit/Cargo.lock b/zkevm-circuits/src/blake2b_circuit/Cargo.lock new file mode 100644 index 0000000000..d84554c198 --- /dev/null +++ b/zkevm-circuits/src/blake2b_circuit/Cargo.lock @@ -0,0 +1,700 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "colored", + "num-traits", + "rand", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2b_circuit" +version = "0.1.0" +dependencies = [ + "ark-std", + "halo2_proofs", + "hex", + "rand", + "rand_xorshift", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "halo2_proofs" +version = "0.2.0" +source = "git+https://github.com/privacy-scaling-explorations/halo2.git?tag=v2023_02_02#0a8646b78286a13d320759b1c585262d6536dce4" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2curves", + "rand_core", + "rayon", + "sha3", + "tracing", +] + +[[package]] +name = "halo2curves" +version = "0.3.1" +source = "git+https://github.com/privacy-scaling-explorations/halo2curves.git?tag=0.3.1#9b67e19bca30a35208b0c1b41c1723771e2c9f49" +dependencies = [ + "ff", + "group", + "lazy_static", + "num-bigint", + "num-traits", + "pasta_curves", + "rand", + "rand_core", + "static_assertions", + "subtle", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[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.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[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-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +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 = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer", + "digest", + "keccak", + "opaque-debug", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "2.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/zkevm-circuits/src/blake2b_circuit/Cargo.toml b/zkevm-circuits/src/blake2b_circuit/Cargo.toml new file mode 100644 index 0000000000..cab30f2ac4 --- /dev/null +++ b/zkevm-circuits/src/blake2b_circuit/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "blake2b_circuit" +version = "0.1.0" +edition = "2021" + + +[dependencies] +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02" } +ark-std = { version = "0.3", features = ["print-trace"] } +rand_xorshift = "0.3" +hex = "0.4" +rand = "0.8" diff --git a/zkevm-circuits/src/blake2b_circuit/src/blake2b.rs b/zkevm-circuits/src/blake2b_circuit/src/blake2b.rs new file mode 100644 index 0000000000..d8417b7961 --- /dev/null +++ b/zkevm-circuits/src/blake2b_circuit/src/blake2b.rs @@ -0,0 +1,2044 @@ +/* The basic building blocks of this circuit are called GATES. In this context a gate is a map between tuples of values, + which are stored in the circuit table, with the corresponding computation algorithm and the system of algebraic equations, + which are defined over the circuit's native field in varibales of the computation algorithm and express the sufficient + condition of integrity of the corresponding computation. + + The position of a circuit table value used by a gate is determined by three parameters: + - a circuit column; + - a "point of reference" row for the gate (which is called an ASSIGNMENT ROW); + - the integer offset reltive to this row. + + The gates, which differ only in positions of the used values in the circuit table or constants of the associated algorithms + and equations, constitute a separate CLASS of gates. The gates of the same class, which differ only in assignment rows, + constitute a separate TYPE of gates. + + The library contains the struct types defining the classes of the gates (their names end with "Gate"). The instances of these + structs describe the types of gates and are created during the circuit configuring by calling the function "configure", which is + associated with the corresponding struct type. This function receives the constant values, which are specific for the described + type of gates, and the struct instances, which describe the relative (to an assignment row) positions of the used circuit + table values (including combined selectors) and their types (i.e. "native field element", "byte array", "bit", etc.). The complex + of relative positions of the circuit table cells used to store some value is called "RELATIVE PLACE". + + A gate instance of a certain type is created during the circuit synthesys by calling the method "assign" of the struct instance, + which describes the corresponding gate type. This method receives the assignment row index and information, which describes the + arguments of the map represented by the gate. The information returned by this method describes the value of the gate map and + indicates whether the gate was created successfully. + + Most columns of the circuit table are constrained in terms of stored data types in order to be usable by gates. The corresponding + constraint equations describe the possible content of a row as well as the set of the rows to constrain. This set is specified by + the "ALLOCATED" column, which is a fixed column used as a selector. The given column contains "1" in all usable rows and "0" in + all unusable ones. Also, any possible assignment row for any gate must have such an index that all circuit table values used by this + gate are in the rows, for which the "allocated" column contains "1" (otherwise, the integrity of the witness data cannot be assured). + For this reason, the set of possible assignment rows is specified by the the "ALLOWED" column (a fixed column used as a selector), + which contains "1" in all usable rows except for top and bottom several sequential ones (and "0" in all other rows). The aforesaid + CIRCUIT TABLE STRUCTURE is created during the circuit instance synthesys by calling the method "assign" of the CircuitPreparator + struct instance, which is created during the circuit configuring by calling the function "configure" of the corresponding struct type. + The constrained columns (and their groups) created by the CircuitPreparator instance may be classified into the following categories: + - byte-column pair: a pair of columns, which are allowed to contain only values in {0, 1, ..., 255} in usable rows; + - septet column: a column, usable rows of which contain only values in {0, 1, ..., 127}; + - bit column: a column of usable cells containing either 0 or 1; + - xor column triplet: a group of three columns with usable rows containing values in {0, 1, ..., 255}, the bitwise xor of which is 0. + + Gates of this circuit use only combined selectors. Such a selector consists of two cells in the same row: the first cell is in the + "ALLOWED" column and the second one is in a certain advice column. The selector is active iff both cells contain non-zero values. + Relative places of combined selectors for a gate type is described by an array of "Combiselector" struct instances. A gate is + assigned at some row iff all of the corresponding combined selectors are active. They are called the CONTROL SELECTORS of the gate. + + In order to achieve better inter-circuit integration, the approach called "random linear combination hashing" is used. The corresponding + hash of d, which is an array of n native field elements, for the challenge c and key k is designated as RLC(c, d) and computed as follows: + RLC(k, c, d) = k * c^n + c^(n - 1) * d[0] + c^(n - 2) * d[1] + ... + c * d[n - 2] + d[n - 1]. It is not hard to verify that RLC has the + given useful property: RLC(0, c, a|b) = RLC(RLC(0, c, a), c, b), where a|b stands for the concatenation of arrays a and b. + + The random linear combination hash of the concatenation of BLAKE2b compression function input and output is computed as the expression + RLC(0, c, field(r|h|m|(t mod 2^64)|(t div 2^64)|f|o)), where c is the challenge, r, h, m, t, f are defined as in RFC 7693, o is the output + state vector and field(d) maps the number tuple d to the array of the naturally corresponding native field elements. Such a hash for only + the compression function input is computed as the expression, which differs from the aforesaid one only in the absence of "|o". Therefore, + using the aforementioned property of the RLC function, the hash of the concatenation of the compression function input and output can be + expressed as RLC(i, c, field(o)), where i is the hash of the compression function input. + + Another circuit may use the computation results of the current one by adding a lookup argument, in which the table expressions are formed + using the information specified by the RLC TABLE created during the circuit configuring. This table specifies the column, whose cell may + store a computed random linear combination hash of the concatenation of the compression function input and output, the indicator pair of + columns, which simultaneously contain 1 on thoses and only those rows, which contain the hashes, and the source of the challenge used for + random linear combination hashing. The idea of this approach lies in random linear combination hashing of the unconstrained witness data, + which describes the compression function input and output, and using the lookup argument to assert that the obtained hash is the part of + the output data of the current circuit instance. + + The computation of the BLAKE2b compression function is described in the circuit instance by the sequence of the gates. The sequence starts + with an InitialGate class gate followed by zero or more RoundGate class gates and ends with a FinalGate class gate. This sequence describes + the state evolution of the abstract BLAKE2b compression function calculator described below. Its inputs are h, m, t and f defined in RFC 7693, + and the number of rounds. The challenge used for random linear combination hashing is not a gate input, but a global circuit parameter. An + InitialGate class gate uses the inputs to compute the pre-round state of the calculator. The pre-round, inter-round and post-round states + have the same structure, which includes the amount of rounds left to be performed, the random linear combination hash of the corresponding + compression function input, m permuted for the current round, h, v (defined in RFC 7693) and the array of 10 binary flags indicating the + counted from 0 number of the current round modulo 10 by the only non-zero entry. A RoundGate class gate describes the transition between the + calculator's states caused by performing a round, which changes the value of v in accordance with RFC 7693. A FinalGate class gate computes + the final calculator's state including the output of the BLAKE2b compression function as well as the random linear combination hash of the + concatenation of the input and output of this function. + + The circuit-table data of a gate may be classified into the CONTROL, INPUT, OUTPUT and INTERMEDIATE data. The control data is represented + by combined selectors except for the ones used to store the aforesaid 10 binary flags in the case of gates of the InitialGate and RoundGate + classes. These combined selectors are considered to be either input or output data. + + The input data of gates of the RoundGate and FinalGate classes are validated to be the output data of a gate of RoundGate or InitialGate + classes. This validation is done by checking the states of the specified combined selectors, the activity of which imply the existence of + the gates producing the input data for the current gate. The selectors checked by a gate this way and the gate's control selectors form the + set of the gates control data. The amount of rounds left to be performed is checked by a FinalGate class gate to be zero. + + The circuit areas, over which the gates of the aforesaid sequences operate, overlap. For an InitialGate class gate the control selectors and + the part of the calculator's pre-round state are stored in the assignment row and certain sequential ones. Its other data are stored in the + previous 8 sequential rows. The for first RoundGate class gate the assignment row is the same as for the corresponding InitialGate class gate. + The distance between the assignment rows of the adjacent gates of the RoundGate class is R, where R is the number of rows per round for the + circuit instance. The control selectors as well as the input and intermediate data of a gate of the RoundGate class are stored in R sequential + rows, the first of which is its assignment row. The output data of this gate are stored below in certain sequential rows. The assignment row of + a FinalGate class gate is R rows below the corresponding row of the sequence's last gate of the RoundGate class, iff the the sequence contains + this gate. If this sequence does not contain a gate of the RoundGate class, the gates of the InitialGate and FinalGate classes have the same + assignment rows. The control selectors as well as the input, output and intermediate data of the FinalGate class gate are stored in T sequential + rows, the first of which is its assignment row, where T is called the TAIL HEIGHT and determined by R in such a way that 8 <= T <= R. Thus, the + sequence containing q gates of the RoundGate class is stored in q * R + T + 8 rows. Therefore, if a circuit instance processes n compression + function inputs, for which the total number of rounds is q, then all the sequences are stored in q * R + n * (T + 8) rows. Since the last gate + of a sequence belongs to the FinalGate class and T <= R, the sufficient amount of the usable rows of the circuit bottom, which are not allowed + to be the assignment rows, is R - 1. The corresponding amount for the circuit top is R, since no gate operates on a row, which is more than R + rows above the assignment one. The data of the first 8 rows of the first InitialGate class gate and last T - 1 rows of the last FinalGate class + gate may be stored in the rows of these top and bottom areas of the circuit. Thus, the amount of the usable rows in the circuit instance cannot + be less than (q + 2) * R + (n - 1) * (T + 8). Also, the circuit instance must contain at least 65536 rows to be able to store the lookup-table + column triplet defining the possible values of the usable rows of a xor column triplet. Thus, the minimum value of the integer binary logarithm + of the height of a circuit instance is ceil(log2(max((q + 2) * R + (n - 1) * (T + 8), 65536) + u)), where u is the amount of the unusable rows. + + For the gates of the InitialGate, RoundGate and FialGate classes the little-endian representations of the used or computed 64-bit values are + taken from or assigned to the specified input byte-column chunks of height 8. For storing the amounts of rounds left to be performed and the + random linear combination hashes the specified unconstrained column cells are used. The 10 binary flags for both the states are stored in the + form of the specified combined selectors, which control the gates computing the m permuted for the next round. The value of f is taken from + the specified bit cell. Some specified cells are used to store the intermediate results of the computation performed by a gate. + + The value of m permuted for the next round is computed by a RoundGate class gate from the value of m permuted for the current round. The new + value is the result of applying a certain index permutation to the current one. An index permutation is an element of the group of permutations + of the set {0, 1, ..., n - 1}, where the n is the array's length. Such a permutation can described by the n-element array, the i-th entry of + which contains the value replacing i. The index permutation described by the array a is designated here as per(a). If an array is considered as + the table with the rows of elements and their indexes, then applying an index permutation to the array means applying this permutation to the + content of second row. So the value of m permuted for the (i + 1) round is the result of applying the index permutation P(i) = p(i + 1) * p'(i) + to the value of m permuted for the i-th round, where p(j) is the index permutation applied to the value m to permute it for the j-th round, + p'(j) is the inverse permutation for p(j) and p(j) * p(k) is the permutation composition, i.e. the permutation, which is equivalent to applying + p(k) and then p(j). Since the value m permuted for the i-th round is (m[SIGMA[i % 10][0]], m[SIGMA[i % 10][1]], ..., m[SIGMA[i % 10][15]]), + the value of SIGMA[i % 10][j] is replaced with j by p(i). On the other hand, per(SIGMA[i % 10]) replaces j with SIGMA[i % 10][j], therefore, + p(i) is per'(SIGMA[i % 10]). So P(i) = per'(SIGMA[(i + 1) % 10]) * per(SIGMA[i % 10]). The permutations computed by this formula are used by + RoundGate class gate to compute the values of m permuted for the next rounds without the need to access the value of m. +*/ + +use std::{ marker::PhantomData, array, convert::TryInto }; +use halo2_proofs::{ arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation }; + +// Blake2 initialization vector +const IV: [u64; 8] = [0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179]; + +// Blake2 message schedule +const SIGMA:[[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]]; + +// Optimal numbers of rows per round for the compression circuit. Optimality means that every +// number is the smallest one among all that result in the same number of columns in the circuit +const ROWS_PER_ROUND:[usize; 12] = [8, 16, 24, 32, 40, 48, 56, 64, 80, 88, 120, 128]; + +// A small module providing the implementation of the BLAKE2b compression function +pub mod compression { + // Computes the mixing function G. The elements of the input are specified according to RFC 7693. + // The computation results are returned by means of updating the vector referenced by "v" + fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); + } + + // Computes the BLAKE2b compression function. The elements of the input are specified according to RFC 7693 + pub fn compress(r: u32, h: &[u64; 8], m: &[u64; 16], t: u128, f: bool) -> [u64; 8] { + let mut h = *h; + let mut v = [0u64; 16]; + + for i in 0..8 { + v[i] = h[i]; + v[i + 8] = super::IV[i]; + } + + v[12] ^= t as u64; + v[13] ^= (t >> 64) as u64; + + if f { v[14] ^= 0xFFFF_FFFF_FFFF_FFFF; } + + for i in 0..(r as usize) { + let s = &super::SIGMA[i % 10]; + + g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]); + g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]); + g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]); + g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]); + + g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]); + g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } + + h + } +} + +// A small module providing functions for performing operations in the group of permutations of +// the set {0, 1, ..., L - 1} for the specified L. A permutation, which a module function takes +// or returns, is represented by the array, the i-th entry of which contains the value replacing i +pub mod permutation { + // Computes the permutation composition a * b, i.e. the permutation, which is + // equivalent to applying the permutations b and a in the corresponding order + pub fn compose(a: &[usize; L], b: &[usize; L]) -> [usize; L] { + let mut result = [0; L]; + for i in 0..L { + result[i] = a[b[i]]; + } + result + } + + // Computes the inverse permutation for x + pub fn invert(x: &[usize; L]) -> [usize; L] { + let mut result = [0; L]; + for i in 0..L { + result[x[i]] = i; + } + result + } +} + +// Converts a specified value into an Expression instance +fn gf(value: u128) -> Expression { + Expression::Constant(F::from_u128(value)) +} + +// Converts a specified value into a Value instance +fn known(value: u64) -> Value { + Value::known(F::from(value)) +} + +// Computes the index of a row, using it's offset relative to the specified row +fn shift_row(row: usize, offset: i32) -> usize { + let row = row as i64 + offset as i64; + assert!(row >= 0, "The row {} does not exist. Row indices are nonnegative!", row); + row as usize +} + +// Computes the expression equal to the number stored in the specified Chunk +fn chunk_to_number(meta: &mut VirtualCells, chunk: Chunk) -> Expression { + (0..(H as usize)).map(|cell| chunk.expr(meta, cell)).rfold(gf(0), |sum, byte| { sum * gf(256) + byte }) +} + +// Splits the specified slice into arrays without copying its elements +fn arrefs_from_slice(slice: &[T]) -> [&[T; S]; L] { + array::from_fn(|i| slice[S * i..][..S].try_into().unwrap()) +} + +// Creates Chunks, the byte columns for which are specified by "pairs". The relative places of these Chunks can be considered as cells +// of a table-like structure, for which columns are described by "pairs" and each row has a height of H cells. The cells of this structure +// are numbered from left to right row by row. The relative position of the first cell of the structure is the row "offset" of the column +// "pairs[0][0]". The created Chunks occupy the cells of a table-like structure, whose numbers are "skip", "skip" + 1, ..., "skip" + L - 1 +fn create_chuncks(pairs: &[[Column; 2]], offset: i32, skip: usize) -> [Chunk; L] { + array::from_fn(|i| { + let i = i + skip; + let column = pairs[i / 2 % pairs.len()][i % 2]; + let shift = i / 2 / pairs.len() * H as usize; + Chunk::::new(column, shift as i32 + offset) + }) +} + +// Creates XChunks, the xor column triplets for which are specified by "xtriplets". The relative places of these XChunks can be considered +// as cells of a table-like structure, for which columns are xor column triplets described by "xtriplets" and each row has a height of H +// cells. The cells of this structure are numbered from left to right row by row. The relative position of the first cell of the structure +// is the row "offset" of the column "xtriplets[0]". The created chunks occupy the cells of a table-like structure, whose numbers are "skip", +// "skip" + 1, ..., "skip" + L - 1 +fn create_xchunks(xtriplets: &[[Column; 3]], offset: i32, skip: usize) -> [XChunk; L] { + array::from_fn(|i| { + let i = i + skip; + let xtriplet = xtriplets[i % xtriplets.len()]; + let shift = i / xtriplets.len() * H as usize; + XChunk::::new(xtriplet, shift as i32 + offset) + }) +} + +// Creates the instance of Expression which is 1, if all the combined selectors stored in the specified relative +// places are active, and 0 otherwise +fn combine_selectors(meta: &mut VirtualCells, selectors: &[Combiselector]) -> Expression { + selectors.iter().fold(gf(1), |product, selector| product * selector.expr(meta)) +} + +// Sets the state of the combined selectors specified by their relative places and the assignment row +fn enable_selectors(region: &mut Region, row: usize, selectors: &[Combiselector], active: bool) -> Result<(), Error> { + for selector in selectors { + selector.enable(region, row, active)?; + } + Ok(()) +} + +// Creates the constraint, which asserts that strictly one group of the combined selectors stored in the relative places specified by +// "targets" is completely active for each assignment row, for which the group of the combined selectors stored in the relative places +// specified by "selectors" is completely active +fn assert_single_active(meta: &mut ConstraintSystem, selectors: &[Combiselector], targets: &[&[Combiselector]]) { + meta.create_gate("SingleCombiselector", |meta| { + vec![combine_selectors(meta, selectors) * targets.iter().fold(-gf(1), |sum, term| sum + combine_selectors(meta, term))] + }); +} + +// Creates the constraint, which asserts that the value stored in the in the relative place specified by "cell" is 0 +// for each assignment row, for which the group of the combined selectors stored in the relative places specified by +// "selectors" is completely active +fn assert_zero(meta: &mut ConstraintSystem, selectors: &[Combiselector], cell: GeneralCell) { + meta.create_gate("ZeroChunk", |meta| { + vec![combine_selectors(meta, selectors) * cell.expr(meta)] + }); +} + +// Describes the relative place of a byte-column chunk of height H +#[derive(Copy, Clone)] +struct Chunk { + column: Column, // The column containing the chunk + offset: i32, // The chunk offset relative to the assignment row + _marker: PhantomData // The marker used to specify the column's native field +} + +impl Chunk { + // Creates a Chunk for the specified byte column + // and offset relative to the assignment row + fn new(column: Column, offset: i32) -> Self { + assert!(H <= 8, "Cannot create the {}-cell Chunk. The maximum height is 8!", H); + Self { column, offset, _marker: PhantomData } + } + + // Creates the instance of Expression for the specified cell of the Chunk + fn expr(&self, meta: &mut VirtualCells, cell: usize) -> Expression { + assert!(cell < H.into(), "Accessing the cell {} in the {}-cell Chunk!", cell, H); + meta.query_advice(self.column, Rotation(self.offset + (cell as i32))) + } + + // Assigns the little-endian representation of "value" to the byte-column chunk, which + // is described by the current Chunk instance and the specified assigment row + fn assign(&self, region: &mut Region, row: usize, value: u64) -> Result<(), Error> { + let offset = shift_row(row, self.offset); + for (i, v) in value.to_le_bytes()[0..H as usize].iter().enumerate() { + region.assign_advice(|| "", self.column, offset + i, || known::(*v as u64))?; + } + Ok(()) + } + + // Creates the L-cell Chunk, which is a part of the Chunk instance and starts at its ("skip" + 1)-th cell + fn subchunk(&self, skip: u8) -> Chunk { + assert!(skip + L <= H, "Cannot create the {}-cell subchunk from the {}-cell Chunk skipping {} cells!", L, H, skip); + Chunk { column: self.column, offset: self.offset + skip as i32, _marker: PhantomData } + } +} + +// Describes the relative place of a cell of a column, usable +// rows of which contain only values in {0, 1, ..., M}; +#[derive(Copy, Clone)] +struct ShortCell { + column: Column, // The column containing the cell + offset: i32, // The cell offset relative to the assignment row + _marker: PhantomData // The marker used to specify the column's native field +} + +impl ShortCell { + // Creates a ShortCell for the specified constrained + // column and offset relative to the assignment row + fn new(column: Column, offset: i32) -> Self { + Self { column, offset, _marker: PhantomData } + } + + // Creates the instance of Expression for the ShortCell + fn expr(&self, meta: &mut VirtualCells) -> Expression { + meta.query_advice(self.column, Rotation(self.offset)) + } + + // Assigns "value" to the constrained column cell, which is described + // by the current ShortCell instance and the specified assigment row + fn assign(&self, region: &mut Region, row: usize, value: u8) -> Result<(), Error> { + let offset = shift_row(row, self.offset); + assert!(value <= M, "Cannot assign the value {} to a ShortCell with a maximum value of {}!", value, M); + region.assign_advice(|| "", self.column, offset, || known::(value as u64))?; + Ok(()) + } +} + +// Describes the relative place of a xor column triplet chunk of height H +#[derive(Copy, Clone)] +struct XChunk { + xtriplet: [Column; 3], // The xor column triplet containing the chunk + offset: i32, // The chunk offset relative to the assignment row + _marker: PhantomData // The marker used to specify the native field of the columns +} + +impl XChunk { + // Creates an XChunk for the specified xor column + // triplet and offset relative to the assignment row + fn new(xtriplet: [Column; 3], offset: i32) -> Self { + assert!(H <= 8, "Cannot create the XChunk of height {}. The maximum height is 8!", H); + Self { xtriplet, offset, _marker: PhantomData } + } + + // Creates the H-cell Chunk, which describes the "index"-th part of the current XChunk instance + fn operand(&self, index: usize) -> Chunk { + assert!(index < 3, "The operand {} does not exist in XChunks!", index); + Chunk::::new(self.xtriplet[index], self.offset) + } +} + +// Describes the relative place of a cell +// of an unconstrained column in the P-th phase +#[derive(Copy, Clone)] +struct GeneralCell { + column: Column, // The column containing the cell + offset: i32, // The cell offset relative to the assignment row + _marker: PhantomData // The marker used to specify the column's native field + +} + +impl GeneralCell { + // Creates a GeneralCell for the specified unconstrained + // column and offset relative to the assignment row + fn new(column: Column, offset: i32) -> Self { + Self { column, offset, _marker: PhantomData } + } + + // Creates the instance of Expression for the GeneralCell + fn expr(&self, meta: &mut VirtualCells) -> Expression { + meta.query_advice(self.column, Rotation(self.offset)) + } + + // Assigns "value" to the unconstrained column cell, which is described + // by the current GeneralCell instance and the specified assigment row + fn assign(&self, region: &mut Region, row: usize, value: Value) -> Result<(), Error> { + region.assign_advice(|| "", self.column, shift_row(row, self.offset), || value)?; + Ok(()) + } +} + +// Describes the relative place of a combined selector +#[derive(Copy, Clone)] +struct Combiselector { + allowed: Column, // The "allowed" column containing the fixed cell of the combined selector + selector: Column, // The column containing the advice cell of the combined selector + offset: i32, // The combined selector's offset relative to the assignment row + _marker: PhantomData // The marker used to specify the native field of the columns +} + +impl Combiselector { + // Creates a Combiselector for the specified "allowed" column, + // advice column and offset relative to the assignment row + fn new(allowed: Column, selector: Column, offset: i32) -> Self { + Self { allowed, selector, offset, _marker: PhantomData } + } + + // Creates the instance of Expression for the Combiselector + fn expr(&self, meta: &mut VirtualCells) -> Expression { + meta.query_fixed(self.allowed, Rotation(self.offset)) * meta.query_advice(self.selector, Rotation(self.offset)) + } + + // Activates or inactivates (depending on "active") the combined selector, which is + // described by the current Combiselector instance and the specified assigment row + fn enable(&self, region: &mut Region, row: usize, active: bool) -> Result<(), Error> { + let offset = shift_row(row, self.offset); + region.assign_advice(|| "", self.selector, offset, || known::(active as u64))?; + Ok(()) + } +} + +type OctaChunk = Chunk; +type ByteChunk = Chunk; +type BitCell = ShortCell; +type SeptaCell = ShortCell; + +// Defines the gate class, whose representatives activate +// or inactivate the specified target combined selector +#[derive(Clone)] +struct SelectorGate { + selectors: Vec>, // The control Combiselectors + input: bool, // The flag specifying whether the target combined selector should be activated + result: Combiselector // The target Combiselector +} + +impl SelectorGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The flag specifying whether the target combined selector should be activated + input: bool, + // The target Combiselector + result: Combiselector) -> Self { + meta.create_gate("SelectorGate", |meta| { + vec![combine_selectors(meta, selectors) * (result.expr(meta) - gf(input as u128))] + }); + + Self { selectors: selectors.to_vec(), input, result } + } + + fn assign(&self, region: &mut Region, row: usize) -> Result { + self.result.enable(region, row, self.input)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(self.input) + } +} + +// Defines the gate class, whose representatives assign the little-endian representation +// of a certain constant to the specified target byte-column chunk of height 8 +#[derive(Clone)] +struct ConstantGate { + selectors: Vec>, // The control Combiselectors + input: u64, // The constant, whose little-endian representation is assigned + result: OctaChunk // The target Chunk +} + +impl ConstantGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The constant, whose little-endian representation is assigned + input: u64, + // The target Chunk + result: OctaChunk) -> Self { + meta.create_gate("ConstantGate", |meta| { + let mut constraints = vec![]; + let selector = combine_selectors(meta, selectors); + let input = input.to_le_bytes(); + for c in 0..8 { + constraints.push(selector.clone() * (result.expr(meta, c) - gf(input[c] as u128))); + } + constraints + }); + + Self { selectors: selectors.to_vec(), input, result } + } + + fn assign(&self, region: &mut Region, row: usize) -> Result { + self.result.assign(region, row, self.input)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(self.input) + } +} + +// Defines the gate class, whose representatives assign to the specified +// target byte-column chunk of height 8 the little-endian representation +// of a constant, which is сhosen from a certain pair in accordance with +// the value of the specified choosing bit cell +#[derive(Clone)] +struct BiconstantGate { + selectors: Vec>, // The control Combiselectors + input: [u64; 2], // The constants pair, where the i-th entry is assigned iff the choosing bit cell contains i + result: OctaChunk, // The target Chunk +} + +impl BiconstantGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The constants pair, where the i-th entry is assigned iff the choosing bit cell contains i + input: [u64; 2], + // The relative place of the choosing bit cell + flag: BitCell, + // The target Chunk + result: OctaChunk) -> Self { + meta.create_gate("BiconstantGate", |meta| { + let flag = flag.expr(meta); + let selector = combine_selectors(meta, selectors); + let input = [input[0].to_le_bytes(), input[1].to_le_bytes()]; + let mut constraints = vec![]; + + for i in 0..8 { + let zero = (gf(1) - flag.clone()) * gf(input[0][i] as u128); + let one = flag.clone() * gf(input[1][i] as u128); + constraints.push(selector.clone() * (result.expr(meta, i) - zero - one)); + } + + constraints + }); + + Self { selectors: selectors.to_vec(), input, result } + } + + // The value of the choosing bit cell is described by "flag" + fn assign(&self, region: &mut Region, row: usize, flag: bool) -> Result{ + let result = if flag { self.input[1] } else { self.input[0] }; + self.result.assign(region, row, result)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives copy the value of +// the specified input byte-column chunk of height 8 to the target one +#[derive(Clone)] +struct CopyGate { + selectors: Vec>, // The control Combiselectors + result: OctaChunk // The target Chunk +} + +impl CopyGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input Chunk + input: OctaChunk, + // The target Chunk + result: OctaChunk) -> Self { + meta.create_gate("CopyGate", |meta| { + let mut constraints = vec![]; + let selector = combine_selectors(meta, selectors); + for c in 0..8 { + constraints.push(selector.clone() * (result.expr(meta, c) - input.expr(meta, c))); + } + constraints + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The number, whose little-endian representation is stored + // in the input byte-column chunk, is specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: u64) -> Result { + self.result.assign(region, row, input)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(input) + } +} + +// Defines the gate class, whose representatives copy the value of +// the specified input unconstrained column cell to the target one +#[derive(Clone)] +struct CopyGeneralGate { + selectors: Vec>, // The control Combiselectors + result: GeneralCell // The target GeneralCell +} + +impl CopyGeneralGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input GeneralCell + input: GeneralCell, + // The target GeneralCell + result: GeneralCell) -> Self { + meta.create_gate("CopyGeneralGate", |meta| { + vec![combine_selectors(meta, selectors) * (result.expr(meta) - input.expr(meta))] + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The value stored in the input unconstrained column cell is specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: Value) -> Result, Error> { + self.result.assign(region, row, input)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(input) + } +} + +// Defines the gate class, whose representatives assign to the specified +// target unconstrained column cell the value of the input one minus 1 in +// accordance with the native field arithmetic +#[derive(Clone)] +struct DownCounterGate { + selectors: Vec>, // The control Combiselectors + result: GeneralCell // The target GeneralCell +} + +impl DownCounterGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input GeneralCell + input: GeneralCell, + // The target GeneralCell + result: GeneralCell) -> Self { + meta.create_gate("DownCounterGate", |meta| { + vec![combine_selectors(meta, selectors) * (result.expr(meta) + gf(1) - input.expr(meta))] + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The value stored in the input unconstrained column cell is specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: F) -> Result { + let result = input - F::one(); + self.result.assign(region, row, Value::known(result))?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives compute the nine byte sum of up to 256 numbers, +// whose little-endian representations are stored in the specified input byte-column chunks of +// height 8, transform this sum into the little-endian representation, assign the first eight +// bytes of it to the specified target byte-column chunk of height 8 and save the last byte into +// the specified target one-byte-column chunk +#[derive(Clone)] +struct AddGate { + selectors: Vec>, // The control Combiselectors + result: OctaChunk, // The target Chunk of height 8 + carry: ByteChunk // The target ByteChunk +} + +impl AddGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input Chunks of height 8 + input: &[OctaChunk; S], + // The target Chunk of height 8 + result: OctaChunk, + // The target ByteChunk + carry: ByteChunk) -> Self { + assert!(S <= 256, "Cannot create the AddGate with {} inputs. The maximum number of inputs is 256!", S); + + meta.create_gate("AddGate", |meta| { + let left = input.iter().map(|term| chunk_to_number(meta, *term)).fold(gf(0), |sum, term| sum + term); + let right = carry.expr(meta, 0) * gf(1u128 << 64) + chunk_to_number(meta, result); + vec![combine_selectors(meta, selectors) * (left - right)] + }); + + Self { selectors: selectors.to_vec(), result, carry } + } + + // The summands are specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: &[u64; S]) -> Result<(u64, u8), Error>{ + let sum = input.iter().fold(0, |sum, term| sum + (*term as u128)); + let (result, carry) = (sum as u64, (sum >> 64) as u64); + self.result.assign(region, row, result)?; + self.carry.assign(region, row, carry)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok((result, carry as u8)) + } +} + +// Defines the gate class, whose representatives compute the bitwise xor of two numbers, whose +// little-endian representations are stored in the first and second subcolumns of the specified +// xor column triplet chunk of height 8, and assigns its little-endian representation to the third +// subcolumn of the xor column triplet chunk +#[derive(Clone)] +struct XorGate{ + result: OctaChunk // The third part of the XChunk instance used by the gate +} + +impl XorGate { + // The relative place of the xor column triplet chunk is specified by "xchunk" + fn configure(xchunk: XChunk) -> Self { + Self { result: xchunk.operand(2) } + } + + // The xored values are specified by "first" and "second" + fn assign(&self, region: &mut Region, row: usize, first: u64, second: u64) -> Result { + let result = first ^ second; + self.result.assign(region, row, result)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives compute the B-byte circular right +// shift of the number, whose little-endian representation is stored in the specified +// input byte-column chunk of height 8, and assign the little-endian representation +// of the computation result to the specified target byte-column chunk +#[derive(Clone)] +struct ShiftBytesGate{ + selectors: Vec>, // The control Combiselectors + result: OctaChunk // The target Chunk +} + +impl ShiftBytesGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input Chunk + input: OctaChunk, + // The target Chunk + result: OctaChunk) -> Self { + meta.create_gate("ShiftBytesGate", |meta| { + let mut constraints = vec![]; + let selector = combine_selectors(meta, selectors); + + for i in 0..8 { + let position = (i + 8 - B) % 8; + constraints.push(selector.clone() * (result.expr(meta, position) - input.expr(meta, i))); + } + + constraints + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The shifted number is specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: u64) -> Result{ + let result = input.rotate_right(B as u32 * 8); + self.result.assign(region, row, result)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives compute the 63-bit circular right +// shift of the number, whose little-endian representation is stored in the specified +// input byte-column chunk of height 8, and assign the little-endian representation +// of the computation result to the specified target byte-column chunk +#[derive(Clone)] +struct Shift63Gate{ + selectors: Vec>, // The control Combiselectors + bit: BitCell, // The ShortCell of the copy of the highest bit of the shifted number + septet: SeptaCell, // The ShortCell of the shifted number's highest-byte value modulo 128 + result: OctaChunk // The target Chunk +} + +impl Shift63Gate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The input Chunk + input: OctaChunk, + // The ShortCell of the copy of the highest bit of the shifted number + bit: BitCell, + // The ShortCell of the shifted number's highest-byte value modulo 128 + septet: SeptaCell, + // The target Chunk + result: OctaChunk) -> Self { + meta.create_gate("Shift63Gate", |meta| { + let selector = combine_selectors(meta, selectors); + let septet = septet.expr(meta); + let bit = bit.expr(meta); + let high = input.expr(meta, 7); + + let byte = bit.clone() * gf(128) + septet.clone(); + let left = septet * gf(1 << 57) + chunk_to_number(meta, input.subchunk::<7>(0)) * gf(2) + bit.clone(); + let right = chunk_to_number(meta, result); + + vec![selector.clone() * (byte - high), selector * (left - right)] + }); + + Self { selectors: selectors.to_vec(), bit, septet, result } + } + + // The shifted number is specified by "input" + fn assign(&self, region: &mut Region, row: usize, input: u64) -> Result{ + let bit = (input >> 63) as u8; + let septet = ((input >> 56) & 0x7F) as u8; + let result = input.rotate_right(63); + self.result.assign(region, row, result)?; + self.bit.assign(region, row, bit)?; + self.septet.assign(region, row, septet)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives set the states +// of the target combined selectors from the specified array +#[derive(Clone)] +struct SelectorMultiGate { + selectors: [SelectorGate; L] // The array of SelectorGates dealing with the corresponding target Combiselectors +} + +impl SelectorMultiGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The flag array, where the i-th entry is true iff the i-th target combined selector should be activated + input: &[bool; L], + // The array of the target Combiselectors + result: &[Combiselector; L]) -> Self { + Self { selectors: array::from_fn(|i| SelectorGate::::configure(meta, selectors, input[i], result[i])) } + } + + fn assign(&self, region: &mut Region, row: usize) -> Result<([bool; L]), Error> { + let mut result = [false; L]; + for i in 0..L { + result[i] = self.selectors[i].assign(region, row)?; + } + Ok(result) + } +} + +// Defines the gate class, whose representatives assign the little-endian +// representations of constants from a certain array to the corresponding +// the target byte-column chunks of height 8 from the specified list +#[derive(Clone)] +struct ConstantMultiGate { + constants: [ConstantGate; L] // The array of ConstantGates dealing with the corresponding target Chunks +} + +impl ConstantMultiGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The little-endian representation of the i-th entry of this array is assigned to the i-th target byte-column chunk + input: &[u64; L], + // The array of the target Chunks + result: &[OctaChunk; L]) -> Self { + Self { constants: array::from_fn(|i| ConstantGate::::configure(meta, selectors, input[i], result[i])) } + } + + fn assign(&self, region: &mut Region, row: usize) -> Result<([u64; L]), Error> { + let mut result = [0; L]; + for i in 0..L { + result[i] = self.constants[i].assign(region, row)?; + } + Ok(result) + } +} + +// Defines the gate class, whose representatives copy the values of the byte-column chunks +// of height 8 from the specified input list to such chunks from a certain target list in +// accordance with the index permutation described by the specified array, the i-th entry +// of which contains the value replacing i, i.e. the value of the j-th chunk of the input +// list is assigned to the chunk, whose index in the target list equals to the value of +// the j-th entry of the array describing the index permutation +#[derive(Clone)] +struct PermuteGate{ + permutation: [usize; L], // The array describing the index permutation + copy: [CopyGate; L] // The array of CopyGates dealing with the corresponding target Chunks +} + +impl PermuteGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The array describing the index permutation or "None" indicating the identity index permutation + permutation: Option<&[usize; L]>, + // The array of the input Chunks + input: &[OctaChunk; L], + // The array of the target Chunks + result: &[OctaChunk; L]) -> Self { + let permutation = match permutation { Some(value) => *value, None => array::from_fn(|i| i) }; + let copy = array::from_fn(|i| CopyGate::::configure(meta, selectors, input[i], result[permutation[i]])); + Self { permutation, copy } + } + + // The i-th entry of "input" specifies the number, whose little-endian representation is stored in the i-th input byte-column chunk + fn assign(&self, region: &mut Region, row: usize, input: &[u64; L]) -> Result<([u64; L]), Error> { + let mut result = [0; L]; + for i in 0..L { + result[self.permutation[i]] = self.copy[i].assign(region, row, input[i])?; + } + Ok(result) + } +} + +// Defines the gate class, whose representatives set the states of the combined selectors from +// the specified target list in such a way that the ((i + 1) mod L)-th selector of this list +// has the same state as i-th combined selector of the specified input list +#[derive(Clone)] +struct SelectorShiftGate{ + selectors: Vec>, // The control Combiselectors + result: [Combiselector; L], // The array of the target Combiselectors +} + +impl SelectorShiftGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The array of the input Combiselectors + input: &[Combiselector; L], + // The array of the target Combiselectors + result: &[Combiselector; L]) -> Self { + meta.create_gate("SelectorShiftGate", |meta| { + let selector = combine_selectors(meta, selectors); + let mut constraints = vec![]; + + for i in 0..L { + constraints.push(selector.clone() * (result[(i + 1) % L].expr(meta) - input[i].expr(meta))); + } + + constraints + }); + + Self { selectors: selectors.to_vec(), result: *result } + } + + // The i-th entry of "input" specifies whether the i-th input combined selector is active + fn assign(&self, region: &mut Region, row: usize, input: &[bool; L]) -> Result<([bool; L]), Error> { + let mut result = [false; L]; + for i in 0..L { + let j = (i + 1) % L; + self.result[j].enable(region, row, input[i])?; + result[j] = input[i]; + } + enable_selectors(region, row, &self.selectors, true)?; + Ok(result) + } +} + +// Defines the gate class, whose representatives assign to the specified target unconstrained +// column cell the random linear combination hash of certain BLAKE2b compression function +// input. The little-endian representations of the 64-bit input values, including t mod 2^64 +// and t div 2^64, are taken from the specified input byte-column chunks of height 8. The +// field elements describing r and f are taken from the specified input unconstrained column +// cell and bit cell +#[derive(Clone)] +struct InitialRLCGate{ + selectors: Vec>, // The control Combiselectors + result: GeneralCell // The target GeneralCell +} + +impl InitialRLCGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The source of the challenge used for hashing + challenge: Challenge, + // The GeneralCell of the number of rounds + r: GeneralCell, + // The array of the Chunks of the initial state vector + h: &[OctaChunk; 8], + // The array of the Chunks of the message block vector + m: &[OctaChunk; 16], + // The array of the Chunks of the vector (t mod 2^64, t div 2^64), where t is the message byte offset + t: &[OctaChunk; 2], + // The ShortCell of the flag indicating the last block + f: BitCell, + // The target GeneralCell + result: GeneralCell) -> Self { + meta.create_gate("InitialRLCGate", |meta| { + let challenge = meta.query_challenge(challenge); + + let (mut rlc, f) = (r.expr(meta), f.expr(meta)); + + let terms = h.iter().chain(m.iter()).chain(t.iter()).map(|c| + chunk_to_number(meta, *c)).chain([f].into_iter()); + + for term in terms { + rlc = rlc * challenge.clone() + term; + } + + vec![combine_selectors(meta, selectors) * (result.expr(meta) - rlc)] + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The challenge is specified by "c", the elements of the compression function input are specified according to RFC 7693 + fn assign(&self, region: &mut Region, row: usize, c: Value, r: F, + h: &[u64; 8], m: &[u64; 16], t: u128, f: bool) -> Result, Error> { + + let (t, f) = ([t as u64, (t >> 64) as u64], [f as u64]); + let terms = h.iter().chain(m.iter()).chain(t.iter()).chain(f.iter()).map(|v| known::(*v)); + + let mut rlc = Value::known(r); + for term in terms { + rlc = rlc * c + term; + } + + self.result.assign(region, row, rlc)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(rlc) + } +} + +// Defines the gate class, whose representatives assign to the specified target unconstrained +// column cell the random linear combination hash of the concatenation of certain BLAKE2b +// compression function input and output. The computation of the hash is based on the formula +// RLC(i, c, field(o)) mentioned above. The value of i is taken from the specified input +// unconstrained column cell. The little-endian representations of elements of o are taken +// from the specified input byte-column chunks of height 8 +#[derive(Clone)] +struct FinalRLCGate{ + selectors: Vec>, // The control Combiselectors + result: GeneralCell // The target GeneralCell +} + +impl FinalRLCGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The source of the challenge used for random linear combination hashing + challenge: Challenge, + // The GeneralCell of the hash of the compression function input + i: GeneralCell, + // The array of the Chunks of the output state vector + o: &[OctaChunk; 8], + // The target GeneralCell + result: GeneralCell) -> Self { + meta.create_gate("FinalRLCGate", |meta| { + let challenge = meta.query_challenge(challenge); + + let mut rlc = i.expr(meta); + for term in o.iter().map(|c| chunk_to_number(meta, *c)) { + rlc = rlc * challenge.clone() + term; + } + + vec![combine_selectors(meta, selectors) * (result.expr(meta) - rlc)] + }); + + Self { selectors: selectors.to_vec(), result } + } + + // The challenge is specified by "c", the hash of the compression function + // input is described by "i", the output state vector is specified by "o" + fn assign(&self, region: &mut Region, row: usize, c: Value, i: Value, o: &[u64; 8]) -> Result, Error> { + let mut rlc = i; + for term in o.iter().map(|v| known::(*v)) { + rlc = rlc * c + term; + } + + self.result.assign(region, row, rlc)?; + enable_selectors(region, row, &self.selectors, true)?; + Ok(rlc) + } +} + +// Defines the gate class, whose representatives compute the new values of four entries of the local state vector v, +// which are used by the mixing function G in accordance with RFC 7693. The little-endian representations of the 64-bit +// values of the four affected entries of v and two entries of the message block vector m, which are used by G, are taken +// from the specified input byte-column chunks of height 8. The results of computation are assigned to the specified such +// chunks. Some specified cells are used to store the intermediate results of the computation +#[derive(Clone)] +struct GGate { + copy: [CopyGate; 4], // The gate types used for copying the values to subcolumns of xor column triplet chunks + add3: [AddGate; 2], // The gate types used for computing (v[a] + v[b] + x) mod 2**w and (v[a] + v[b] + y) mod 2**w + add2: [AddGate; 2], // The gate types used for computing (v[c] + v[d]) mod 2**w + xors: [XorGate; 4], // The gate types used for computing the expressions involving the bitwise xor operation + shift32: ShiftBytesGate, // The gate type used for computing (v[d] ^ v[a]) >>> R1, where R1 = 32 + shift24: ShiftBytesGate, // The gate type used for computing (v[b] ^ v[c]) >>> R2, where R2 = 24 + shift16: ShiftBytesGate, // The gate type used for computing (v[d] ^ v[a]) >>> R3, where R3 = 16 + shift63: Shift63Gate // The gate type used for computing (v[b] ^ v[c]) >>> R4, where R4 = 63 +} + +impl GGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The array of the Chunks of the initial values of v[a], v[b], v[c] and v[d] + input: &[OctaChunk; 4], + // The Chunk of x + x: OctaChunk, + // The Chunk of y + y: OctaChunk, + // The ShortCell of the intermediate results of the computation + bit: BitCell, + // The ShortCell of the intermediate results of the computation + septet: SeptaCell, + // The array of the Chunks of the intermediate results of the computation + bytes: &[ByteChunk; 4], + // The array of the XChunks of the intermediate results of the computation + xchunks: &[XChunk; 4], + // The array of the Chunks of the new values of v[a], v[b], v[c] and v[d] + result: &[OctaChunk; 4]) -> Self { + // The Chunks of the intermediate values of v[a], v[b], v[c] and v[d] in the middle of the computation of G + let [a, c, d, b]: [OctaChunk; 4] = array::from_fn(|i| xchunks[i].operand(0)); + let xout: [OctaChunk; 4] = array::from_fn(|i| xchunks[i].operand(2)); + + Self { + copy: [CopyGate::::configure(meta, selectors, input[3], xchunks[0].operand(1)), + CopyGate::::configure(meta, selectors, input[1], xchunks[1].operand(1)), + CopyGate::::configure(meta, selectors, result[0], xchunks[2].operand(1)), + CopyGate::::configure(meta, selectors, result[2], xchunks[3].operand(1))], + + xors: [XorGate::::configure(xchunks[0]), XorGate::::configure(xchunks[1]), + XorGate::::configure(xchunks[2]), XorGate::::configure(xchunks[3])], + + add3: [AddGate::::configure(meta, selectors, &[input[0], input[1], x], a, bytes[0]), + AddGate::::configure(meta, selectors, &[a, b, y], result[0], bytes[1])], + + add2: [AddGate::::configure(meta, selectors, &[d, input[2]], c, bytes[2]), + AddGate::::configure(meta, selectors, &[c, result[3]], result[2], bytes[3])], + + shift32: ShiftBytesGate::::configure(meta, selectors, xout[0], d), + shift24: ShiftBytesGate::::configure(meta, selectors, xout[1], b), + shift16: ShiftBytesGate::::configure(meta, selectors, xout[2], result[3]), + shift63: Shift63Gate::::configure(meta, selectors, xout[3], bit, septet, result[1]), + } + } + + // The input of the function G is specified according to RFC 7693. The computation + // results are returned by means of updating the vector referenced by "v" + fn assign(&self, region: &mut Region, row: usize, v: &mut [u64; 16], + a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) -> Result<(), Error> { + + v[a] = self.add3[0].assign(region, row, &[v[a], v[b], x])?.0; + self.copy[0].assign(region, row, v[d])?; + v[d] = self.xors[0].assign(region, row, v[d], v[a])?; + v[d] = self.shift32.assign(region, row, v[d])?; + v[c] = self.add2[0].assign(region, row, &[v[c], v[d]])?.0; + self.copy[1].assign(region, row, v[b])?; + v[b] = self.xors[1].assign(region, row, v[b], v[c])?; + v[b] = self.shift24.assign(region, row, v[b])?; + + v[a] = self.add3[1].assign(region, row, &[v[a], v[b], y])?.0; + self.copy[2].assign(region, row, v[a])?; + v[d] = self.xors[2].assign(region, row, v[d], v[a])?; + v[d] = self.shift16.assign(region, row, v[d])?; + v[c] = self.add2[1].assign(region, row, &[v[c], v[d]])?.0; + self.copy[3].assign(region, row, v[c])?; + v[b] = self.xors[3].assign(region, row, v[b], v[c])?; + v[b] = self.shift63.assign(region, row, v[b])?; + + Ok(()) + } +} + +// Defines the gate class, whose representatives compute the pre-round +// state of the abstract BLAKE2b compression function calculator +#[derive(Clone)] +struct InitialGate { + rlc: InitialRLCGate, // The gate type for computing the random linear combination hash + half: PermuteGate, // The gate type for computing the first half of the local work vector + quarter: ConstantMultiGate, // The gate type for computing the third quarter of the local work vector + x: ConstantMultiGate, // The gate types for assigning the Blake2 initialization vector data to subcolumns of xor column triplet chunks + t: PermuteGate, // The gate types for copying the data of the vector (t mod 2^64, t div 2^64) to subcolumns of xor column triplet chunks + xors: [XorGate; 2], // The gate types for computing the 12-th and 13-th elements of the local work vector + xout: PermuteGate::, // The gate types for copying the values from the third subcolumns of xor column triplet chunks + not: BiconstantGate, // The gate type for computing the 14-th element of the local work vector + last: ConstantGate, // The gate type for computing the 15-th element of the local work vector + p: SelectorMultiGate // The gate type used for setting the states of the 10 "binary flags" combined selectors +} + +impl InitialGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The source of the challenge used for random linear combination hashing + challenge: Challenge, + // The GeneralCell of the number of rounds. It is also a part of the calculator's pre-round state + r: GeneralCell, + // The array of the Chunks of the initial state vector. It is also a part of the calculator'spre-round state + h: &[OctaChunk; 8], + // The array of the Chunks of the message block vector. It is also a part of the calculator's pre-round state + m: &[OctaChunk; 16], + // The array of the Chunks of the vector (t mod 2^64, t div 2^64), where t is the message byte offset + t: &[OctaChunk; 2], + // The ShortCell of the flag indicating the last block + f: BitCell, + // The array of the XChunks of the intermediate results of the computation + xchunks: &[XChunk; 2], + // The array of the Chunks of the local work vector computed for the calculator's pre-round state + v: &[OctaChunk; 16], + // The Combiselector array of the 10 "binary flags" combined selectors computed for the calculator's pre-round state + p: &[Combiselector; 10], + // The GeneralCell of the random linear combination hash computed for the calculator's pre-round state + rlc: GeneralCell) -> Self { + Self { + half: PermuteGate::::configure(meta, selectors, + None, &h, &v[0..8].try_into().unwrap()), + + quarter: ConstantMultiGate::::configure(meta, selectors, + &IV[0..4].try_into().unwrap(), &v[8..12].try_into().unwrap()), + + x: ConstantMultiGate::::configure(meta, selectors, + &IV[4..6].try_into().unwrap(), &[xchunks[0].operand(0), xchunks[1].operand(0)]), + + t: PermuteGate::::configure(meta, selectors, None, + &t, &[xchunks[0].operand(1), xchunks[1].operand(1)]), + + xors: [XorGate::::configure(xchunks[0]), XorGate::::configure(xchunks[1])], + + xout: PermuteGate::::configure(meta, selectors, None, + &[xchunks[0].operand(2), xchunks[1].operand(2)], &[v[12], v[13]]), + + not: BiconstantGate::::configure(meta, selectors, [IV[6], !IV[6]], f, v[14]), + + last: ConstantGate::::configure(meta, selectors, IV[7], v[15]), + + p: SelectorMultiGate::::configure(meta, selectors, &array::from_fn(|i| i == 0), p), + + rlc: InitialRLCGate::::configure(meta, selectors, challenge, r, h, m, t, f, rlc) + } + } + + // The challenge is specified by "c", the compression function input is specified according to RFC 7693 + fn assign(&self, region: &mut Region, row: usize, c: Value, r: F, h: &[u64; 8], + m: &[u64; 16], t: u128, f: bool) -> Result<(Value, [u64; 16]), Error> { + + let rlc = self.rlc.assign(region, row, c, r, h, m, t, f)?; + + let mut v = [0; 16]; + v[0..8].clone_from_slice(&self.half.assign(region, row, h)?); + v[8..12].clone_from_slice(&self.quarter.assign(region, row)?); + + let t = [t as u64, (t >> 64) as u64]; + let xins = [self.x.assign(region, row)?, + self.t.assign(region, row, &t)?]; + let xouts = [self.xors[0].assign(region, row, xins[0][0], xins[1][0])?, + self.xors[1].assign(region, row, xins[0][1], xins[1][1])?]; + v[12..14].copy_from_slice(&self.xout.assign(region, row, &xouts)?); + + v[14] = self.not.assign(region, row, f)?; + v[15] = self.last.assign(region, row)?; + + self.p.assign(region, row)?; + + Ok((rlc, v)) + } +} + +// Defines the gate class, whose representatives compute the new state of the +// abstract BLAKE2b compression function calculator after it performs a round +#[derive(Clone)] +struct RoundGate { + l: DownCounterGate, // The gate type for computing the new amount of rounds left to be performed + h: PermuteGate, // The gate type for copying the value of h, since it is unchanged + p: SelectorShiftGate, // The gate type for computing the new states of the 10 "binary flags" combined selectors + m: [PermuteGate; 10], // The gate type for computing the value of m permuted for the next round + v: [GGate; 8], // The gate types for computing the expressions involving the mixing function G + rlc: CopyGeneralGate // The gate type for copying the random linear combination hash, since it is unchanged +} + +impl RoundGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The Combiselectors, whose activity imply the existence of the InitialGate class gate producing the input data for this gate + initial: &[Combiselector], + // The Combiselectors, whose activity imply the existence of the RoundGate class gate producing the input data for this gate + round: &[Combiselector], + // The GeneralCells of the current and new amounts of rounds left to be performed + left: [GeneralCell; 2], + // The Chunk arrays of h for the current and new states of the calculator + h: [&[OctaChunk; 8]; 2], + // The Chunk arrays of the m values permuted for the current and next rounds + m: [&[OctaChunk; 16]; 2], + // The Chunk arrays of the current and new values of v + v: [&[OctaChunk; 16]; 2], + // The Combiselector arrays of the 10 "binary flags" combined selectors for the current and new states of the calculator + p: [&[Combiselector; 10]; 2], + // The GeneralCells of the random linear combination hash for the current and new states of the calculator + rlc: [GeneralCell; 2], + // The array of the ShortCells of the intermediate results of the computation + bits: &[BitCell; 8], + // The array of the ShortCells of the intermediate results of the computation + septets: &[SeptaCell; 8], + // The array of the Chunks of the intermediate results of the computation + bytes: &[ByteChunk; 32], + // The array of the Chunks of the intermediate results of the computation + qwords: &[OctaChunk; 16], + // The array of the XChunks of the intermediate results of the computation + xchunks: &[XChunk; 32]) -> Self { + let bytes: [&[ByteChunk; 4]; 8] = arrefs_from_slice(bytes); + let xors: [&[XChunk; 4]; 8] = arrefs_from_slice(xchunks); + let ([pi, po], [mi, mo], [vi, vo]) = (p, m, v); + assert_single_active(meta, selectors, &[initial, round]); + + Self { + l: DownCounterGate::::configure(meta, selectors, left[0], left[1]), + + h: PermuteGate::::configure(meta, selectors, None, h[0], h[1]), + + p: SelectorShiftGate::::configure(meta, selectors, pi, po), + + m: array::from_fn(|i| { + // Using the formula P(i) = per'(SIGMA[(i + 1) % 10]) * per(SIGMA[i % 10]) + let permutation = permutation::compose(&permutation::invert(&SIGMA[(i + 1) % 10]), &SIGMA[i]); + PermuteGate::configure(meta, &[selectors, &pi[i..i + 1]].concat(), Some(&permutation), &mi, &mo) + }), + + v: [GGate::configure(meta, selectors, &[vi[0], vi[4], vi[8], vi[12]], mi[0], + mi[1], bits[0], septets[0], bytes[0], xors[0], &[qwords[0], qwords[4], qwords[8], qwords[12]]), + GGate::configure(meta, selectors, &[vi[1], vi[5], vi[9], vi[13]], mi[2], + mi[3], bits[1], septets[1], bytes[1], xors[1], &[qwords[1], qwords[5], qwords[9], qwords[13]]), + GGate::configure(meta, selectors, &[vi[2], vi[6], vi[10], vi[14]], mi[4], + mi[5], bits[2], septets[2], bytes[2], xors[2], &[qwords[2], qwords[6], qwords[10], qwords[14]]), + GGate::configure(meta, selectors, &[vi[3], vi[7], vi[11], vi[15]], mi[6], + mi[7], bits[3], septets[3], bytes[3], xors[3], &[qwords[3], qwords[7], qwords[11], qwords[15]]), + + GGate::configure(meta, selectors, &[qwords[0], qwords[5], qwords[10], qwords[15]], mi[8], + mi[9], bits[4], septets[4], bytes[4], xors[4], &[vo[0], vo[5], vo[10], vo[15]]), + GGate::configure(meta, selectors, &[qwords[1], qwords[6], qwords[11], qwords[12]], mi[10], + mi[11], bits[5], septets[5], bytes[5], xors[5], &[vo[1], vo[6], vo[11], vo[12]]), + GGate::configure(meta, selectors, &[qwords[2], qwords[7], qwords[8], qwords[13]], mi[12], + mi[13], bits[6], septets[6], bytes[6], xors[6], &[vo[2], vo[7], vo[8], vo[13]]), + GGate::configure(meta, selectors, &[qwords[3], qwords[4], qwords[9], qwords[14]], mi[14], + mi[15], bits[7], septets[7], bytes[7], xors[7], &[vo[3], vo[4], vo[9], vo[14]]) + ], + + rlc: CopyGeneralGate::::configure(meta, selectors, rlc[0], rlc[1]) + } + } + + // The hash of the compression function input is specified by "rlc", the counted from 0 number of the current round modulo 10 is + // described by "round", the amount of rounds left to be performed for the state, which preceeds the current round, is specified + // by "left", "h", "m" and "v" describe the corresponding elements of the calculator's state. After a successful execution of the + // method, the aforesaid variables describe the calculator's computed state + fn assign(&self, region: &mut Region, row: usize, rlc: Value, round: &mut usize, + left: &mut F, h: &[u64; 8], m: &mut [u64; 16], v: &mut [u64; 16]) -> Result<(), Error> { + + *left = self.l.assign(region, row, *left)?; + self.h.assign(region, row, h)?; + + self.v[0].assign(region, row, v, 0, 4, 8, 12, m[0], m[1])?; + self.v[1].assign(region, row, v, 1, 5, 9, 13, m[2], m[3])?; + self.v[2].assign(region, row, v, 2, 6, 10, 14, m[4], m[5])?; + self.v[3].assign(region, row, v, 3, 7, 11, 15, m[6], m[7])?; + + self.v[4].assign(region, row, v, 0, 5, 10, 15, m[8], m[9])?; + self.v[5].assign(region, row, v, 1, 6, 11, 12, m[10], m[11])?; + self.v[6].assign(region, row, v, 2, 7, 8, 13, m[12], m[13])?; + self.v[7].assign(region, row, v, 3, 4, 9, 14, m[14], m[15])?; + + self.p.assign(region, row, &array::from_fn(|i| i == *round % 10))?; + *m = self.m[*round % 10].assign(region, row, m)?; + *round += 1; + + self.rlc.assign(region, row, rlc)?; + + Ok(()) + } +} + + +// Defines the gate class, whose representatives compute the final state of the abstract BLAKE2b compression function calculator. +// This state includes the output of the BLAKE2b compression function as well as the random linear combination hash of the +// concatenation of the input and output of this function +#[derive(Clone)] +struct FinalGate { + h: PermuteGate, // The gate types for copying the data of the initial state vector to subcolumns of xor column triplet chunks + v: PermuteGate, // The gate types for copying the data of the current local work vector to subcolumns of xor column triplet chunks + xh: [XorGate; 8], // The gate types for computing the compression function output + xv: [XorGate; 8], // The gate types for computing the bitwise xor of the halves of the current local work vector + xcopy: PermuteGate, // The gate types for data copying between the subcolumns of xor column triplet chunks + rlc: FinalRLCGate // The gate type for computing the hash of the concatenation of the input and output of the compression function +} + +impl FinalGate { + fn configure(meta: &mut ConstraintSystem, + // The control Combiselectors + selectors: &[Combiselector], + // The source of the challenge used for random linear combination hashing + challenge: Challenge, + // The Combiselectors, whose activity imply the existence of the InitialGate class gate producing the input data for this gate + initial: &[Combiselector], + // The Combiselectors, whose activity imply the existence of the RoundGate class gate producing the input data for this gate + round: &[Combiselector], + // The GeneralCell of the current amount of rounds left to be performed + left: GeneralCell, + // The Chunk array of the initial state vector + h: &[OctaChunk; 8], + // The Chunk array of the current local work vector + v: &[OctaChunk; 16], + // The array of the XChunks, whose default-ordered third parts constitute the Chunk array of the compression function output + xh: &[XChunk; 8], + // The array of the XChunks of the intermediate results of the computation + xv: &[XChunk; 8], + // The GeneralCells of the random linear combination hash of the compression function input and the computed hash + rlc: [GeneralCell; 2]) -> Self { + assert_single_active(meta, selectors, &[initial, round]); + assert_zero(meta, selectors, left); + + Self { + h: PermuteGate::::configure(meta, selectors, None, h, &array::from_fn(|i| xh[i].operand(1))), + + v: PermuteGate::::configure(meta, selectors, None, v, &array::from_fn(|i| xv[i % 8].operand(i / 8))), + + xh: array::from_fn(|i| XorGate::::configure(xh[i])), + + xv: array::from_fn(|i| XorGate::::configure(xv[i])), + + xcopy: PermuteGate::::configure(meta, selectors, None, + &array::from_fn(|i| xv[i].operand(2)), &array::from_fn(|i| xh[i].operand(0))), + + rlc: FinalRLCGate::configure(meta, selectors, challenge, rlc[0], &array::from_fn(|i| xh[i].operand(2)), rlc[1]) + } + } + + // The challenge is specified by "c", the hash of the compression function input is specified + // by "rlc", "h" and "v" describe the corresponding elements of the calculator's current state + fn assign(&self, region: &mut Region, row: usize, c: Value, + rlc: Value, h: &[u64; 8], v: &[u64; 16]) -> Result<([u64; 8], Value), Error> { + + self.h.assign(region, row, h)?; + self.v.assign(region, row, v)?; + let mut xor = [0u64; 8]; + + for i in 0..8 { + xor[i] = self.xv[i].assign(region, row, v[i], v[i + 8])?; + } + + self.xcopy.assign(region, row, &xor)?; + + for i in 0..8 { + xor[i] = self.xh[i].assign(region, row, xor[i], h[i])?; + } + + let rlc = self.rlc.assign(region, row, c, rlc, &xor)?; + + Ok((xor, rlc)) + } +} + +// An instance of this struct creates the circuit table structure +#[derive(Clone)] +struct CircuitPreparator { + allocated: Column, // The "allocated" column + allowed: Column, // The "allowed" column + septalookup: Column, // The lookup-table column defining the possible values of the usable rows of a septet column + xlookup: [Column; 3], // The lookup-table column triplet defining the possible values of the usable rows of a xor column triplet + _marker: PhantomData // The marker used to specify the circuit's native field +} + +impl CircuitPreparator { + fn configure(meta: &mut ConstraintSystem, + // The "allocated" column + allocated: Column, + // The "allowed" column + allowed: Column, + // The lookup-table column defining the possible values of the usable rows of a septet column + septalookup: Column, + // The lookup-table column triplet defining the possible values of the usable rows of a xor column triplet + xlookup: [Column; 3], + // The bit columns + binary: &[Column], + // The septet columns + septenary: &[Column], + // The byte-column pairs + pairs: &[[Column; 2]], + // The xor column triplets + xtriplets: &[[Column; 3]]) -> Self { + meta.create_gate("CircuitPreparatorBinary", |meta| { + let allocated = meta.query_fixed(allocated, Rotation::cur()); + let mut constraints = vec![]; + for column in binary { + let column = meta.query_advice(*column, Rotation::cur()); + constraints.push(allocated.clone() * column.clone() * (gf(1) - column)); + } + constraints + }); + + for column in septenary { + meta.lookup_any("CircuitPreparatorSeptenary", |meta| { + let allocated = meta.query_fixed(allocated, Rotation::cur()); + vec![(allocated * meta.query_advice(*column, Rotation::cur()), meta.query_fixed(septalookup, Rotation::cur()))] + }); + } + + for pair in pairs { + meta.lookup_any("CircuitPreparatorPairs", |meta| { + let allocated = meta.query_fixed(allocated, Rotation::cur()); + vec![(allocated.clone() * meta.query_advice(pair[0], Rotation::cur()), meta.query_fixed(xlookup[0], Rotation::cur())), + (allocated.clone() * meta.query_advice(pair[1], Rotation::cur()), meta.query_fixed(xlookup[1], Rotation::cur()))] + }); + } + + for xtriplet in xtriplets { + meta.lookup_any("CircuitPreparatorXTriplets", |meta| { + let allocated = meta.query_fixed(allocated, Rotation::cur()); + vec![(allocated.clone() * meta.query_advice(xtriplet[0], Rotation::cur()), meta.query_fixed(xlookup[0], Rotation::cur())), + (allocated.clone() * meta.query_advice(xtriplet[1], Rotation::cur()), meta.query_fixed(xlookup[1], Rotation::cur())), + (allocated.clone() * meta.query_advice(xtriplet[2], Rotation::cur()), meta.query_fixed(xlookup[2], Rotation::cur()))] + }); + } + + Self { allocated, allowed, septalookup, xlookup, _marker: PhantomData } + } + + // The binary logarithm of the circuit's height is specified by "k", "before" and "after" specify the amounts + // of the top and bottom sequential usable rows, for which the "allowed"-column cell contain 0, the amount of + // the unusable rows is specified by "unusable" + fn assign(&self, region: &mut Region, k: u32, before: usize, after: usize, unusable: usize) -> Result<(), Error> { + assert!(65536 + unusable <= (1 << k), "Not enough rows to prepare the lookup tables!"); + + let disallowed = before + after + unusable; + assert!(disallowed < (1 << k), "Not enough rows to prepare the place for gates!"); + + for row in 0..128 { + region.assign_fixed(|| "", self.septalookup, row as usize, || known::(row))?; + } + + for row in 0..65536 { + let (first, second) = (row as u64 & 0xFF, (row as u64 >> 8) & 0xFF); + region.assign_fixed(|| "", self.xlookup[0], row, || known::(first))?; + region.assign_fixed(|| "", self.xlookup[1], row, || known::(second))?; + region.assign_fixed(|| "", self.xlookup[2], row, || known::(first ^ second))?; + } + + let usable = (1 << k) - unusable; + let allowed = (1 << k) - disallowed; + + for row in 0..usable { + region.assign_fixed(|| "", self.allocated, row, || known::(1))?; + } + + for row in before..(before + allowed) { + region.assign_fixed(|| "", self.allowed, row, || known::(1))?; + } + + Ok(()) + } +} + +// An instance of this struct describes an RLC table +#[derive(Clone)] +pub struct RLCTable { + pub allowed: Column, // The column of the indicator pair + pub selector: Column, // The column of the indicator pair + pub rlc: Column, // The column, whose cell may store a computed random linear combination hash + pub challenge: Challenge, // The source of the challenge used for random linear combination hashing + pub _marker: PhantomData // The marker used to specify the circuit's native field +} + +impl RLCTable { + // Computes the random linear combination hash of the concatenation of BLAKE2b compression function input and output + pub fn compute_rlc(c: Value, h: &[u64; 8], m: &[u64; 16], t: u128, f: bool, r: u32, o: &[u64; 8]) -> Value { + let (t, f) = ([t as u64, (t >> 64) as u64], [f as u64]); + let terms = h.iter().chain(m.iter()).chain(t.iter()). + chain(f.iter()).chain(o.iter()).map(|v| known::(*v)); + let mut rlc = known::(r as u64); + for term in terms { + rlc = rlc * c + term; + } + rlc + } + + // Computes the BLAKE2b compression function and the random linear combination hash of the concatenation + // of the corresponding input and output + pub fn compress_with_rlc(c: Value, h: &[u64; 8], m: &[u64; 16], t: u128, f: bool, r: u32) -> ([u64; 8], Value) { + let o = compression::compress(r, h, m, t, f); + let rlc = Self::compute_rlc(c, h, m, t, f, r, &o); + (o, rlc) + } +} + +// An instrance of this struct describes +// the BLAKE2b compression function input +#[derive(Clone, Debug)] +pub struct CompressionInput { + pub r: u32, // The number of rounds + pub h: [u64; 8], // The initial state vector + pub m: [u64; 16], // The message block vector + pub t: u128, // The message byte offset + pub f: bool // The flag indicating the last block +} + +// An instance of this struct describes the relative places of the +// elements of the input for a gate type of the InitialGate class +#[derive(Clone)] +struct InitialInput { + r: GeneralCell, // The GeneralCell of the number of rounds + h: [OctaChunk; 8], // The array of the Chunks of the initial state vector + m: [OctaChunk; 16], // The array of the Chunks of the message block vector + t: [OctaChunk; 2], // The array of the Chunks of the vector (t mod 2^64, t div 2^64), where t is the message byte offset + f: BitCell // The ShortCell of the flag indicating the last block +} + +// An instrance of this struct describes the configuration of the circuit using R rows per round +#[derive(Clone)] +pub struct CompressionConfig { + pub rlc_table: RLCTable, // The RLC table + circuit_preparator: CircuitPreparator, // The CircuitPreparator instance + initial_gate: InitialGate, // The gate type of the InitialGate class gates + round_gate: RoundGate, // The gate type of the RoundGate class gates + final_gate: FinalGate, // The gate type of the FinalGate class gates + initial_input: InitialInput, // The relative places of the elements of the input for the gate type of the InitialGate class gates + tail_height: usize, // The tail height + unusable_rows: usize // The amount of the unusable rows +} + +// An instrance of this struct describes the height and input +// parameters of the circuit instance using R rows per round +#[derive(Default)] +pub struct CompressionCircuit { + k: u32, // The binary logarithm of the circuit's height + inputs: Vec, // The BLAKE2b compression function inputs + _marker: PhantomData // The marker used to specify the circuit's native field +} + +impl CompressionCircuit { + // Creates a CompressionCircuit for the specified binary logarithm + // of the circuit's height and BLAKE2b compression function inputs + pub fn new(k: u32, inputs: &[CompressionInput]) -> Self { + Self { k, inputs: inputs.to_vec(), _marker: PhantomData } + } + + // Computes the minimum value of the integer binary logarithm of the height + // of a circuit instance, whose input parameters are specified by "inputs" + pub fn k(inputs: &[CompressionInput]) -> u32 { + let config = Self::configure(&mut ConstraintSystem::::default()); + let rounds = inputs.into_iter().fold(0, |sum, input| sum + input.r) as usize; + // Using the formula ceil(log2(max((q + 2) * R + (n - 1) * (T + 8), 65536) + u)) + let mut usable = (rounds + 2) * R + (inputs.len() - 1) * ( config.tail_height + 8); + if usable < 65536 { usable = 65536; } + ((usable + config.unusable_rows) as f64).log2().ceil() as u32 + } + + // Computes the array describing the optimal amounts of rows per round for the specified input parameters + // of a circuit instance and all possible circuit heights. The k-th entry of this array is either the + // amount of rows per round, which corresponds to a possible circuit instance with the minimum number + // of columns among all circuit instances of height 2^k, which may be created for these input parameters, + // or None, iff a circuit instance of height 2^k is not possible for the given parameters + pub fn optimums(inputs: &[CompressionInput]) -> [Option; 32] { + let k = [ + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs), + CompressionCircuit::::k(inputs)]; + + array::from_fn(|i| k.iter().enumerate().filter(|e| *e.1 <= i as u32).map(|e| ROWS_PER_ROUND[e.0]).max()) + } +} + +impl Circuit for CompressionCircuit { + type Config = CompressionConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + assert!(ROWS_PER_ROUND.contains(&R), "Invalid number of rows per round!"); + + let fixed = |meta: &mut ConstraintSystem| meta.fixed_column(); + let advice = |meta: &mut ConstraintSystem| meta.advice_column(); + + let [allocated, allowed, septalookup] = array::from_fn(|_| fixed(meta)); + let xlookup: [Column; 3] = array::from_fn(|_| fixed(meta)); + + // Allocating the byte-column pairs required for storing the input and intermediate data of + // a RoundGate class gate in R sequential rows. Storing these data requires 56 byte-column chunks + // of height 8 and 32 byte-column cells. Thus, 56 * 8 + 32 cells or 240 cell pairs are required + let mut pairs = vec![]; + while pairs.len() * R < 240 { + pairs.push([advice(meta), advice(meta)]); + } + + // Allocating the xor column triplets required for storing the intermediate data of a RoundGate class + // gate in R sequential rows. Storing these data requires 32 xor column triplet chunks of height 8 + let mut xors = vec![]; + while xors.len() * R < 256 { + xors.push([advice(meta), advice(meta), advice(meta)]); + } + + // 14 bit columns are required: 10 collumns for 10 "binary flags" Combiselectors, a column for + // the intermediate binary results of the computations of the RoundGate class gates as well as + // for the flag indicating the last block in the case of the InitialGate class gates and 3 columns + // for the control selectors of the gates of the InitialGate, RoundGate and FinalGate classes + let mut binary = vec![]; + for _ in 0..14 { + binary.push(advice(meta)); + } + + let second = meta.advice_column_in(SecondPhase); + let [septenary, field] = array::from_fn(|_| advice(meta)); + let challenge = meta.challenge_usable_after(FirstPhase); + + let circuit_preparator = CircuitPreparator::configure(meta, allocated, + allowed, septalookup, xlookup, &binary, &[septenary], &pairs, &xors); + + let [initial_selector, round_selector, final_selector, bit] = array::from_fn(|_| binary.pop().unwrap()); + + let hi: [OctaChunk; 8] = create_chuncks(&pairs, 0, 0); + let ho: [OctaChunk; 8] = create_chuncks(&pairs, R as i32, 0); + let mi: [OctaChunk; 16] = create_chuncks(&pairs, 0, 8); + let mo: [OctaChunk; 16] = create_chuncks(&pairs, R as i32, 8); + let vi: [OctaChunk; 16] = create_chuncks(&pairs, 0, 24); + let vo: [OctaChunk; 16] = create_chuncks(&pairs, R as i32, 24); + let pi: [Combiselector; 10] = array::from_fn(|i| Combiselector::::new(allowed, binary[i], 0)); + let po: [Combiselector; 10] = array::from_fn(|i| Combiselector::::new(allowed, binary[i], R as i32)); + let left = [GeneralCell::::new(field, 0), GeneralCell::::new(field, R as i32)]; + let rlc = [GeneralCell::::new(second, 1), GeneralCell::::new(second, R as i32 + 1)]; + + let xchunks: [XChunk; 32] = create_xchunks(&xors, 0, 0); + let qwords: [OctaChunk; 16] = create_chuncks(&pairs, 0, 40); + let bytes: [OctaChunk; 4] = create_chuncks(&pairs, 0, 56); + let bytes: [ByteChunk; 32] = array::from_fn(|i| bytes[i / 8].subchunk((i % 8) as u8)); + let septets: [SeptaCell; 8] = array::from_fn(|i| SeptaCell::::new(septenary, i as i32)); + let bits: [BitCell; 8] = array::from_fn(|i| BitCell::::new(bit, i as i32)); + + let initial = Combiselector::::new(allowed, initial_selector, 0); + let round = Combiselector::::new(allowed, round_selector, -(R as i32)); + let selector = Combiselector::::new(allowed, round_selector, 0); + + let round_gate = RoundGate::::configure(meta, &[selector], &[initial], &[round], left, [&hi, &ho], + [&mi, &mo], [&vi, &vo], [&pi, &po], rlc, &bits, &septets, &bytes, &qwords, &xchunks); + + let f = BitCell::::new(bit, -8); + let t: [OctaChunk; 2] = create_chuncks(&pairs, -8, 0); + let top: [XChunk; 2] = create_xchunks(&xors, -8, 0); + + let selector = Combiselector::::new(allowed, initial_selector, 0); + + let initial_gate = InitialGate::::configure(meta, &[selector], + challenge, left[0], &hi, &mi, &t, f, &top, &vi, &pi, rlc[0]); + + let xv: &[XChunk; 8] = xchunks[0..8].try_into().unwrap(); + let xh: &[XChunk; 8] = xchunks[8..16].try_into().unwrap(); + let out = GeneralCell::::new(second, 0); + let selector = Combiselector::::new(allowed, final_selector, 0); + + let final_gate = FinalGate::::configure(meta, &[selector], challenge, + &[initial], &[round], left[0], &hi, &vi, &xh, &xv, [rlc[0], out]); + + // Computing the tail height. The input data of a FinalGate class gate, which + // are stored in the byte-column cells, have the same layout as the corresponding + // RoundGate class gate input data, storing of which requires 40 byte-column chunks + // of height 8. The data of a FinalGate class gate, which are stored in xor column + // triplet chunks, do not affect the gate's height (a hint: pairs.len() <= xors.len(), + // so 20.0 / pairs.len() > 16.0 / xors.len()) + let tail_height = 8 * (20.0 / pairs.len() as f64).ceil() as usize; + let unusable_rows = meta.blinding_factors() + 1; + + let initial_input = InitialInput { r: left[0], h: hi, m: mi, t, f }; + let rlc_table = RLCTable { allowed, selector: final_selector, rlc: second, challenge, _marker: PhantomData }; + + Self::Config { rlc_table, circuit_preparator, initial_gate, final_gate, round_gate, initial_input, tail_height, unusable_rows } + } + + fn synthesize(&self, config: Self::Config, mut layouter: impl Layouter) -> Result<(), Error> { + let initial = &config.initial_input; + let challenge = layouter.get_challenge(config.rlc_table.challenge); + + layouter.assign_region(|| "BLAKE2b compression function computation", + |mut region| { + config.circuit_preparator.assign(&mut region, self.k, R, R - 1, config.unusable_rows)?; + let mut row = R; + + for input in &self.inputs { + let mut left = F::from(input.r as u64); + + initial.r.assign(&mut region, row, Value::known(left))?; + initial.t[0].assign(&mut region, row, input.t as u64)?; + initial.t[1].assign(&mut region, row, (input.t >> 64) as u64)?; + initial.f.assign(&mut region, row, input.f as u8)?; + + let pairs = input.h.iter().chain(input.m.iter()).zip(initial.h.iter().chain(initial.m.iter())); + + for (value, chunk) in pairs { + chunk.assign(&mut region, row, *value)?; + } + + let (mut h, mut m) = (input.h, input.m); + let (mut rlc, mut v) = config.initial_gate.assign(&mut region, row, challenge, left, &h, &m, input.t, input.f)?; + + let mut round = 0; + for _ in 0..(input.r as usize) { + config.round_gate.assign(&mut region, row, rlc, &mut round, &mut left, &mut h, &mut m, &mut v)?; + row += R; + } + + // Checking the correctness of the computation + if cfg!(test) { + (h, rlc) = config.final_gate.assign(&mut region, row, challenge, rlc, &h, &v)?; + let (h_ex, rlc_ex) = RLCTable::compress_with_rlc(challenge, &input.h, &input.m, input.t, input.f, input.r); + let correctness = (h == h_ex) && (format!("{:?}", rlc) == format!("{:?}", rlc_ex)); + assert!(correctness, "Processing of a BLAKE2b compression function input was incorrect! This input is {:?}", input); + } else { + config.final_gate.assign(&mut region, row, challenge, rlc, &h, &v)?; + } + + // A gate of the InitialGate class is to be created next, and some of its + // data are to be stored in the 8 sequential rows before the assignment row + row += config.tail_height + 8; + } + + Ok(()) + } + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use hex::decode_to_slice; + use std::{ array, convert::TryInto }; + use halo2_proofs::{ halo2curves::bn256::Fr, dev::MockProver }; + use super::{ CompressionCircuit, CompressionInput, compression::compress }; + + // EIP-152 test vectors 4-7 describing the BLAKE2b compression function inputs with the corresponding outputs. The vectors 1-3 + // do not describe the correct inputs and are not representable in the format used in the tested library. The number of rounds + // for the vector 8 is 2^32 - 1, so the corresponding input cannot be processed by a circuit instance + const EIP152_VECTORS: [[&str; 2]; 4] = [ + ["0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b"], + ["0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"], + ["0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", + "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735"], + ["0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421"] + ]; + + // Creates the description of a BLAKE2b compression function input, which corresponds to the + // specified EIP-152 test vector, in accordance with the format used in the tested library + pub fn hex_to_input(v: &str) -> CompressionInput { + let mut buffer = [0u8; 213]; + decode_to_slice(v, &mut buffer).expect("Hex input must be correct!"); + assert!(buffer[212] <= 1, "Incorrect final block indicator flag!"); + + CompressionInput { + r: u32::from_be_bytes(buffer[0..4].try_into().unwrap()), + h: array::from_fn(|i| u64::from_le_bytes(buffer[4 + 8 * i..][..8].try_into().unwrap())), + m: array::from_fn(|i| u64::from_le_bytes(buffer[68 + 8 * i..][..8].try_into().unwrap())), + t: u128::from_le_bytes(buffer[196..212].try_into().unwrap()), + f: buffer[212] == 1 + } + } + + // Tests the circuit instance, which has height 2^17 and processes the BLAKE2b + // compression function inputs corresponding to the EIP-152 test vectors 4-7 + #[test] + fn circuit_check() { + let k = 17; + let vectors: Vec = EIP152_VECTORS.iter().map(|v| hex_to_input(v[0])).collect(); + let circuit = CompressionCircuit::::new(k, &vectors); + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + prover.assert_satisfied(); + } + + // Tests the implementation of the BLAKE2b compression function. The inputs + // with the corresponding outputs are described by the EIP-152 test vectors 4-7 + #[test] + fn compression_check() { + let mut expected = [0u8; 64]; + for vector in &EIP152_VECTORS { + let input = hex_to_input(vector[0]); + let result = compress(input.r, &input.h, &input.m, input.t, input.f); + decode_to_slice(vector[1], &mut expected).expect("Hex expected value must be correct!"); + let expected: [u64; 8] = array::from_fn(|i| u64::from_le_bytes(expected[8 * i..][..8].try_into().unwrap())); + assert_eq!(result, expected); + } + } + + // Tests the implementation of the "optimums" function, + // which associated with the CompressionCircuit struct + #[test] + fn optimums_check() { + let inputs = [ + CompressionInput { r: 200, h: [0; 8], m: [1; 16], t: 2, f: true }, + CompressionInput { r: 9000, h: [1; 8], m: [3; 16], t: 1, f: true }, + CompressionInput { r: 10000, h: [2; 8], m: [6; 16], t: 0, f: true }]; + let optimums = CompressionCircuit::::optimums(&inputs); + assert_eq!(optimums[0..18], [None; 18]); + assert_eq!(optimums[18..22], [Some(8), Some(24), Some(48), Some(88)]); + assert_eq!(optimums[22..32], [Some(128); 10]); + } +} + +#[cfg(test)] +mod benchmark { + use rand::SeedableRng; + use rand_xorshift::XorShiftRng; + use ark_std::{ start_timer, end_timer }; + use halo2_proofs:: { + plonk::{ create_proof, keygen_vk, keygen_pk, verify_proof }, + poly::kzg::{ + commitment::{ KZGCommitmentScheme, ParamsKZG }, + multiopen::{ ProverSHPLONK, VerifierSHPLONK }, + strategy::SingleStrategy + }, + poly::commitment::ParamsProver, + halo2curves::bn256::{ Bn256, Fr, G1Affine }, + transcript::{ Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer } + }; + use super::{ CompressionCircuit, CompressionInput }; + + // Binary logarithm of the height of a circuit instance + const K: u32 = 18; + // Number of rows per round + const R: usize = 8; + // BLAKE2b compression function inputs. Their total round number is the maximum one for + // the chosen k, number of rows per round and amount of the compression function inputs + const INPUTS:[CompressionInput; 5] = [ + CompressionInput { + r: 32600, + h: [534542, 235, 325, 235, 53252, 532452, 235324, 25423], + m: [5542, 23, 35, 35, 5252, 52452, 2324, 2523, 254, 35, 354, 235, 5532, 5235, 35, 525], + t: 1234, + f: true, + }, + CompressionInput { + r: 13, + h: [532, 235, 325, 235, 53252, 5324654452, 235324, 25423], + m: [55142, 23, 35, 31115, 5252, 52452, 2324, 2523, 254, 35, 354, 235, 5532, 5235, 35, 525], + t: 123784, + f: false, + }, + CompressionInput { + r: 90, + h: [532, 235, 325, 235, 53252, 0, 235324, 25423], + m: [55142, 0, 35, 31115, 5252, 52452, 2324, 2523, 254, 35, 354, 235, 5532, 0, 35, 525], + t: 0, + f: true, + }, + CompressionInput { + r: 0, + h: [53200, 235, 325, 235, 53252, 0, 235324, 25423], + m: [55142, 0, 35, 31115, 5252, 52452, 232400, 2523, 254, 35, 354, 235, 5532, 0, 350, 52500], + t: 5345435, + f: true + }, + CompressionInput { + r: 51, + h: [53200, 235, 325, 235, 53252, 0, 235324, 25423], + m: [55142, 0, 35, 31115, 5252, 52452, 232400, 2523, 254, 35, 354, 235, 5532, 0, 350, 52500], + t: 5345435, + f: true, + } + ]; + + // Runs the test bench for the BLAKE2b compression function circuit. In order to obtain correct results, + // it is recommended to run the tests by executing "cargo test --release -- --nocapture --test-threads=1" + #[test] + #[ignore] + fn bench() { + println!("The test bench for the BLAKE2b compression function circuit:"); + + let mut more = INPUTS; + more[0].r += 1; + assert!(CompressionCircuit::::k(&more) > K, + "The total round number must be the maximum one for the chosen k, number of rows per round and amount of the compression function inputs!"); + + let circuit = CompressionCircuit::::new(K, &INPUTS); + + let timer = start_timer!(|| "KZG setup"); + let mut random = XorShiftRng::from_seed([0xC; 16]); + let general_kzg_params = ParamsKZG::::setup(K, &mut random); + let verifier_kzg_params = general_kzg_params.verifier_params().clone(); + end_timer!(timer); + + let verifying_key = keygen_vk(&general_kzg_params, &circuit).expect("The verifying key must be generated successfully!"); + let proving_key = keygen_pk(&general_kzg_params, verifying_key, &circuit).expect("The proving key must be generated successfully!"); + let mut transcript = Blake2bWrite::, G1Affine, Challenge255<_>>::init(vec![]); + + let timer = start_timer!(|| "Proof generation"); + create_proof::, ProverSHPLONK<'_, Bn256>, _, _, _, _>(&general_kzg_params, + &proving_key, &[circuit], &[&[]], random, &mut transcript).expect("The proof must be generated successfully!"); + let transcripted = transcript.finalize(); + end_timer!(timer); + + let performance = 1000 * INPUTS.iter().fold(0, |sum, input| sum + input.r) as u128 / timer.time.elapsed().as_millis(); + println!("The prover's performace is {} rounds/second", performance); + + let timer = start_timer!(|| "Proof verification"); + let mut transcript = Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&transcripted[..]); + let strategy = SingleStrategy::new(&general_kzg_params); + verify_proof::, VerifierSHPLONK<'_, Bn256>, _, _, _>(&verifier_kzg_params, + proving_key.get_vk(), strategy, &[&[]], &mut transcript).expect("The proof must be verified successfully!"); + end_timer!(timer); + } +} diff --git a/zkevm-circuits/src/blake2b_circuit/src/lib.rs b/zkevm-circuits/src/blake2b_circuit/src/lib.rs new file mode 100644 index 0000000000..b52ad7fd46 --- /dev/null +++ b/zkevm-circuits/src/blake2b_circuit/src/lib.rs @@ -0,0 +1 @@ +mod blake2b; \ No newline at end of file