diff --git a/.gitignore b/.gitignore index d9937f42..27217da8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode /docs/benchmark_graphs/.venv minimal_zkVM.synctex.gz +crates/rec_aggregation/test_data .claude \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index cc523c9e..1cc03e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,11 +16,48 @@ name = "air" version = "0.1.0" dependencies = [ "backend", - "rand", + "rand 0.10.0", "tracing", "utils", ] +[[package]] +name = "alloy-primitives" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "serde", + "sha3 0.10.8", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -86,6 +123,201 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -95,6 +327,17 @@ dependencies = [ "critical-section", ] +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -118,12 +361,51 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[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 = "block-buffer" version = "0.10.4" @@ -133,12 +415,46 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -153,7 +469,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core", + "rand_core 0.10.0", ] [[package]] @@ -187,7 +503,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -211,6 +527,59 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -260,6 +629,24 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -270,14 +657,129 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -286,6 +788,25 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -298,18 +819,150 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[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" @@ -318,6 +971,30 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", ] [[package]] @@ -328,12 +1005,23 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", - "rand_core", + "r-efi 6.0.0", + "rand_core 0.10.0", "wasip2", "wasip3", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hash32" version = "0.2.1" @@ -343,13 +1031,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -357,6 +1051,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "heapless" @@ -366,9 +1065,9 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version", + "rustc_version 0.4.1", "serde", - "spin", + "spin 0.9.8", "stable_deref_trait", ] @@ -378,12 +1077,56 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -402,6 +1145,24 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -417,6 +1178,48 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "keccak-asm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -431,12 +1234,12 @@ dependencies = [ "backend", "clap", "lean_vm", - "rand", + "leansig_wrapper", + "rand 0.10.0", "rec_aggregation", "serde_json", "sub_protocols", "utils", - "xmss", ] [[package]] @@ -448,11 +1251,10 @@ dependencies = [ "lean_vm", "pest", "pest_derive", - "rand", + "rand 0.10.0", "sub_protocols", "tracing", "utils", - "xmss", ] [[package]] @@ -461,17 +1263,16 @@ version = "0.1.0" dependencies = [ "air", "backend", - "itertools", + "itertools 0.14.0", "lean_compiler", "lean_vm", "pest", "pest_derive", - "rand", + "rand 0.10.0", "rec_aggregation", "sub_protocols", "tracing", "utils", - "xmss", ] [[package]] @@ -480,13 +1281,66 @@ version = "0.1.0" dependencies = [ "air", "backend", - "itertools", + "itertools 0.14.0", + "leansig_wrapper", "pest", "pest_derive", - "rand", + "rand 0.10.0", + "serde", "tracing", "utils", - "xmss", +] + +[[package]] +name = "leansig" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leanSig?branch=devnet4#5cc7e37480362f94e86695428a9ceb9a96b66b97" +dependencies = [ + "dashmap", + "ethereum_ssz", + "num-bigint", + "num-traits", + "p3-baby-bear", + "p3-field", + "p3-koala-bear", + "p3-symmetric", + "rand 0.10.0", + "rayon", + "serde", + "sha3 0.10.8", + "thiserror", +] + +[[package]] +name = "leansig_fast_keygen" +version = "0.1.0" +source = "git+https://github.com/TomWambsgans/leanSig?branch=devnet4-fast-keygen#5b86867a4d3c1d4a8add840f70fa047ea1506188" +dependencies = [ + "dashmap", + "ethereum_ssz", + "num-bigint", + "num-traits", + "p3-baby-bear", + "p3-field", + "p3-koala-bear", + "p3-symmetric", + "rand 0.10.0", + "rayon", + "serde", + "sha3 0.10.8", + "thiserror", +] + +[[package]] +name = "leansig_wrapper" +version = "0.1.0" +dependencies = [ + "backend", + "ethereum_ssz", + "leansig", + "leansig_fast_keygen", + "p3-field", + "rand 0.10.0", ] [[package]] @@ -497,9 +1351,21 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "lock_api" @@ -564,11 +1430,11 @@ dependencies = [ name = "mt-field" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.14.0", "mt-utils", "num-bigint", "paste", - "rand", + "rand 0.10.0", "rayon", "serde", "tracing", @@ -578,12 +1444,12 @@ dependencies = [ name = "mt-koala-bear" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.14.0", "mt-field", "mt-utils", "num-bigint", "paste", - "rand", + "rand 0.10.0", "rayon", "serde", "tracing", @@ -593,11 +1459,11 @@ dependencies = [ name = "mt-poly" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.14.0", "mt-field", "mt-koala-bear", "mt-utils", - "rand", + "rand 0.10.0", "rayon", "serde", ] @@ -635,7 +1501,7 @@ dependencies = [ name = "mt-whir" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.14.0", "mt-fiat-shamir", "mt-field", "mt-koala-bear", @@ -643,7 +1509,7 @@ dependencies = [ "mt-sumcheck", "mt-symetric", "mt-utils", - "rand", + "rand 0.10.0", "rayon", "tracing", "tracing-forest", @@ -685,6 +1551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -699,6 +1566,215 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "p3-baby-bear" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-challenger", + "p3-field", + "p3-mds", + "p3-monty-31", + "p3-poseidon1", + "p3-poseidon2", + "p3-symmetric", + "rand 0.10.0", +] + +[[package]] +name = "p3-challenger" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-monty-31", + "p3-symmetric", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-maybe-rayon", + "p3-util", + "paste", + "rand 0.10.0", + "serde", + "tracing", +] + +[[package]] +name = "p3-koala-bear" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-challenger", + "p3-field", + "p3-mds", + "p3-monty-31", + "p3-poseidon1", + "p3-poseidon2", + "p3-symmetric", + "rand 0.10.0", +] + +[[package]] +name = "p3-matrix" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.10.0", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" + +[[package]] +name = "p3-mds" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-dft", + "p3-field", + "p3-symmetric", + "p3-util", + "rand 0.10.0", +] + +[[package]] +name = "p3-monty-31" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon1", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand 0.10.0", + "serde", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-poseidon1" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-field", + "p3-symmetric", + "rand 0.10.0", +] + +[[package]] +name = "p3-poseidon2" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "p3-field", + "p3-mds", + "p3-symmetric", + "p3-util", + "rand 0.10.0", +] + +[[package]] +name = "p3-symmetric" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-util", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.5.1" +source = "git+https://github.com/Plonky3/Plonky3.git#df6b530e6264f64a4ef7edce95418af6ff319898" +dependencies = [ + "serde", + "transpose", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "paste" version = "1.0.15" @@ -735,7 +1811,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -754,6 +1830,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "postcard" version = "1.1.3" @@ -767,6 +1853,15 @@ dependencies = [ "serde", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -774,49 +1869,185 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "unicode-ident", + "getrandom 0.2.17", ] [[package]] -name = "quote" -version = "1.0.45" +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "proc-macro2", + "getrandom 0.3.4", + "serde", ] [[package]] -name = "r-efi" -version = "6.0.0" +name = "rand_core" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] -name = "rand" -version = "0.10.0" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "chacha20", - "getrandom", - "rand_core", + "rand_core 0.9.5", ] [[package]] -name = "rand_core" -version = "0.10.0" +name = "rapidhash" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] [[package]] name = "rayon" @@ -847,14 +2078,24 @@ dependencies = [ "lean_compiler", "lean_prover", "lean_vm", + "leansig_wrapper", "lz4_flex", "postcard", - "rand", + "rand 0.10.0", "serde", + "sha3 0.11.0", "sub_protocols", "tracing", "utils", - "xmss", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", ] [[package]] @@ -874,13 +2115,119 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", ] [[package]] @@ -889,12 +2236,44 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -922,7 +2301,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -946,7 +2325,37 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.2", + "keccak 0.2.0", +] + +[[package]] +name = "sha3-asm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +dependencies = [ + "cc", + "cfg-if", ] [[package]] @@ -958,6 +2367,22 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -973,12 +2398,43 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strsim" version = "0.11.1" @@ -991,11 +2447,28 @@ version = "0.1.0" dependencies = [ "backend", "lean_vm", - "rand", + "rand 0.10.0", "tracing", "utils", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1007,6 +2480,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -1024,7 +2516,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1036,6 +2528,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.44" @@ -1055,7 +2577,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1110,6 +2632,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "twox-hash" version = "2.1.2" @@ -1128,12 +2660,36 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1151,7 +2707,7 @@ name = "utils" version = "0.1.0" dependencies = [ "backend", - "rand", + "rand 0.10.0", "tracing", "tracing-forest", "tracing-subscriber", @@ -1169,6 +2725,21 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1218,7 +2789,7 @@ dependencies = [ "bitflags", "hashbrown 0.15.5", "indexmap", - "semver", + "semver 1.0.27", ] [[package]] @@ -1258,6 +2829,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -1288,7 +2868,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1304,7 +2884,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1338,7 +2918,7 @@ dependencies = [ "id-arena", "indexmap", "log", - "semver", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -1347,15 +2927,52 @@ dependencies = [ ] [[package]] -name = "xmss" -version = "0.1.0" +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "backend", - "lz4_flex", - "postcard", - "rand", - "serde", - "utils", + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cd82d84a..4922efac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,11 +54,11 @@ wildcard_imports = "allow" air = { path = "crates/air" } utils = { path = "crates/utils" } lean_vm = { path = "crates/lean_vm" } -xmss = { path = "crates/xmss" } sub_protocols = { path = "crates/sub_protocols" } lean_compiler = { path = "crates/lean_compiler" } lean_prover = { path = "crates/lean_prover" } rec_aggregation = { path = "crates/rec_aggregation" } +leansig_wrapper = { path = "crates/leansig_wrapper" } backend = { path = "crates/backend" } # External @@ -75,11 +75,13 @@ tracing-subscriber = { version = "0.3.23", features = ["std", "env-filter"] } tracing-forest = { version = "0.3.0", features = ["ansi", "smallvec"] } postcard = { version = "1.1.3", features = ["alloc"] } lz4_flex = "0.13.0" +sha3 = "0.11.0" leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "devnet4" } leansig_fast_keygen = { git = "https://github.com/TomWambsgans/leanSig", branch = "devnet4-fast-keygen" } [features] prox-gaps-conjecture = ["rec_aggregation/prox-gaps-conjecture"] +test-config = ["rec_aggregation/test-config"] [dependencies] clap.workspace = true @@ -88,8 +90,8 @@ air.workspace = true rand.workspace = true sub_protocols.workspace = true utils.workspace = true +leansig_wrapper.workspace = true lean_vm.workspace = true -xmss.workspace = true backend.workspace = true [dev-dependencies] diff --git a/crates/backend/field/src/op_assign_macros.rs b/crates/backend/field/src/op_assign_macros.rs index 78ffeb4f..a78ace7b 100644 --- a/crates/backend/field/src/op_assign_macros.rs +++ b/crates/backend/field/src/op_assign_macros.rs @@ -266,7 +266,7 @@ macro_rules! impl_rng { impl$(<$param_name: $type_param>)? Distribution<$type$(<$param_name>)?> for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> $type$(<$param_name>)? { - $type(rand::RngExt::random(rng)) + $type(StandardUniform.sample(rng)) } } } diff --git a/crates/backend/koala-bear/src/benchmark_poseidons.rs b/crates/backend/koala-bear/src/benchmark_poseidons.rs index 66c6a5a0..f92b083e 100644 --- a/crates/backend/koala-bear/src/benchmark_poseidons.rs +++ b/crates/backend/koala-bear/src/benchmark_poseidons.rs @@ -5,6 +5,7 @@ use field::Field; use field::PackedValue; use field::PrimeCharacteristicRing; +use crate::default_koalabear_poseidon1_24; use crate::{KoalaBear, default_koalabear_poseidon1_16}; type FPacking = ::Packing; @@ -17,13 +18,17 @@ fn bench_poseidon() { let n = 1 << 23; let poseidon1_16 = default_koalabear_poseidon1_16(); + let poseidon1_24 = default_koalabear_poseidon1_24(); // warming let mut state_16: [FPacking; 16] = [FPacking::ZERO; 16]; + let mut state_24: [FPacking; 24] = [FPacking::ZERO; 24]; for _ in 0..1 << 15 { poseidon1_16.compress_in_place(&mut state_16); + poseidon1_24.compress_in_place(&mut state_24); } let _ = black_box(state_16); + let _ = black_box(state_24); let time = Instant::now(); for _ in 0..n / PACKING_WIDTH { @@ -36,4 +41,16 @@ fn bench_poseidon() { PACKING_WIDTH, (n as f64 / time_p1_simd.as_secs_f64() / 1_000_000.0) ); + + let time = Instant::now(); + for _ in 0..n / PACKING_WIDTH { + poseidon1_24.compress_in_place(&mut state_24); + } + let _ = black_box(state_24); + let time_p1_simd = time.elapsed(); + println!( + "Poseidon1 24 SIMD (width {}): {:.2}M hashes/s", + PACKING_WIDTH, + (n as f64 / time_p1_simd.as_secs_f64() / 1_000_000.0) + ); } diff --git a/crates/backend/koala-bear/src/lib.rs b/crates/backend/koala-bear/src/lib.rs index 959ed3ad..9fc32bed 100644 --- a/crates/backend/koala-bear/src/lib.rs +++ b/crates/backend/koala-bear/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; mod koala_bear; pub mod monty_31; mod poseidon1_koalabear_16; +mod poseidon1_koalabear_24; pub mod quintic_extension; pub mod symmetric; @@ -22,9 +23,13 @@ mod x86_64_avx2; #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] mod x86_64_avx512; -pub use koala_bear::*; pub use monty_31::*; + +pub use koala_bear::*; + pub use poseidon1_koalabear_16::*; +pub use poseidon1_koalabear_24::*; + pub use quintic_extension::*; #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] diff --git a/crates/backend/koala-bear/src/monty_31/aarch64_neon/poseidon_helpers.rs b/crates/backend/koala-bear/src/monty_31/aarch64_neon/poseidon_helpers.rs index 1a92e9d9..28ab8f8e 100644 --- a/crates/backend/koala-bear/src/monty_31/aarch64_neon/poseidon_helpers.rs +++ b/crates/backend/koala-bear/src/monty_31/aarch64_neon/poseidon_helpers.rs @@ -32,6 +32,29 @@ impl InternalLayer16 { } } +/// A specialized representation of the Poseidon state for a width of 24. +/// +/// Same split as `InternalLayer16` but for width 24. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct InternalLayer24 { + pub(crate) s0: PackedMontyField31Neon, + pub(crate) s_hi: [uint32x4_t; 23], +} + +impl InternalLayer24 { + #[inline] + pub(crate) unsafe fn to_packed_field_array(self) -> [PackedMontyField31Neon; 24] { + unsafe { transmute(self) } + } + + #[inline] + #[must_use] + pub(crate) fn from_packed_field_array(vector: [PackedMontyField31Neon; 24]) -> Self { + unsafe { transmute(vector) } + } +} + /// Converts a scalar constant into a packed NEON vector in "negative form" (`c - P`). #[inline(always)] pub(crate) fn convert_to_vec_neg_form_neon(input: i32) -> int32x4_t { diff --git a/crates/backend/koala-bear/src/poseidon1_koalabear_24.rs b/crates/backend/koala-bear/src/poseidon1_koalabear_24.rs new file mode 100644 index 00000000..d2668f43 --- /dev/null +++ b/crates/backend/koala-bear/src/poseidon1_koalabear_24.rs @@ -0,0 +1,1049 @@ +// Credits: Plonky3 (https://github.com/Plonky3/Plonky3) (MIT and Apache-2.0 licenses). + +use std::sync::OnceLock; + +use core::ops::Mul; + +use crate::KoalaBear; +use crate::symmetric::Permutation; +use field::{Algebra, Field, InjectiveMonomial, PrimeCharacteristicRing}; + +pub const POSEIDON1_WIDTH_24: usize = 24; +pub const POSEIDON1_HALF_FULL_ROUNDS_24: usize = 4; +pub const POSEIDON1_PARTIAL_ROUNDS_24: usize = 23; +pub const POSEIDON1_SBOX_DEGREE_24: u64 = 3; +const POSEIDON1_N_ROUNDS_24: usize = 2 * POSEIDON1_HALF_FULL_ROUNDS_24 + POSEIDON1_PARTIAL_ROUNDS_24; + +// ========================================================================= +// MDS circulant matrix (first column) +// ========================================================================= + +/// First column of the circulant MDS matrix for width 24. +/// +/// Derived from the plonky3 first-row data via first_row_to_first_col: +/// col[0] = row[0], col[i] = row[24 - i] for i = 1..23. +const MDS_CIRC_COL_24: [KoalaBear; 24] = KoalaBear::new_array([ + 0x2D0AAAAB, 0x0878A07F, 0x17E118F6, 0x5C7790FA, 0x0A6E572C, 0x6BE4DF69, 0x0524C7F2, 0x0C23DC41, 0x3C2C3DBE, + 0x1689DD98, 0x5D57AFC2, 0x2495A71D, 0x68FC71C8, 0x0360405D, 0x26D52A61, 0x3C0F5038, 0x77CDA9E2, 0x729601A7, + 0x18D6F3CA, 0x60703026, 0x6D91A8D5, 0x04ECBEB5, 0x17F5551D, 0x64850517, +]); + +// ========================================================================= +// Karatsuba convolution chain: 3 → 6 → 12 → 24 +// +// Ported from Plonky3 mds/src/karatsuba_convolution.rs (FieldConvolve). +// ========================================================================= + +#[inline(always)] +fn parity_dot, const N: usize>( + lhs: [R; N], + rhs: [KoalaBear; N], +) -> R { + let mut acc = lhs[0] * rhs[0]; + for i in 1..N { + acc += lhs[i] * rhs[i]; + } + acc +} + +#[inline(always)] +fn conv3>(lhs: [R; 3], rhs: [KoalaBear; 3], output: &mut [R]) { + output[0] = parity_dot(lhs, [rhs[0], rhs[2], rhs[1]]); + output[1] = parity_dot(lhs, [rhs[1], rhs[0], rhs[2]]); + output[2] = parity_dot(lhs, [rhs[2], rhs[1], rhs[0]]); +} + +#[inline(always)] +fn negacyclic_conv3>( + lhs: [R; 3], + rhs: [KoalaBear; 3], + output: &mut [R], +) { + output[0] = parity_dot(lhs, [rhs[0], -rhs[2], -rhs[1]]); + output[1] = parity_dot(lhs, [rhs[1], rhs[0], -rhs[2]]); + output[2] = parity_dot(lhs, [rhs[2], rhs[1], rhs[0]]); +} + +#[inline(always)] +fn conv_n_recursive, const N: usize, const H: usize>( + lhs: [R; N], + rhs: [KoalaBear; N], + output: &mut [R], + inner_conv: fn([R; H], [KoalaBear; H], &mut [R]), + inner_neg: fn([R; H], [KoalaBear; H], &mut [R]), +) { + let mut lp = [R::ZERO; H]; + let mut ln = [R::ZERO; H]; + let mut rp = [KoalaBear::ZERO; H]; + let mut rn = [KoalaBear::ZERO; H]; + for i in 0..H { + lp[i] = lhs[i] + lhs[i + H]; + ln[i] = lhs[i] - lhs[i + H]; + rp[i] = rhs[i] + rhs[i + H]; + rn[i] = rhs[i] - rhs[i + H]; + } + let (left, right) = output.split_at_mut(H); + inner_neg(ln, rn, left); + inner_conv(lp, rp, right); + for i in 0..H { + left[i] += right[i]; + left[i] = left[i].halve(); + right[i] -= left[i]; + } +} + +#[inline(always)] +fn negacyclic_conv_n_recursive< + R: PrimeCharacteristicRing + Mul, + const N: usize, + const H: usize, +>( + lhs: [R; N], + rhs: [KoalaBear; N], + output: &mut [R], + inner_neg: fn([R; H], [KoalaBear; H], &mut [R]), +) { + let mut le = [R::ZERO; H]; + let mut lo = [R::ZERO; H]; + let mut ls = [R::ZERO; H]; + let mut re = [KoalaBear::ZERO; H]; + let mut ro = [KoalaBear::ZERO; H]; + let mut rs = [KoalaBear::ZERO; H]; + for i in 0..H { + le[i] = lhs[2 * i]; + lo[i] = lhs[2 * i + 1]; + ls[i] = le[i] + lo[i]; + re[i] = rhs[2 * i]; + ro[i] = rhs[2 * i + 1]; + rs[i] = re[i] + ro[i]; + } + let mut es = [R::ZERO; H]; + let (left, right) = output.split_at_mut(H); + inner_neg(le, re, &mut es); + inner_neg(lo, ro, left); + inner_neg(ls, rs, right); + right[0] -= es[0] + left[0]; + es[0] -= left[H - 1]; + for i in 1..H { + right[i] -= es[i] + left[i]; + es[i] += left[i - 1]; + } + for i in 0..H { + output[2 * i] = es[i]; + output[2 * i + 1] = output[i + H]; + } +} + +#[inline(always)] +fn conv6>(lhs: [R; 6], rhs: [KoalaBear; 6], output: &mut [R]) { + conv_n_recursive(lhs, rhs, output, conv3::, negacyclic_conv3::); +} + +#[inline(always)] +fn negacyclic_conv6>( + lhs: [R; 6], + rhs: [KoalaBear; 6], + output: &mut [R], +) { + negacyclic_conv_n_recursive(lhs, rhs, output, negacyclic_conv3::); +} + +#[inline(always)] +fn conv12>( + lhs: [R; 12], + rhs: [KoalaBear; 12], + output: &mut [R], +) { + conv_n_recursive(lhs, rhs, output, conv6::, negacyclic_conv6::); +} + +#[inline(always)] +fn negacyclic_conv12>( + lhs: [R; 12], + rhs: [KoalaBear; 12], + output: &mut [R], +) { + negacyclic_conv_n_recursive(lhs, rhs, output, negacyclic_conv6::); +} + +/// Circulant MDS multiply via Karatsuba convolution: state = C * state. +#[inline(always)] +pub fn mds_circ_24>(state: &mut [R; 24]) { + let input = *state; + conv_n_recursive( + input, + MDS_CIRC_COL_24, + state.as_mut_slice(), + conv12::, + negacyclic_conv12::, + ); +} + +// ========================================================================= +// NEON-optimized Karatsuba using mixed_dot_product (fewer Montgomery reductions) +// +// On NEON, mixed_dot_product accumulates products in 64-bit precision and +// does a single Montgomery reduction. The rhs (MDS column) stays as scalar +// KoalaBear values throughout the recursion, avoiding redundant NEON +// add/sub operations and SIMD port contention. Only at the leaf level +// are scalars broadcast to NEON lanes for the multiply-accumulate. +// ========================================================================= + +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +mod neon_karatsuba { + use super::*; + type P = PackedKB; + type F = KoalaBear; + + #[inline(always)] + fn pdot(lhs: [P; N], rhs: [F; N]) -> P { + P::mixed_dot_product(&lhs, &rhs) + } + + #[inline(always)] + fn conv3(lhs: [P; 3], rhs: [F; 3], output: &mut [P]) { + output[0] = pdot(lhs, [rhs[0], rhs[2], rhs[1]]); + output[1] = pdot(lhs, [rhs[1], rhs[0], rhs[2]]); + output[2] = pdot(lhs, [rhs[2], rhs[1], rhs[0]]); + } + + #[inline(always)] + fn negacyclic_conv3(lhs: [P; 3], rhs: [F; 3], output: &mut [P]) { + output[0] = pdot(lhs, [rhs[0], -rhs[2], -rhs[1]]); + output[1] = pdot(lhs, [rhs[1], rhs[0], -rhs[2]]); + output[2] = pdot(lhs, [rhs[2], rhs[1], rhs[0]]); + } + + #[inline(always)] + fn conv_n( + lhs: [P; N], + rhs: [F; N], + output: &mut [P], + inner_conv: fn([P; H], [F; H], &mut [P]), + inner_neg: fn([P; H], [F; H], &mut [P]), + ) { + let mut lp = [P::ZERO; H]; + let mut ln = [P::ZERO; H]; + let mut rp = [F::ZERO; H]; + let mut rn = [F::ZERO; H]; + for i in 0..H { + lp[i] = lhs[i] + lhs[i + H]; + ln[i] = lhs[i] - lhs[i + H]; + rp[i] = rhs[i] + rhs[i + H]; + rn[i] = rhs[i] - rhs[i + H]; + } + let (left, right) = output.split_at_mut(H); + inner_neg(ln, rn, left); + inner_conv(lp, rp, right); + for i in 0..H { + left[i] += right[i]; + left[i] = left[i].halve(); + right[i] -= left[i]; + } + } + + #[inline(always)] + fn negacyclic_conv_n( + lhs: [P; N], + rhs: [F; N], + output: &mut [P], + inner_neg: fn([P; H], [F; H], &mut [P]), + ) { + let mut le = [P::ZERO; H]; + let mut lo = [P::ZERO; H]; + let mut ls = [P::ZERO; H]; + let mut re = [F::ZERO; H]; + let mut ro = [F::ZERO; H]; + let mut rs = [F::ZERO; H]; + for i in 0..H { + le[i] = lhs[2 * i]; + lo[i] = lhs[2 * i + 1]; + ls[i] = le[i] + lo[i]; + re[i] = rhs[2 * i]; + ro[i] = rhs[2 * i + 1]; + rs[i] = re[i] + ro[i]; + } + let mut es = [P::ZERO; H]; + let (left, right) = output.split_at_mut(H); + inner_neg(le, re, &mut es); + inner_neg(lo, ro, left); + inner_neg(ls, rs, right); + right[0] -= es[0] + left[0]; + es[0] -= left[H - 1]; + for i in 1..H { + right[i] -= es[i] + left[i]; + es[i] += left[i - 1]; + } + for i in 0..H { + output[2 * i] = es[i]; + output[2 * i + 1] = output[i + H]; + } + } + + #[inline(always)] + fn conv6(lhs: [P; 6], rhs: [F; 6], output: &mut [P]) { + conv_n(lhs, rhs, output, conv3, negacyclic_conv3); + } + + #[inline(always)] + fn negacyclic_conv6(lhs: [P; 6], rhs: [F; 6], output: &mut [P]) { + negacyclic_conv_n(lhs, rhs, output, negacyclic_conv3); + } + + #[inline(always)] + fn conv12(lhs: [P; 12], rhs: [F; 12], output: &mut [P]) { + conv_n(lhs, rhs, output, conv6, negacyclic_conv6); + } + + #[inline(always)] + fn negacyclic_conv12(lhs: [P; 12], rhs: [F; 12], output: &mut [P]) { + negacyclic_conv_n(lhs, rhs, output, negacyclic_conv6); + } + + #[inline(always)] + pub(super) fn mds_circ_24_neon(state: &mut [P; 24], col: &[F; 24]) { + let input = *state; + conv_n(input, *col, state.as_mut_slice(), conv12, negacyclic_conv12); + } +} + +// ========================================================================= +// Sparse matrix decomposition helpers (precomputation only) +// ========================================================================= + +type F24 = [KoalaBear; 24]; +type M24 = [[KoalaBear; 24]; 24]; + +fn matrix_mul_24(a: &M24, b: &M24) -> M24 { + core::array::from_fn(|i| { + core::array::from_fn(|j| { + let mut s = KoalaBear::ZERO; + for k in 0..24 { + s += a[i][k] * b[k][j]; + } + s + }) + }) +} + +fn matrix_vec_mul_24(m: &M24, v: &F24) -> F24 { + core::array::from_fn(|i| { + let mut s = KoalaBear::ZERO; + for j in 0..24 { + s += m[i][j] * v[j]; + } + s + }) +} + +fn matrix_transpose_24(m: &M24) -> M24 { + core::array::from_fn(|i| core::array::from_fn(|j| m[j][i])) +} + +fn matrix_inverse_24(m: &M24) -> M24 { + let mut aug: M24 = *m; + let mut inv: M24 = + core::array::from_fn(|i| core::array::from_fn(|j| if i == j { KoalaBear::ONE } else { KoalaBear::ZERO })); + for col in 0..24 { + let pivot_row = (col..24) + .find(|&r| aug[r][col] != KoalaBear::ZERO) + .expect("Matrix is singular"); + if pivot_row != col { + aug.swap(col, pivot_row); + inv.swap(col, pivot_row); + } + let pivot_inv = aug[col][col].inverse(); + for j in 0..24 { + aug[col][j] *= pivot_inv; + inv[col][j] *= pivot_inv; + } + for i in 0..24 { + if i == col { + continue; + } + let factor = aug[i][col]; + if factor == KoalaBear::ZERO { + continue; + } + let aug_col_row = aug[col]; + let inv_col_row = inv[col]; + for j in 0..24 { + aug[i][j] -= factor * aug_col_row[j]; + inv[i][j] -= factor * inv_col_row[j]; + } + } + } + inv +} + +/// Inverse of the 23x23 bottom-right submatrix (Vec-based). +fn submatrix_inverse_23(m: &M24) -> Vec> { + let n = 23; + let mut sub: Vec> = (0..n).map(|i| (0..n).map(|j| m[i + 1][j + 1]).collect()).collect(); + let mut inv: Vec> = (0..n) + .map(|i| { + let mut row = vec![KoalaBear::ZERO; n]; + row[i] = KoalaBear::ONE; + row + }) + .collect(); + for col in 0..n { + let pivot_row = (col..n) + .find(|&r| sub[r][col] != KoalaBear::ZERO) + .expect("Submatrix is singular"); + if pivot_row != col { + sub.swap(col, pivot_row); + inv.swap(col, pivot_row); + } + let pivot_inv = sub[col][col].inverse(); + for j in 0..n { + sub[col][j] *= pivot_inv; + inv[col][j] *= pivot_inv; + } + for i in 0..n { + if i == col { + continue; + } + let factor = sub[i][col]; + if factor == KoalaBear::ZERO { + continue; + } + let sub_col_row: Vec = sub[col].clone(); + let inv_col_row: Vec = inv[col].clone(); + for j in 0..n { + sub[i][j] -= factor * sub_col_row[j]; + inv[i][j] -= factor * inv_col_row[j]; + } + } + } + inv +} + +/// Factor the dense MDS matrix into sparse matrices for partial rounds. +/// Returns (m_i, v_collection, w_hat_collection) in forward application order. +fn compute_equivalent_matrices_24(mds: &M24) -> (M24, Vec, Vec) { + let rounds_p = POSEIDON1_PARTIAL_ROUNDS_24; + let mut w_hat_collection: Vec = Vec::with_capacity(rounds_p); + let mut v_collection: Vec = Vec::with_capacity(rounds_p); + + let mds_t = matrix_transpose_24(mds); + let mut m_mul = mds_t; + let mut m_i = [[KoalaBear::ZERO; 24]; 24]; + + for _ in 0..rounds_p { + let v_arr: F24 = core::array::from_fn(|j| if j < 23 { m_mul[0][j + 1] } else { KoalaBear::ZERO }); + let w: Vec = (1..24).map(|i| m_mul[i][0]).collect(); + let m_hat_inv = submatrix_inverse_23(&m_mul); + let w_hat_arr: F24 = core::array::from_fn(|i| { + if i < 23 { + let mut s = KoalaBear::ZERO; + for k in 0..23 { + s += m_hat_inv[i][k] * w[k]; + } + s + } else { + KoalaBear::ZERO + } + }); + v_collection.push(v_arr); + w_hat_collection.push(w_hat_arr); + + m_i = m_mul; + m_i[0][0] = KoalaBear::ONE; + for row in m_i.iter_mut().skip(1) { + row[0] = KoalaBear::ZERO; + } + for elem in m_i[0].iter_mut().skip(1) { + *elem = KoalaBear::ZERO; + } + m_mul = matrix_mul_24(&mds_t, &m_i); + } + + let m_i_returned = matrix_transpose_24(&m_i); + v_collection.reverse(); + w_hat_collection.reverse(); + (m_i_returned, v_collection, w_hat_collection) +} + +/// Compress round constants via backward substitution through MDS^{-1}. +fn equivalent_round_constants_24(partial_rc: &[F24], mds_inv: &M24) -> (F24, Vec) { + let rounds_p = partial_rc.len(); + let mut opt_partial_rc = vec![KoalaBear::ZERO; rounds_p]; + let mut tmp = partial_rc[rounds_p - 1]; + for i in (0..rounds_p - 1).rev() { + let inv_cip = matrix_vec_mul_24(mds_inv, &tmp); + opt_partial_rc[i + 1] = inv_cip[0]; + tmp = partial_rc[i]; + for j in 1..24 { + tmp[j] += inv_cip[j]; + } + } + let first_round_constants = tmp; + let scalar_constants = opt_partial_rc[1..].to_vec(); + (first_round_constants, scalar_constants) +} + +// ========================================================================= +// NEON types (conditional) +// ========================================================================= + +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +type FP = crate::KoalaBearParameters; +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +type PackedKB = crate::PackedKoalaBearNeon; + +// ========================================================================= +// Precomputed constants +// ========================================================================= + +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +struct NeonPrecomputed24 { + /// Initial full round constants in negative NEON form (first 3 rounds). + packed_initial_rc: [[core::arch::aarch64::int32x4_t; 24]; POSEIDON1_HALF_FULL_ROUNDS_24 - 1], + /// Last initial round constant in negative NEON form. + packed_last_initial_rc: [core::arch::aarch64::int32x4_t; 24], + /// Terminal full round constants in negative NEON form. + packed_terminal_rc: [[core::arch::aarch64::int32x4_t; 24]; POSEIDON1_HALF_FULL_ROUNDS_24], + /// MDS circulant column for NEON Karatsuba (scalar, not packed). + /// Kept as scalar so the Karatsuba recursion avoids redundant NEON + /// operations on the constant side, reducing SIMD port contention. + mds_col: [KoalaBear; 24], + /// Fused matrix: m_i * MDS. + packed_fused_mi_mds: [[PackedKB; 24]; 24], + /// Fused bias: m_i * first_round_constants. + packed_fused_bias: [PackedKB; 24], + /// Pre-packed sparse first rows. + packed_sparse_first_row: [[PackedKB; 24]; POSEIDON1_PARTIAL_ROUNDS_24], + /// Pre-packed v vectors. + packed_sparse_v: [[PackedKB; 24]; POSEIDON1_PARTIAL_ROUNDS_24], + /// Pre-packed scalar round constants for partial rounds 0..RP-2. + packed_round_constants: [PackedKB; POSEIDON1_PARTIAL_ROUNDS_24 - 1], +} + +#[cfg(all(target_arch = "aarch64", target_feature = "neon"))] +impl std::fmt::Debug for NeonPrecomputed24 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NeonPrecomputed24").finish_non_exhaustive() + } +} + +#[derive(Debug)] +struct Precomputed24 { + /// First round constant vector (full width), added once before m_i multiply. + sparse_first_round_constants: F24, + /// Dense transition matrix m_i, applied once before the partial round loop. + sparse_m_i: M24, + /// Per-round full first row: [mds_0_0, ŵ[0], ..., ŵ[22]]. + sparse_first_row: Vec, + /// Per-round first-column vectors (excluding [0,0]). + sparse_v: Vec, + /// Scalar constants for partial rounds 0..RP-2. + sparse_round_constants: Vec, + + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + neon: NeonPrecomputed24, +} + +static PRECOMPUTED_24: OnceLock = OnceLock::new(); + +fn precomputed_24() -> &'static Precomputed24 { + PRECOMPUTED_24.get_or_init(|| { + let mds: M24 = core::array::from_fn(|i| core::array::from_fn(|j| MDS_CIRC_COL_24[(24 + i - j) % 24])); + + let partial_rc = &POSEIDON1_RC_24 + [POSEIDON1_HALF_FULL_ROUNDS_24..POSEIDON1_HALF_FULL_ROUNDS_24 + POSEIDON1_PARTIAL_ROUNDS_24]; + + // Sparse matrix decomposition. + let mds_inv = matrix_inverse_24(&mds); + let (first_round_constants, scalar_round_constants) = equivalent_round_constants_24(partial_rc, &mds_inv); + let (m_i, sparse_v, sparse_w_hat) = compute_equivalent_matrices_24(&mds); + + // Pre-assemble full first rows: [mds_0_0, ŵ[0], ..., ŵ[22]]. + let mds_0_0 = mds[0][0]; + let sparse_first_row: Vec = sparse_w_hat + .iter() + .map(|w| core::array::from_fn(|i| if i == 0 { mds_0_0 } else { w[i - 1] })) + .collect(); + + // --- NEON pre-packed constants --- + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + let neon = { + use crate::PackedMontyField31Neon; + use crate::convert_to_vec_neg_form_neon; + + let pack = |c: KoalaBear| PackedMontyField31Neon::::from(c); + let neg_form = |c: KoalaBear| convert_to_vec_neg_form_neon::(c.value as i32); + + // Initial full round constants (first 3; 4th is fused). + let init_rc = poseidon1_24_initial_constants(); + let packed_initial_rc: [[core::arch::aarch64::int32x4_t; 24]; POSEIDON1_HALF_FULL_ROUNDS_24 - 1] = + core::array::from_fn(|r| init_rc[r].map(neg_form)); + let packed_last_initial_rc = init_rc[POSEIDON1_HALF_FULL_ROUNDS_24 - 1].map(neg_form); + + // Terminal full round constants. + let term_rc = poseidon1_24_final_constants(); + let packed_terminal_rc: [[core::arch::aarch64::int32x4_t; 24]; POSEIDON1_HALF_FULL_ROUNDS_24] = + core::array::from_fn(|r| term_rc[r].map(neg_form)); + + // Pre-packed sparse constants. + let packed_sparse_first_row: [[PackedKB; 24]; POSEIDON1_PARTIAL_ROUNDS_24] = + core::array::from_fn(|r| sparse_first_row[r].map(pack)); + let packed_sparse_v: [[PackedKB; 24]; POSEIDON1_PARTIAL_ROUNDS_24] = + core::array::from_fn(|r| sparse_v[r].map(pack)); + let packed_round_constants: [PackedKB; POSEIDON1_PARTIAL_ROUNDS_24 - 1] = + core::array::from_fn(|r| pack(scalar_round_constants[r])); + + // MDS column for NEON Karatsuba (scalar, not packed). + let mds_col: [KoalaBear; 24] = MDS_CIRC_COL_24; + + // Fused matrix: m_i * MDS. + let fused_mi_mds = matrix_mul_24(&m_i, &mds); + let packed_fused_mi_mds: [[PackedKB; 24]; 24] = core::array::from_fn(|i| fused_mi_mds[i].map(pack)); + + // Fused bias: m_i * first_round_constants. + let fused_bias = matrix_vec_mul_24(&m_i, &first_round_constants); + let packed_fused_bias: [PackedKB; 24] = fused_bias.map(pack); + + NeonPrecomputed24 { + packed_initial_rc, + packed_last_initial_rc, + packed_terminal_rc, + mds_col, + packed_fused_mi_mds, + packed_fused_bias, + packed_sparse_first_row, + packed_sparse_v, + packed_round_constants, + } + }; + + Precomputed24 { + sparse_first_round_constants: first_round_constants, + sparse_m_i: m_i, + sparse_first_row, + sparse_v, + sparse_round_constants: scalar_round_constants, + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + neon, + } + }) +} + +const POSEIDON1_RC_24: [[KoalaBear; 24]; POSEIDON1_N_ROUNDS_24] = KoalaBear::new_2d_array([ + // Initial full rounds (4) + [ + 0x1d0939dc, 0x6d050f8d, 0x628058ad, 0x2681385d, 0x3e3c62be, 0x032cfad8, 0x5a91ba3c, 0x015a56e6, 0x696b889c, + 0x0dbcd780, 0x5881b5c9, 0x2a076f2e, 0x55393055, 0x6513a085, 0x547ac78f, 0x4281c5b8, 0x3e7a3f6c, 0x34562c19, + 0x2c04e679, 0x0ed78234, 0x5f7a1aa9, 0x0177640e, 0x0ea4f8d1, 0x15be7692, + ], + [ + 0x6eafdd62, 0x71a572c6, 0x72416f0a, 0x31ce1ad3, 0x2136a0cf, 0x1507c0eb, 0x1eb6e07a, 0x3a0ccf7b, 0x38e4bf31, + 0x44128286, 0x6b05e976, 0x244a9b92, 0x6e4b32a8, 0x78ee2496, 0x4761115b, 0x3d3a7077, 0x75d3c670, 0x396a2475, + 0x26dd00b4, 0x7df50f59, 0x0cb922df, 0x0568b190, 0x5bd3fcd6, 0x1351f58e, + ], + [ + 0x52191b5f, 0x119171b8, 0x1e8bb727, 0x27d21f26, 0x36146613, 0x1ee817a2, 0x71abe84e, 0x44b88070, 0x5dc04410, + 0x2aeaa2f6, 0x2b7bb311, 0x6906884d, 0x0522e053, 0x0c45a214, 0x1b016998, 0x479b1052, 0x3acc89be, 0x0776021a, + 0x7a34a1f5, 0x70f87911, 0x2caf9d9e, 0x026aff1b, 0x2c42468e, 0x67726b45, + ], + [ + 0x09b6f53c, 0x73d76589, 0x5793eeb0, 0x29e720f3, 0x75fc8bdf, 0x4c2fae0e, 0x20b41db3, 0x7e491510, 0x2cadef18, + 0x57fc24d6, 0x4d1ade4a, 0x36bf8e3c, 0x3511b63c, 0x64d8476f, 0x732ba706, 0x46634978, 0x0521c17c, 0x5ee69212, + 0x3559cba9, 0x2b33df89, 0x653538d6, 0x5fde8344, 0x4091605d, 0x2933bdde, + ], + // Partial rounds (23) + [ + 0x1395d4ca, 0x5dbac049, 0x51fc2727, 0x13407399, 0x39ac6953, 0x45e8726c, 0x75a7311c, 0x599f82c9, 0x702cf13b, + 0x026b8955, 0x44e09bbc, 0x2211207f, 0x5128b4e3, 0x591c41af, 0x674f5c68, 0x3981d0d3, 0x2d82f898, 0x707cd267, + 0x3b4cca45, 0x2ad0dc3c, 0x0cb79b37, 0x23f2f4e8, 0x3de4e739, 0x7d232359, + ], + [ + 0x389d82f9, 0x259b2e6c, 0x45a94def, 0x0d497380, 0x5b049135, 0x3c268399, 0x78feb2f9, 0x300a3eec, 0x505165bb, + 0x20300973, 0x2327c081, 0x1a45a2f4, 0x5b32ea2e, 0x2d5d1a70, 0x053e613e, 0x5433e39f, 0x495529f0, 0x1eaa1aa9, + 0x578f572a, 0x698ede71, 0x5a0f9dba, 0x398a2e96, 0x0c7b2925, 0x2e6b9564, + ], + [ + 0x026b00de, 0x7644c1e9, 0x5c23d0bd, 0x3470b5ef, 0x6013cf3a, 0x48747288, 0x13b7a543, 0x3eaebd44, 0x0004e60c, + 0x1e8363a2, 0x2343259a, 0x69da0c2a, 0x06e3e4c4, 0x1095018e, 0x0deea348, 0x1f4c5513, 0x4f9a3a98, 0x3179112b, + 0x524abb1f, 0x21615ba2, 0x23ab4065, 0x1202a1d1, 0x21d25b83, 0x6ed17c2f, + ], + [ + 0x391e6b09, 0x5e4ed894, 0x6a2f58f2, 0x5d980d70, 0x3fa48c5e, 0x1f6366f7, 0x63540f5f, 0x6a8235ed, 0x14c12a78, + 0x6edde1c9, 0x58ce1c22, 0x718588bb, 0x334313ad, 0x7478dbc7, 0x647ad52f, 0x39e82049, 0x6fee146a, 0x082c2f24, + 0x1f093015, 0x30173c18, 0x53f70c0d, 0x6028ab0c, 0x2f47a1ee, 0x26a6780e, + ], + [ + 0x3540bc83, 0x1812b49f, 0x5149c827, 0x631dd925, 0x001f2dea, 0x7dc05194, 0x3789672e, 0x7cabf72e, 0x242dbe2f, + 0x0b07a51d, 0x38653650, 0x50785c4e, 0x60e8a7e0, 0x07464338, 0x3482d6e1, 0x08a69f1e, 0x3f2aff24, 0x5814c30d, + 0x13fecab2, 0x61cb291a, 0x68c8226f, 0x5c757eea, 0x289b4e1e, 0x0198d9b3, + ], + [ + 0x070a92e6, 0x2f1b6cb3, 0x535008bb, 0x35af339a, 0x7a38e92c, 0x4ff71b5c, 0x3b193aba, 0x34d12a1e, 0x17e94240, + 0x2ec214dc, 0x43e09385, 0x7d546918, 0x71af9dfd, 0x761a21bb, 0x43fdc986, 0x05dda714, 0x2d0e78b5, 0x1fcd387b, + 0x76e10a76, 0x28a112d5, 0x1a7bd787, 0x40190de2, 0x2e27906a, 0x2033954e, + ], + [ + 0x20afd2c8, 0x71b5ecb2, 0x57828fb3, 0x222851d8, 0x732df0e9, 0x73f48435, 0x7e63ea98, 0x058be348, 0x229e7a5f, + 0x04576a2f, 0x29939f10, 0x7afd830a, 0x5d6dd961, 0x0eb65d94, 0x39da2b79, 0x36bce8ba, 0x5f53a7d4, 0x383b1cd2, + 0x1fdc3c5f, 0x7d9ca544, 0x77480711, 0x36c51a1a, 0x009ea59b, 0x731b17fd, + ], + [ + 0x201359bd, 0x22bf6499, 0x610f1a29, 0x3c73aa45, 0x6a092599, 0x1c7cb703, 0x79533459, 0x7ef62d86, 0x5ab925ab, + 0x67722ab1, 0x33ca4cff, 0x007f7dce, 0x0eeac41e, 0x4724bea7, 0x45eaf64f, 0x21a6c90f, 0x094b4150, 0x0d942630, + 0x18712c30, 0x3a470338, 0x6eba7720, 0x487827c8, 0x77013a6d, 0x4ad07390, + ], + [ + 0x57d802ea, 0x720f5fd4, 0x5b8a5357, 0x3649db1f, 0x35ea476a, 0x4c6589f5, 0x02c9f31f, 0x16d04670, 0x62d74b20, + 0x1de813cc, 0x189966ed, 0x527add06, 0x1704f5af, 0x000f1703, 0x00152a1f, 0x2f49a365, 0x40ee4288, 0x0ab86260, + 0x080c8576, 0x36c6cc05, 0x0ab9346f, 0x62aa3ec8, 0x51109797, 0x0feb1585, + ], + [ + 0x04700024, 0x01dee723, 0x5cd4aaa8, 0x1fe43ce5, 0x25c31267, 0x58512b48, 0x54147539, 0x4e340ab9, 0x563fbaeb, + 0x60c8353a, 0x65a12d49, 0x6c499fb2, 0x7ea07556, 0x396e2bbb, 0x31a318f1, 0x11f855ae, 0x6edffb87, 0x59977042, + 0x6ec5fa94, 0x75b4f690, 0x44b6fc61, 0x02a8bed8, 0x4c88c824, 0x08e31432, + ], + [ + 0x09a4c09f, 0x4796b47d, 0x215b7e75, 0x0c639599, 0x0d93dd4c, 0x2fac41de, 0x4f46dadd, 0x03905848, 0x2b1c39c1, + 0x25fff199, 0x38621f7b, 0x69e59315, 0x1874c308, 0x024a3959, 0x2bae1f12, 0x3c200626, 0x6ba5d369, 0x2fe9b97e, + 0x674cc08e, 0x2cbb9657, 0x550e56c2, 0x5b80e0ec, 0x6549ccff, 0x54e3e61a, + ], + [ + 0x0fa689e3, 0x2c534848, 0x1eb24382, 0x61b959b5, 0x4d5f001e, 0x003a95cd, 0x1edd4507, 0x621e895d, 0x7dc6e599, + 0x0fbc2771, 0x152d0879, 0x77801087, 0x6a2dd731, 0x3644aba2, 0x2e43a814, 0x12ff923f, 0x01cfe2c9, 0x35f8a572, + 0x5789fd35, 0x16f39e7a, 0x7c0ca31c, 0x01016283, 0x2c9dcd96, 0x5d3c6f4e, + ], + [ + 0x0058a186, 0x16354360, 0x502a262b, 0x2b56f93e, 0x0bc41ecb, 0x33c83e8b, 0x21968fc3, 0x6364490c, 0x16a45aa5, + 0x286d873f, 0x2be17254, 0x381fbc06, 0x0df309aa, 0x15d48b84, 0x0fb2c5dd, 0x7c440d21, 0x74908f00, 0x75520624, + 0x7e58f065, 0x141e1e41, 0x6582f4ae, 0x2c4479e5, 0x7a09fff8, 0x1baa979f, + ], + [ + 0x45ab39bd, 0x774f78bc, 0x3c5f9aa2, 0x115d9dc9, 0x4b1546d7, 0x196c1a55, 0x6a88fb5e, 0x4c1ca910, 0x34869067, + 0x2662dcbb, 0x0a4625d4, 0x25b121c8, 0x1a50ccd2, 0x490ea316, 0x42556ffa, 0x6b5e4f88, 0x329faf33, 0x54f39a88, + 0x3b411e09, 0x6950ae8e, 0x310a912c, 0x63bddcba, 0x347977c0, 0x52831335, + ], + [ + 0x41f32fc6, 0x67dd5acb, 0x41ae544e, 0x1d83750a, 0x4bb58d20, 0x2f5496ee, 0x353819ec, 0x412ee425, 0x1bfd2747, + 0x32a14699, 0x2f7be906, 0x38afda41, 0x5b1e6316, 0x7b810b48, 0x6aebb30d, 0x55d94f89, 0x69db4833, 0x3a6ecb6c, + 0x50e7d206, 0x148a4b69, 0x1ac5548d, 0x40019cf9, 0x1e566f2a, 0x0998a950, + ], + [ + 0x5bc887f0, 0x73fbbd18, 0x341e05a8, 0x7d0597d5, 0x582308d9, 0x7a98addf, 0x0938b854, 0x544bf13d, 0x50090144, + 0x13baf374, 0x1896a8d5, 0x75ea7475, 0x23510dd8, 0x72c93bcc, 0x1c41410e, 0x4b72d5f9, 0x103ccc4e, 0x3896bef2, + 0x2c5e0b1c, 0x1e2096de, 0x15594d47, 0x04e035ce, 0x2785d1b1, 0x795bc87d, + ], + [ + 0x373fecbf, 0x0b18c3a0, 0x6516874a, 0x2b567be9, 0x5a2a3d1b, 0x74d99c04, 0x437de605, 0x047df991, 0x322faad4, + 0x2ef2f76f, 0x5f9e7278, 0x62740235, 0x18c1e8c2, 0x0691e203, 0x3324646d, 0x59542c9f, 0x32433d0d, 0x42c17492, + 0x45ac808a, 0x685394e0, 0x316f7193, 0x5ea108a0, 0x6bb3f12f, 0x232f8865, + ], + [ + 0x7c162b62, 0x52aa9e45, 0x1b69f8db, 0x3ec35206, 0x1ef086dd, 0x34d7a5e3, 0x33aeea57, 0x03565cc8, 0x5bc5fd47, + 0x47adc343, 0x1d5857a2, 0x5e7ece76, 0x0239fba3, 0x58bdead4, 0x41671aef, 0x3c8a9189, 0x7342ed52, 0x19871456, + 0x573a02c8, 0x2ec8ad55, 0x09c4a997, 0x34b9b63a, 0x226da984, 0x6b31d16e, + ], + [ + 0x458384d2, 0x353911e1, 0x4cfd1256, 0x163c23af, 0x7609c5e0, 0x76596c08, 0x087adac7, 0x4fd4b62c, 0x3692a037, + 0x51c54b62, 0x133daf4d, 0x0c76f623, 0x387d21f3, 0x6034abe5, 0x7c982e2b, 0x63a266b4, 0x4f2b17b8, 0x0bd62f1d, + 0x70e37a7c, 0x4f162da9, 0x38f0e527, 0x6ce798d7, 0x6c74250b, 0x606f2fad, + ], + [ + 0x212b041d, 0x6724fd32, 0x73aaf9af, 0x3ae9b76b, 0x014fe151, 0x37687943, 0x36bb7786, 0x01da85ef, 0x28c618ae, + 0x36706580, 0x3f5f610d, 0x2e0b9391, 0x5750e38d, 0x00b48d71, 0x0f1f1d7a, 0x7107c415, 0x35c1e287, 0x26ccce2f, + 0x4e29277a, 0x1580ee9d, 0x18136f74, 0x530f32ad, 0x5a19b05d, 0x3d38b320, + ], + [ + 0x6a3bf1e4, 0x39e9edbb, 0x2ce6a59e, 0x2df215e1, 0x216a17ba, 0x3a8f3cfa, 0x0a14d990, 0x1162e529, 0x1213c181, + 0x3daa68f5, 0x16c570ff, 0x1063321c, 0x06a2d0e8, 0x17c094a4, 0x39a5d9c9, 0x086d4802, 0x67ab7fe3, 0x67f51392, + 0x3649c2ac, 0x62aa8cf8, 0x55b6fdbb, 0x55c3e972, 0x2f865724, 0x314fa653, + ], + [ + 0x029f66f1, 0x016f80a2, 0x4b70e0c2, 0x1782f9ab, 0x697578ee, 0x07b2c8b7, 0x123f6681, 0x2b78db24, 0x2cd8db9d, + 0x302947b1, 0x04f4c99a, 0x1f8bcbbd, 0x61c782ea, 0x3459928c, 0x3efec720, 0x24f2b8f6, 0x5dec66b5, 0x622386cc, + 0x26b70002, 0x1fa0d640, 0x6edeaa0a, 0x670ff3e1, 0x18641d8e, 0x43b68197, + ], + [ + 0x315b1707, 0x46db526a, 0x02fa5277, 0x36f6edf9, 0x31ad912b, 0x7d518ebd, 0x61db2eea, 0x0ba28bad, 0x3c839e59, + 0x7ed007f1, 0x74447f8a, 0x6b4ce5b7, 0x7272e3a4, 0x192257d1, 0x5f882281, 0x5f890768, 0x47eec4cb, 0x2ef3e6c8, + 0x43d6e4e2, 0x668ce6ba, 0x50679e00, 0x24c067a8, 0x605be47c, 0x324ac2ec, + ], + // Terminal full rounds (4) + [ + 0x5883788f, 0x7eba66af, 0x23620f78, 0x44492c9a, 0x7cc098a4, 0x705191fa, 0x2f7185e2, 0x6ebbb07e, 0x23508c3b, + 0x6cb0f0f4, 0x1190a8c0, 0x60f8f1d0, 0x316c16a1, 0x440742c7, 0x7643f142, 0x642f9668, 0x214b7566, 0x52a5c469, + 0x1bfd90da, 0x1d7d8076, 0x6e06d1e8, 0x7d672e6d, 0x6fd2e3e3, 0x3257ae18, + ], + [ + 0x75861a51, 0x0e2996fe, 0x2bdc228b, 0x6879fcb8, 0x14ca9b1c, 0x29953d92, 0x36ee671d, 0x31366e47, 0x79c4f5f2, + 0x2b8c8639, 0x073a293d, 0x32802c31, 0x4894d32f, 0x06acc989, 0x40d852b1, 0x508857c4, 0x2ffe504d, 0x18be00c1, + 0x75a114e9, 0x4ed5922a, 0x1060ee72, 0x2176563c, 0x0b91b242, 0x6bfbf1a4, + ], + [ + 0x06f94470, 0x694f4383, 0x53cada3e, 0x1527bfd8, 0x2bdfe868, 0x120c2d2c, 0x7dfd6309, 0x10b619c2, 0x0550bc7f, + 0x488cf3dc, 0x4c5454a2, 0x00be2976, 0x349c9669, 0x2b4eb07d, 0x0450bf40, 0x58de7343, 0x3495a265, 0x2305e3b7, + 0x661dd781, 0x1c183983, 0x46992791, 0x3eb3751f, 0x38f728c8, 0x775d0a30, + ], + [ + 0x7636645a, 0x7125aa5d, 0x0c3f2dca, 0x13b595cc, 0x5a5e9bce, 0x54bb3456, 0x069a1a5a, 0x7b9f15ee, 0x50150189, + 0x68c9157b, 0x07e06e22, 0x568aecdb, 0x1403f847, 0x436cf5da, 0x3f09c026, 0x652f7b1b, 0x3e8607f3, 0x5bb37c57, + 0x1b1a9ecf, 0x39d11cb0, 0x1841a51c, 0x1251ad48, 0x74fb5edd, 0x21fa33c6, + ], +]); + +// ========================================================================= +// Accessors +// ========================================================================= + +pub fn poseidon1_24_round_constants() -> &'static [[KoalaBear; 24]; POSEIDON1_N_ROUNDS_24] { + &POSEIDON1_RC_24 +} + +#[inline(always)] +pub fn poseidon1_24_initial_constants() -> &'static [[KoalaBear; 24]] { + &POSEIDON1_RC_24[..POSEIDON1_HALF_FULL_ROUNDS_24] +} + +#[inline(always)] +pub fn poseidon1_24_partial_constants() -> &'static [[KoalaBear; 24]] { + &POSEIDON1_RC_24[POSEIDON1_HALF_FULL_ROUNDS_24..POSEIDON1_HALF_FULL_ROUNDS_24 + POSEIDON1_PARTIAL_ROUNDS_24] +} + +#[inline(always)] +pub fn poseidon1_24_final_constants() -> &'static [[KoalaBear; 24]] { + &POSEIDON1_RC_24[POSEIDON1_HALF_FULL_ROUNDS_24 + POSEIDON1_PARTIAL_ROUNDS_24..] +} + +pub fn poseidon1_24_sparse_m_i() -> &'static [[KoalaBear; 24]; 24] { + &precomputed_24().sparse_m_i +} + +pub fn poseidon1_24_sparse_first_row() -> &'static Vec<[KoalaBear; 24]> { + &precomputed_24().sparse_first_row +} + +pub fn poseidon1_24_sparse_v() -> &'static Vec<[KoalaBear; 24]> { + &precomputed_24().sparse_v +} + +pub fn poseidon1_24_sparse_first_round_constants() -> &'static [KoalaBear; 24] { + &precomputed_24().sparse_first_round_constants +} + +pub fn poseidon1_24_sparse_scalar_round_constants() -> &'static Vec { + &precomputed_24().sparse_round_constants +} + +#[derive(Clone, Debug)] +pub struct Poseidon1KoalaBear24 { + pre: &'static Precomputed24, +} + +impl Poseidon1KoalaBear24 { + #[inline(always)] + #[allow(clippy::needless_range_loop)] + fn permute_generic + InjectiveMonomial<3>>(&self, state: &mut [R; 24]) { + // Initial full rounds: AddRC + S-box + Karatsuba MDS. + for rc in poseidon1_24_initial_constants() { + Self::full_round(state, rc); + } + + // Partial rounds via sparse decomposition. + // Add first-round constants. + for (s, &c) in state.iter_mut().zip(self.pre.sparse_first_round_constants.iter()) { + *s += c; + } + // Apply dense transition matrix m_i (once). + { + let input = *state; + for i in 0..24 { + state[i] = R::ZERO; + for j in 0..24 { + state[i] += input[j] * self.pre.sparse_m_i[i][j]; + } + } + } + // Loop over partial rounds: S-box + scalar constant + cheap_matmul. + let rounds_p = self.pre.sparse_first_row.len(); + for r in 0..rounds_p { + state[0] = state[0].injective_exp_n(); + if r < rounds_p - 1 { + state[0] += self.pre.sparse_round_constants[r]; + } + // cheap_matmul: O(24) sparse matrix multiply. + let old_s0 = state[0]; + state[0] = parity_dot(*state, self.pre.sparse_first_row[r]); + for i in 1..24 { + state[i] += old_s0 * self.pre.sparse_v[r][i - 1]; + } + } + + // Terminal full rounds. + for rc in poseidon1_24_final_constants() { + Self::full_round(state, rc); + } + } + + #[inline(always)] + fn full_round + InjectiveMonomial<3>>(state: &mut [R; 24], rc: &[KoalaBear; 24]) { + for (s, &c) in state.iter_mut().zip(rc.iter()) { + *s += c; + } + for s in state.iter_mut() { + *s = s.injective_exp_n(); + } + mds_circ_24(state); + } + + /// NEON-specific fast path with pre-packed constants and latency hiding. + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + #[inline(always)] + fn permute_neon(&self, state: &mut [PackedKB; 24]) { + use crate::PackedMontyField31Neon; + use crate::exp_small; + use crate::{InternalLayer24, add_rc_and_sbox}; + use core::mem::transmute; + + let neon = &self.pre.neon; + + // --- Initial full rounds (first 3 of 4) --- + for round_constants in &neon.packed_initial_rc { + for (s, &rc) in state.iter_mut().zip(round_constants.iter()) { + add_rc_and_sbox::(s, rc); + } + neon_karatsuba::mds_circ_24_neon(state, &neon.mds_col); + } + + // --- Last initial full round: AddRC + S-box, then fused (m_i * MDS) --- + { + for (s, &rc) in state.iter_mut().zip(neon.packed_last_initial_rc.iter()) { + add_rc_and_sbox::(s, rc); + } + let input = *state; + for (i, s) in state.iter_mut().enumerate() { + *s = PackedMontyField31Neon::::dot_product(&input, &neon.packed_fused_mi_mds[i]) + + neon.packed_fused_bias[i]; + } + } + + // --- Partial rounds with latency hiding via InternalLayer24 split --- + { + let mut split = InternalLayer24::from_packed_field_array(*state); + + for r in 0..POSEIDON1_PARTIAL_ROUNDS_24 { + // PATH A (high latency): S-box on s0. + unsafe { + let s0_signed = split.s0.to_signed_vector(); + let s0_sboxed = exp_small::(s0_signed); + split.s0 = PackedMontyField31Neon::from_vector(s0_sboxed); + } + + // Add scalar round constant (except last round). + if r < POSEIDON1_PARTIAL_ROUNDS_24 - 1 { + split.s0 += neon.packed_round_constants[r]; + } + + // PATH B (can overlap with S-box): partial dot product on s_hi. + let s_hi: &[PackedKB; 23] = unsafe { transmute(&split.s_hi) }; + let first_row = &neon.packed_sparse_first_row[r]; + let first_row_hi: &[PackedKB; 23] = first_row[1..].try_into().unwrap(); + let partial_dot = PackedMontyField31Neon::::dot_product(s_hi, first_row_hi); + + // SERIAL: complete s0 = first_row[0] * s0 + partial_dot. + let s0_val = split.s0; + split.s0 = s0_val * first_row[0] + partial_dot; + + // Rank-1 update: s_hi[j] += s0_old * v[j]. + let v = &neon.packed_sparse_v[r]; + let s_hi_mut: &mut [PackedKB; 23] = unsafe { transmute(&mut split.s_hi) }; + for j in 0..23 { + s_hi_mut[j] += s0_val * v[j]; + } + } + + *state = unsafe { split.to_packed_field_array() }; + } + + // --- Terminal full rounds --- + for round_constants in &neon.packed_terminal_rc { + for (s, &rc) in state.iter_mut().zip(round_constants.iter()) { + add_rc_and_sbox::(s, rc); + } + neon_karatsuba::mds_circ_24_neon(state, &neon.mds_col); + } + } + + /// Compression mode: output = permute(input) + input. + #[inline(always)] + pub fn compress_in_place + InjectiveMonomial<3> + Send + Sync + 'static>( + &self, + state: &mut [R; 24], + ) { + let initial = *state; + Permutation::permute_mut(self, state); + for (s, init) in state.iter_mut().zip(initial) { + *s += init; + } + } +} + +impl + InjectiveMonomial<3> + Send + Sync + 'static> Permutation<[R; 24]> + for Poseidon1KoalaBear24 +{ + fn permute_mut(&self, input: &mut [R; 24]) { + #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + { + if std::any::TypeId::of::() == std::any::TypeId::of::() { + let neon_state: &mut [PackedKB; 24] = unsafe { &mut *(input as *mut [R; 24] as *mut [PackedKB; 24]) }; + self.permute_neon(neon_state); + return; + } + } + self.permute_generic(input); + } +} + +pub fn default_koalabear_poseidon1_24() -> Poseidon1KoalaBear24 { + Poseidon1KoalaBear24 { pre: precomputed_24() } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::KoalaBear; + use field::PrimeField32; + + #[test] + fn test_plonky3_compatibility() { + /* + + use p3_symmetric::Permutation; + + use crate::{KoalaBear, default_koalabear_poseidon1_24}; + + #[test] + fn plonky3_test() { + let poseidon1 = default_koalabear_poseidon1_24(); + let mut input: [KoalaBear; 24] = KoalaBear::new_array([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + ]); + poseidon1.permute_mut(&mut input); + dbg!(&input); + } + + */ + let p1 = default_koalabear_poseidon1_24(); + let mut input: [KoalaBear; 24] = KoalaBear::new_array([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + ]); + p1.permute_mut(&mut input); + let vals: Vec = input.iter().map(|x| x.as_canonical_u32()).collect(); + assert_eq!( + vals, + vec![ + 511672087, 215882318, 237782537, 740528428, 712760904, 54615367, 751514671, 110231969, 1905276435, + 992525666, 918312360, 18628693, 749929200, 1916418953, 691276896, 1112901727, 1163558623, 882867603, + 673396520, 1480278156, 1402044758, 1693467175, 1766273044, 433841551, + ] + ); + } +} diff --git a/crates/backend/symetric/src/permutation.rs b/crates/backend/symetric/src/permutation.rs index c129a1dc..13c15c6d 100644 --- a/crates/backend/symetric/src/permutation.rs +++ b/crates/backend/symetric/src/permutation.rs @@ -1,7 +1,7 @@ // Credits: Plonky3 (https://github.com/Plonky3/Plonky3) (MIT and Apache-2.0 licenses). use field::{Algebra, InjectiveMonomial}; -use koala_bear::{KoalaBear, Poseidon1KoalaBear16}; +use koala_bear::{KoalaBear, Poseidon1KoalaBear16, Poseidon1KoalaBear24}; pub trait Compression: Clone + Sync { #[inline(always)] @@ -20,3 +20,11 @@ impl + InjectiveMonomial<3> + Send + Sync + 'static> Compr self.compress_in_place(input); } } + +impl + InjectiveMonomial<3> + Send + Sync + 'static> Compression<[R; 24]> + for Poseidon1KoalaBear24 +{ + fn compress_mut(&self, input: &mut [R; 24]) { + self.compress_in_place(input); + } +} diff --git a/crates/lean_compiler/Cargo.toml b/crates/lean_compiler/Cargo.toml index 1d4b10c5..362bd814 100644 --- a/crates/lean_compiler/Cargo.toml +++ b/crates/lean_compiler/Cargo.toml @@ -10,7 +10,6 @@ workspace = true pest.workspace = true pest_derive.workspace = true utils.workspace = true -xmss.workspace = true rand.workspace = true tracing.workspace = true diff --git a/crates/lean_compiler/snark_lib.py b/crates/lean_compiler/snark_lib.py index 8eaa1d05..e1a1c41b 100644 --- a/crates/lean_compiler/snark_lib.py +++ b/crates/lean_compiler/snark_lib.py @@ -81,6 +81,16 @@ def poseidon16_compress(left, right, output, mode): _ = left, right, output, mode +def poseidon24_compress_0_9(left, right, output): + _ = left, right, output + +def poseidon24_permute_0_9(left, right, output): + _ = left, right, output + +def poseidon24_permute_9_18(left, right, output): + _ = left, right, output + + def add_be(a, b, result, length=None): _ = a, b, result, length diff --git a/crates/lean_compiler/src/a_simplify_lang.rs b/crates/lean_compiler/src/a_simplify_lang.rs index 2fe5bb93..8a1c5062 100644 --- a/crates/lean_compiler/src/a_simplify_lang.rs +++ b/crates/lean_compiler/src/a_simplify_lang.rs @@ -4,7 +4,10 @@ use crate::{ parser::{ConstArrayValue, parse_program}, }; use backend::PrimeCharacteristicRing; -use lean_vm::{Boolean, BooleanExpr, CustomHint, EXT_OP_FUNCTIONS, FunctionName, SourceLocation, Table, TableT}; +use lean_vm::{ + Boolean, BooleanExpr, CustomHint, EXT_OP_FUNCTIONS, FunctionName, POSEIDON_24_MODE_COMPRESS_0_9, + POSEIDON_24_MODE_PERMUTE_0_9, POSEIDON_24_MODE_PERMUTE_9_18, SourceLocation, Table, TableT, +}; use std::{ collections::{BTreeMap, BTreeSet}, fmt::{Display, Formatter}, @@ -2152,19 +2155,36 @@ fn simplify_lines( continue; } - // Special handling for poseidon16 precompile - if function_name == Table::poseidon16().name() { + // Special handling for poseidon precompiles + let is_p16 = function_name == "poseidon16_compress"; + let is_p24_compress_0_9 = function_name == "poseidon24_compress_0_9"; + let is_p24_permute_0_9 = function_name == "poseidon24_permute_0_9"; + let is_p24_permute_9_18 = function_name == "poseidon24_permute_9_18"; + let is_p24 = is_p24_compress_0_9 || is_p24_permute_0_9 || is_p24_permute_9_18; + if is_p16 || is_p24 { if !targets.is_empty() { return Err(format!( "Precompile {function_name} should not return values, at {location}" )); } - let simplified_args = args + let mut simplified_args = args .iter() .map(|arg| simplify_expr(ctx, state, const_malloc, arg, &mut res)) .collect::, _>>()?; + if is_p24_compress_0_9 { + simplified_args.push(SimpleExpr::scalar(F::from_usize(POSEIDON_24_MODE_COMPRESS_0_9))); + } else if is_p24_permute_0_9 { + simplified_args.push(SimpleExpr::scalar(F::from_usize(POSEIDON_24_MODE_PERMUTE_0_9))); + } else if is_p24_permute_9_18 { + simplified_args.push(SimpleExpr::scalar(F::from_usize(POSEIDON_24_MODE_PERMUTE_9_18))); + } + let table = if is_p16 { + Table::poseidon16() + } else { + Table::poseidon24() + }; res.push(SimpleLine::Precompile { - table: Table::poseidon16(), + table, args: simplified_args, }); continue; diff --git a/crates/lean_compiler/src/b_compile_intermediate.rs b/crates/lean_compiler/src/b_compile_intermediate.rs index 8c673cfb..c4b92879 100644 --- a/crates/lean_compiler/src/b_compile_intermediate.rs +++ b/crates/lean_compiler/src/b_compile_intermediate.rs @@ -512,6 +512,7 @@ fn compile_lines( match table { Table::ExtensionOp(_) => assert_eq!(args.len(), 5), Table::Poseidon16(_) => assert_eq!(args.len(), 3), + Table::Poseidon24(_) => assert_eq!(args.len(), 4), Table::Execution(_) => unreachable!(), } // if arg_c is constant, create a variable (in memory) to hold it diff --git a/crates/lean_compiler/src/instruction_encoder.rs b/crates/lean_compiler/src/instruction_encoder.rs index 64fa4e11..6e270903 100644 --- a/crates/lean_compiler/src/instruction_encoder.rs +++ b/crates/lean_compiler/src/instruction_encoder.rs @@ -56,6 +56,11 @@ pub fn field_representation(instr: &Instruction) -> [F; N_INSTRUCTION_COLUMNS] { } => { let precompile_data = match *table { Table::Poseidon16(_) => POSEIDON_PRECOMPILE_DATA, + Table::Poseidon24(_) => { + let mode = *aux_1; + assert!(mode <= POSEIDON_24_MODE_PERMUTE_9_18, "invalid poseidon24 mode={mode}"); + POSEIDON_24_PRECOMPILE_DATA_OFFSET + mode + } Table::ExtensionOp(_) => { let size = *aux_1; let mode = *aux_2; diff --git a/crates/lean_compiler/src/lib.rs b/crates/lean_compiler/src/lib.rs index 71f6e04e..0bf200f0 100644 --- a/crates/lean_compiler/src/lib.rs +++ b/crates/lean_compiler/src/lib.rs @@ -11,7 +11,7 @@ use crate::{ mod a_simplify_lang; mod b_compile_intermediate; mod c_compile_final; -mod instruction_encoder; +pub mod instruction_encoder; pub mod ir; mod lang; mod parser; diff --git a/crates/lean_compiler/src/parser/parsers/function.rs b/crates/lean_compiler/src/parser/parsers/function.rs index d070bf55..95719cde 100644 --- a/crates/lean_compiler/src/parser/parsers/function.rs +++ b/crates/lean_compiler/src/parser/parsers/function.rs @@ -9,7 +9,7 @@ use crate::{ grammar::{ParsePair, Rule}, }, }; -use lean_vm::{CUSTOM_HINTS, EXT_OP_FUNCTIONS, Table, TableT}; +use lean_vm::{CUSTOM_HINTS, EXT_OP_FUNCTIONS}; /// Reserved function names that users cannot define. pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ @@ -26,6 +26,10 @@ pub const RESERVED_FUNCTION_NAMES: &[&str] = &[ "range", "parallel_range", "match_range", + "poseidon16_compress", + "poseidon24_compress_0_9", + "poseidon24_permute_0_9", + "poseidon24_permute_9_18", ]; /// Check if a function name is reserved. @@ -34,10 +38,6 @@ fn is_reserved_function_name(name: &str) -> bool { if RESERVED_FUNCTION_NAMES.contains(&name) || CUSTOM_HINTS.iter().any(|hint| hint.name() == name) { return true; } - // Check precompile names (poseidon16, extension_op functions) - if Table::poseidon16().name() == name { - return true; - } // Extension op function names if EXT_OP_FUNCTIONS.iter().any(|(fn_name, _)| *fn_name == name) { return true; diff --git a/crates/lean_compiler/zkDSL.md b/crates/lean_compiler/zkDSL.md index 2d21b6fb..bbda41a2 100644 --- a/crates/lean_compiler/zkDSL.md +++ b/crates/lean_compiler/zkDSL.md @@ -427,14 +427,24 @@ ONE_EF_PTR # [1, 0, 0, ...] ## Precompiles ### poseidon16_compress -Always in "compression" mode +Width-16 Poseidon compression: `output = Poseidon(left || right) + (left || right)` (feedforward). ``` -poseidon16_compress(left, right, output) +poseidon16_compress(left, right, output) # result = output[0..8] (first 8 elements of output) ``` - `left`: pointer to 8 field elements - `right`: pointer to 8 field elements - `res`: pointer to result (8 elements) +### poseidon24_compress +Width-24 Poseidon compression: `output = (Poseidon(left || right) + (left || right))` (feedforward). +``` +poseidon24_compress_0_9(left, right, res) # result = output[0..9] (first 9 elements of output) +poseidon24_compress_9_18(left, right, res) # result = output[9..23] (...) +``` +- `left`: pointer to 9 field elements (capacity) +- `right`: pointer to 15 field elements (rate) +- `res`: pointer to result (9 elements) + ### Extension Operations Six built-in functions route through a single `extension_op` precompile table. Each combines an element-wise operation with an accumulation over `length` element pairs. diff --git a/crates/lean_prover/Cargo.toml b/crates/lean_prover/Cargo.toml index 373561e9..02b24c93 100644 --- a/crates/lean_prover/Cargo.toml +++ b/crates/lean_prover/Cargo.toml @@ -13,7 +13,6 @@ prox-gaps-conjecture = [] pest.workspace = true pest_derive.workspace = true utils.workspace = true -xmss.workspace = true rand.workspace = true tracing.workspace = true @@ -25,5 +24,4 @@ backend.workspace = true itertools.workspace = true [dev-dependencies] -xmss.workspace = true rec_aggregation.workspace = true diff --git a/crates/lean_prover/src/lib.rs b/crates/lean_prover/src/lib.rs index 26753d97..62399e3c 100644 --- a/crates/lean_prover/src/lib.rs +++ b/crates/lean_prover/src/lib.rs @@ -25,7 +25,7 @@ pub const WHIR_SUBSEQUENT_FOLDING_FACTOR: usize = 5; pub const RS_DOMAIN_INITIAL_REDUCTION_FACTOR: usize = 5; pub const SNARK_DOMAIN_SEP: [F; 8] = F::new_array([ - 130704175, 1303721200, 493664240, 1035493700, 2063844858, 1410214009, 1938905908, 1696767928, + 1046873597, 587403661, 1441000407, 1547181303, 1522249642, 1883305763, 367566943, 2033638717, ]); pub fn default_whir_config(starting_log_inv_rate: usize) -> WhirConfigBuilder { diff --git a/crates/lean_prover/src/test_zkvm.rs b/crates/lean_prover/src/test_zkvm.rs index fcb20307..6d3998b2 100644 --- a/crates/lean_prover/src/test_zkvm.rs +++ b/crates/lean_prover/src/test_zkvm.rs @@ -3,7 +3,9 @@ use backend::*; use lean_compiler::*; use lean_vm::*; use rand::{RngExt, SeedableRng, rngs::StdRng}; -use utils::{init_tracing, poseidon16_compress}; +use utils::{ + init_tracing, poseidon16_compress, poseidon24_compress_0_9, poseidon24_permute_0_9, poseidon24_permute_9_18, +}; #[test] fn test_zk_vm_all_precompiles() { @@ -12,14 +14,26 @@ DIM = 5 N = 11 M = 3 DIGEST_LEN = 8 +P24_INPUT_LEFT = 9 +P24_INPUT_RIGHT = 15 +P24_OUTPUT = 9 def main(): pub_start = NONRESERVED_PROGRAM_INPUT_START poseidon16_compress(pub_start + 4 * DIGEST_LEN, pub_start + 5 * DIGEST_LEN, pub_start + 6 * DIGEST_LEN) - base_ptr = pub_start + 88 - ext_a_ptr = pub_start + 88 + N - ext_b_ptr = pub_start + 88 + N * (DIM + 1) + # poseidon24 compress_0_9: left (9 elems at offset 56), right (15 at 65), output (9 at 80) + poseidon24_compress_0_9(pub_start + 56, pub_start + 56 + P24_INPUT_LEFT, pub_start + 56 + P24_INPUT_LEFT + P24_INPUT_RIGHT) + + # poseidon24 permute_0_9: same input, output (9 elems at offset 89) + poseidon24_permute_0_9(pub_start + 56, pub_start + 56 + P24_INPUT_LEFT, pub_start + 56 + P24_INPUT_LEFT + P24_INPUT_RIGHT + P24_OUTPUT) + + # poseidon24 permute_9_18: same input, output (9 elems at offset 98) + poseidon24_permute_9_18(pub_start + 56, pub_start + 56 + P24_INPUT_LEFT, pub_start + 56 + P24_INPUT_LEFT + P24_INPUT_RIGHT + 2 * P24_OUTPUT) + + base_ptr = pub_start + 107 + ext_a_ptr = pub_start + 107 + N + ext_b_ptr = pub_start + 107 + N * (DIM + 1) # dot_product_be: sum_i base[i] * ext_a[i] dot_product_be(base_ptr, ext_a_ptr, pub_start + 1000, N) @@ -55,14 +69,20 @@ def main(): let mut rng = StdRng::seed_from_u64(0); let mut public_input = F::zero_vec(1 << 13); - // Poseidon test data + // Poseidon16 test data: input at [32..48], expected output at [48..56] let poseidon_16_compress_input: [F; 16] = rng.random(); public_input[32..48].copy_from_slice(&poseidon_16_compress_input); public_input[48..56].copy_from_slice(&poseidon16_compress(poseidon_16_compress_input)[..8]); + + // Poseidon24 test data: input at [56..80] + // compress_0_9 output at [80..89], permute_0_9 output at [89..98], permute_9_18 output at [98..107] let poseidon_24_input: [F; 24] = rng.random(); public_input[56..80].copy_from_slice(&poseidon_24_input); + public_input[80..89].copy_from_slice(&poseidon24_compress_0_9(poseidon_24_input)); + public_input[89..98].copy_from_slice(&poseidon24_permute_0_9(poseidon_24_input)); + public_input[98..107].copy_from_slice(&poseidon24_permute_9_18(poseidon_24_input)); - // Extension op operands: base[N], ext_a[N], ext_b[N] + // Extension op operands: base[N], ext_a[N], ext_b[N] starting at offset 107 let base_slice: [F; N] = rng.random(); let ext_a_slice: [EF; N] = rng.random(); let ext_b_slice: [EF; N] = rng.random(); @@ -74,9 +94,9 @@ def main(): .collect() }; - public_input[88..][..N].copy_from_slice(&base_slice); - public_input[88 + N..][..N * DIMENSION].copy_from_slice(&ef_to_f(&ext_a_slice)); - public_input[88 + N + N * DIMENSION..][..N * DIMENSION].copy_from_slice(&ef_to_f(&ext_b_slice)); + public_input[107..][..N].copy_from_slice(&base_slice); + public_input[107 + N..][..N * DIMENSION].copy_from_slice(&ef_to_f(&ext_a_slice)); + public_input[107 + N + N * DIMENSION..][..N * DIMENSION].copy_from_slice(&ef_to_f(&ext_b_slice)); // dot_product_be result at 1000 let dot_product_be_result: EF = dot_product(ext_a_slice.into_iter(), base_slice.into_iter()); diff --git a/crates/lean_prover/src/trace_gen.rs b/crates/lean_prover/src/trace_gen.rs index 5e62313a..f5c05952 100644 --- a/crates/lean_prover/src/trace_gen.rs +++ b/crates/lean_prover/src/trace_gen.rs @@ -1,7 +1,8 @@ use backend::*; use lean_vm::*; use std::{array, collections::BTreeMap}; -use utils::{ToUsize, get_poseidon_16_of_zero, transposed_par_iter_mut}; +use tracing::info_span; +use utils::{ToUsize, get_poseidon_16_of_zero, get_poseidon_24_of_zero, transposed_par_iter_mut}; #[derive(Debug)] pub struct ExecutionTrace { @@ -96,6 +97,8 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul // Write poseidon(0) at the end of used memory for poseidon table padding rows let null_poseidon_16_hash_ptr = memory_padded.len(); memory_padded.extend_from_slice(get_poseidon_16_of_zero()); + let null_poseidon_24_hash_ptr = memory_padded.len(); + memory_padded.extend_from_slice(get_poseidon_24_of_zero()); // IMPORTANT: memory size should always be >= number of VM cycles let padded_memory_len = (memory_padded.len().max(n_cycles).max(1 << MIN_LOG_N_ROWS_PER_TABLE)).next_power_of_two(); @@ -103,9 +106,6 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul let ExecutionResult { mut traces, .. } = execution_result; - let poseidon_trace = traces.get_mut(&Table::poseidon16()).unwrap(); - fill_trace_poseidon_16(&mut poseidon_trace.columns); - let extension_op_trace = traces.get_mut(&Table::extension_op()).unwrap(); fill_trace_extension_op(extension_op_trace, &memory_padded); @@ -118,9 +118,39 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul }, ); for table in traces.keys().copied().collect::>() { - pad_table(&table, &mut traces, null_poseidon_16_hash_ptr); + pad_table( + &table, + &mut traces, + null_poseidon_16_hash_ptr, + null_poseidon_24_hash_ptr, + ); } + // Ensure poseidon24 is always the smallest (last) table by padding other tables if needed. + // The recursive aggregation verifier assumes this ordering. + let p24_log = traces[&Table::poseidon24()].log_n_rows; + for &table in &[Table::extension_op(), Table::poseidon16()] { + if traces[&table].log_n_rows < p24_log { + let target = 1usize << p24_log; + let trace = traces.get_mut(&table).unwrap(); + let padding = if table == Table::poseidon16() { + default_poseidon_16_row(null_poseidon_16_hash_ptr) + } else { + table.padding_row() + }; + for (col, val) in trace.columns.iter_mut().zip(padding.iter()) { + col.resize(target, *val); + } + trace.log_n_rows = p24_log; + } + } + + // Fill AIR trace columns (intermediate round states + outputs) + info_span!("Poseidon AIR trace fill").in_scope(|| { + fill_trace_poseidon_16(&mut traces.get_mut(&Table::poseidon16()).unwrap().columns); + fill_trace_poseidon_24(&mut traces.get_mut(&Table::poseidon24()).unwrap().columns); + }); + ExecutionTrace { traces, public_memory_size: execution_result.public_memory_size, @@ -129,20 +159,22 @@ pub fn get_execution_trace(bytecode: &Bytecode, execution_result: ExecutionResul } } -fn pad_table(table: &Table, traces: &mut BTreeMap, null_poseidon_16_hash_ptr: usize) { +fn pad_table( + table: &Table, + traces: &mut BTreeMap, + null_poseidon_16_hash_ptr: usize, + null_poseidon_24_hash_ptr: usize, +) { let trace = traces.get_mut(table).unwrap(); let h = trace.columns[0].len(); - trace - .columns - .iter() - .enumerate() - .for_each(|(i, col)| assert_eq!(col.len(), h, "column {}, table {}", i, table.name())); trace.non_padded_n_rows = h; trace.log_n_rows = log2_ceil_usize(h + 1).max(MIN_LOG_N_ROWS_PER_TABLE); let n_rows = 1 << trace.log_n_rows; let padding_row = if *table == Table::poseidon16() { default_poseidon_16_row(null_poseidon_16_hash_ptr) + } else if *table == Table::poseidon24() { + default_poseidon_24_row(null_poseidon_24_hash_ptr) } else { table.padding_row() }; diff --git a/crates/lean_vm/Cargo.toml b/crates/lean_vm/Cargo.toml index fb31a642..872c226e 100644 --- a/crates/lean_vm/Cargo.toml +++ b/crates/lean_vm/Cargo.toml @@ -10,9 +10,10 @@ workspace = true pest.workspace = true pest_derive.workspace = true utils.workspace = true -xmss.workspace = true rand.workspace = true +leansig_wrapper.workspace = true tracing.workspace = true air.workspace = true backend.workspace = true itertools.workspace = true +serde.workspace = true diff --git a/crates/lean_vm/src/core/constants.rs b/crates/lean_vm/src/core/constants.rs index b8764d9e..94b22a5f 100644 --- a/crates/lean_vm/src/core/constants.rs +++ b/crates/lean_vm/src/core/constants.rs @@ -19,10 +19,11 @@ pub const MAX_LOG_MEMORY_SIZE: usize = 26; /// Minimum and maximum number of rows per table (as powers of two), both inclusive pub const MIN_LOG_N_ROWS_PER_TABLE: usize = 8; // Zero padding will be added to each at least, if this minimum is not reached, (ensuring AIR / GKR work fine, with SIMD, without too much edge cases). Long term, we should find a more elegant solution. -pub const MAX_LOG_N_ROWS_PER_TABLE: [(Table, usize); 3] = [ +pub const MAX_LOG_N_ROWS_PER_TABLE: [(Table, usize); 4] = [ (Table::execution(), 25), (Table::extension_op(), 20), - (Table::poseidon16(), 21), + (Table::poseidon16(), 19), + (Table::poseidon24(), 19), ]; /// Starting program counter diff --git a/crates/lean_vm/src/core/label.rs b/crates/lean_vm/src/core/label.rs index 7d2190de..ee8d03d8 100644 --- a/crates/lean_vm/src/core/label.rs +++ b/crates/lean_vm/src/core/label.rs @@ -1,7 +1,7 @@ use crate::SourceLocation; /// Structured label for bytecode locations -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub enum Label { /// Function entry point: @function_{name} Function(String), @@ -26,7 +26,7 @@ pub enum Label { Custom(String), } -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub enum IfKind { /// @if_{id} If, @@ -36,7 +36,7 @@ pub enum IfKind { End, } -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub enum AuxKind { /// @aux_var_{id} AuxVar, diff --git a/crates/lean_vm/src/core/types.rs b/crates/lean_vm/src/core/types.rs index fbad9af1..f95fb6e1 100644 --- a/crates/lean_vm/src/core/types.rs +++ b/crates/lean_vm/src/core/types.rs @@ -27,7 +27,7 @@ pub type FunctionName = String; pub type FileId = usize; /// Location in source code -#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] +#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct SourceLocation { pub file_id: FileId, pub line_number: SourceLineNumber, diff --git a/crates/lean_vm/src/isa/bytecode.rs b/crates/lean_vm/src/isa/bytecode.rs index e9ce0a7e..1d2ed74b 100644 --- a/crates/lean_vm/src/isa/bytecode.rs +++ b/crates/lean_vm/src/isa/bytecode.rs @@ -1,6 +1,7 @@ //! Bytecode representation and management use backend::*; +use serde::{Deserialize, Serialize}; use crate::{CodeAddress, EF, F, FileId, FunctionName, Hint, SourceLocation}; @@ -8,12 +9,14 @@ use super::Instruction; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Bytecode { pub instructions: Vec, + #[serde(skip)] pub instructions_multilinear: Vec, - pub instructions_multilinear_packed: Vec>, // embedded in the extension field(bad, TODO) - pub hints: BTreeMap>, // pc -> hints + #[serde(skip)] + pub instructions_multilinear_packed: Vec>, + pub hints: BTreeMap>, pub starting_frame_memory: usize, pub hash: [F; DIGEST_ELEMS], // debug diff --git a/crates/lean_vm/src/isa/hint.rs b/crates/lean_vm/src/isa/hint.rs index 6c7fbc06..73c52171 100644 --- a/crates/lean_vm/src/isa/hint.rs +++ b/crates/lean_vm/src/isa/hint.rs @@ -4,15 +4,15 @@ use crate::execution::ExecutionHistory; use crate::execution::memory::MemoryAccess; use crate::isa::operands::MemOrConstant; use backend::*; +use leansig_wrapper::SIG_SIZE_FE; use std::fmt::Debug; use std::fmt::{Display, Formatter}; use std::hash::Hash; use utils::{ToUsize, to_big_endian_in_field, to_little_endian_in_field}; -use xmss::SIG_SIZE_FE; /// VM hints provide execution guidance and debugging information, but does not appear /// in the verified bytecode. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum Hint { /// Compute the inverse of a field element Inverse { @@ -71,7 +71,7 @@ pub enum Hint { }, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum CustomHint { // Decompose values into their custom representations: /// each field element x is decomposed to: (a0, a1, a2, ..., a11, b) where: @@ -133,23 +133,35 @@ impl CustomHint { ) -> Result<(), RunnerError> { match self { Self::DecomposeBitsXMSS => { + // Aborting hypercube decomposition: a_i = Q * d_i + r_i + // where d_i = floor(a_i / Q), r_i = a_i mod Q, Q = 127 + // Then d_i is decomposed into base-w digits (w = 2^chunk_size) let decomposed_ptr = args[0].read_value(ctx.memory, ctx.fp)?.to_usize(); let remaining_ptr = args[1].read_value(ctx.memory, ctx.fp)?.to_usize(); let to_decompose_ptr = args[2].read_value(ctx.memory, ctx.fp)?.to_usize(); let num_to_decompose = args[3].read_value(ctx.memory, ctx.fp)?.to_usize(); let chunk_size = args[4].read_value(ctx.memory, ctx.fp)?.to_usize(); assert!(24_usize.is_multiple_of(chunk_size)); + let q: usize = 127; // Q parameter for aborting hypercube (p = Q * w^z + 1) + let base = 1 << chunk_size; + let n_chunks = 24 / chunk_size; let mut memory_index_decomposed = decomposed_ptr; let mut memory_index_remaining = remaining_ptr; #[allow(clippy::explicit_counter_loop)] for i in 0..num_to_decompose { let value = ctx.memory.get(to_decompose_ptr + i)?.to_usize(); - for i in 0..24 / chunk_size { - let value = F::from_usize((value >> (chunk_size * i)) & ((1 << chunk_size) - 1)); - ctx.memory.set(memory_index_decomposed, value)?; + let mut d_i = value / q; // floor(a_i / Q) + let r_i = value % q; // a_i mod Q + for _ in 0..n_chunks { + ctx.memory.set(memory_index_decomposed, F::from_usize(d_i % base))?; + d_i /= base; memory_index_decomposed += 1; } - ctx.memory.set(memory_index_remaining, F::from_usize(value >> 24))?; + assert_eq!( + d_i, 0, + "d_i does not fit in {n_chunks} base-{base} digits -> invalid XMSS encoding" + ); + ctx.memory.set(memory_index_remaining, F::from_usize(r_i))?; memory_index_remaining += 1; } } @@ -246,7 +258,7 @@ impl CustomHint { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum Boolean { Equal, Different, @@ -254,7 +266,7 @@ pub enum Boolean { LessOrEqual, } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub struct BooleanExpr { pub left: E, pub right: E, diff --git a/crates/lean_vm/src/isa/instruction.rs b/crates/lean_vm/src/isa/instruction.rs index 2f2acbf5..dc7ea36a 100644 --- a/crates/lean_vm/src/isa/instruction.rs +++ b/crates/lean_vm/src/isa/instruction.rs @@ -14,7 +14,7 @@ use std::ops::AddAssign; use utils::ToUsize; /// Complete set of VM instruction types with comprehensive operation support -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum Instruction { /// Basic arithmetic computation instruction (ADD, MUL) Computation { diff --git a/crates/lean_vm/src/isa/operands/mem_or_constant.rs b/crates/lean_vm/src/isa/operands/mem_or_constant.rs index 1082ba33..55ea181f 100644 --- a/crates/lean_vm/src/isa/operands/mem_or_constant.rs +++ b/crates/lean_vm/src/isa/operands/mem_or_constant.rs @@ -5,7 +5,7 @@ use backend::*; use std::fmt::{Display, Formatter}; /// Represents a value that can be either a constant or memory location -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum MemOrConstant { /// Direct constant value Constant(F), diff --git a/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs b/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs index 28c984c3..a64cc1cc 100644 --- a/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs +++ b/crates/lean_vm/src/isa/operands/mem_or_fp_or_constant.rs @@ -6,7 +6,7 @@ use backend::*; use std::fmt::{Display, Formatter}; /// Memory, frame pointer, or constant operand -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum MemOrFpOrConstant { /// memory[fp + offset] MemoryAfterFp { offset: usize }, diff --git a/crates/lean_vm/src/isa/operation.rs b/crates/lean_vm/src/isa/operation.rs index eddc475d..19268355 100644 --- a/crates/lean_vm/src/isa/operation.rs +++ b/crates/lean_vm/src/isa/operation.rs @@ -5,7 +5,7 @@ use backend::*; use std::fmt::{Display, Formatter}; /// Basic arithmetic operations supported by the VM -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub enum Operation { Add, Mul, diff --git a/crates/lean_vm/src/tables/execution/mod.rs b/crates/lean_vm/src/tables/execution/mod.rs index 54f43086..5260b010 100644 --- a/crates/lean_vm/src/tables/execution/mod.rs +++ b/crates/lean_vm/src/tables/execution/mod.rs @@ -5,7 +5,7 @@ use backend::*; mod air; pub use air::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub struct ExecutionTable; impl TableT for ExecutionTable { diff --git a/crates/lean_vm/src/tables/extension_op/mod.rs b/crates/lean_vm/src/tables/extension_op/mod.rs index 31e88469..65ef8319 100644 --- a/crates/lean_vm/src/tables/extension_op/mod.rs +++ b/crates/lean_vm/src/tables/extension_op/mod.rs @@ -38,7 +38,7 @@ pub const EXT_OP_FUNCTIONS: [(&str, usize); 6] = [ ("poly_eq_be", EXT_OP_POLY_EQ_BE), ]; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub struct ExtensionOpPrecompile; impl TableT for ExtensionOpPrecompile { diff --git a/crates/lean_vm/src/tables/mod.rs b/crates/lean_vm/src/tables/mod.rs index 3010d39f..46a7658d 100644 --- a/crates/lean_vm/src/tables/mod.rs +++ b/crates/lean_vm/src/tables/mod.rs @@ -4,6 +4,9 @@ pub use extension_op::*; mod poseidon_16; pub use poseidon_16::*; +mod poseidon_24; +pub use poseidon_24::*; + mod table_enum; pub use table_enum::*; diff --git a/crates/lean_vm/src/tables/poseidon_16/mod.rs b/crates/lean_vm/src/tables/poseidon_16/mod.rs index f24c8d2e..ae9aa221 100644 --- a/crates/lean_vm/src/tables/poseidon_16/mod.rs +++ b/crates/lean_vm/src/tables/poseidon_16/mod.rs @@ -9,7 +9,7 @@ use utils::{ToUsize, poseidon16_compress}; /// For `SymbolicExpression` we use the dense form so the zkDSL generator can /// emit `dot_product_be` precompile calls instead of Karatsuba arithmetic. #[inline(always)] -fn mds_air_16(state: &mut [A; WIDTH]) { +fn mds_air_16(state: &mut [A; WIDTH_16]) { if TypeId::of::() == TypeId::of::>() { dense_mat_vec_air_16(mds_dense_16(), state); return; @@ -17,7 +17,7 @@ fn mds_air_16(state: &mut [A; WIDTH]) { macro_rules! dispatch { ($t:ty) => { if TypeId::of::() == TypeId::of::<$t>() { - mds_circ_16::<$t>(unsafe { &mut *(state as *mut [A; WIDTH] as *mut [$t; WIDTH]) }); + mds_circ_16::<$t>(unsafe { &mut *(state as *mut [A; WIDTH_16] as *mut [$t; WIDTH_16]) }); return; } }; @@ -84,7 +84,7 @@ fn mul_kb(a: A, value: F) -> A { mod trace_gen; pub use trace_gen::{default_poseidon_16_row, fill_trace_poseidon_16}; -pub(super) const WIDTH: usize = 16; +pub(super) const WIDTH_16: usize = 16; const HALF_INITIAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; const PARTIAL_ROUNDS: usize = POSEIDON1_PARTIAL_ROUNDS; const HALF_FINAL_FULL_ROUNDS: usize = POSEIDON1_HALF_FULL_ROUNDS / 2; @@ -98,7 +98,7 @@ pub const POSEIDON_16_COL_INDEX_INPUT_RES: ColIndex = 3; pub const POSEIDON_16_COL_INPUT_START: ColIndex = 4; pub const POSEIDON_16_COL_OUTPUT_START: ColIndex = num_cols_poseidon_16() - 8; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] pub struct Poseidon16Precompile; impl TableT for Poseidon16Precompile { @@ -245,15 +245,15 @@ pub(super) struct Poseidon1Cols16 { pub index_b: T, pub index_res: T, - pub inputs: [T; WIDTH], - pub beginning_full_rounds: [[T; WIDTH]; HALF_INITIAL_FULL_ROUNDS], + pub inputs: [T; WIDTH_16], + pub beginning_full_rounds: [[T; WIDTH_16]; HALF_INITIAL_FULL_ROUNDS], pub partial_rounds: [T; PARTIAL_ROUNDS], - pub ending_full_rounds: [[T; WIDTH]; HALF_FINAL_FULL_ROUNDS - 1], - pub outputs: [T; WIDTH / 2], + pub ending_full_rounds: [[T; WIDTH_16]; HALF_FINAL_FULL_ROUNDS - 1], + pub outputs: [T; WIDTH_16 / 2], } fn eval_poseidon1_16(builder: &mut AB, local: &Poseidon1Cols16) { - let mut state: [_; WIDTH] = local.inputs; + let mut state: [_; WIDTH_16] = local.inputs; let initial_constants = poseidon1_initial_constants(); for round in 0..HALF_INITIAL_FULL_ROUNDS { @@ -317,10 +317,10 @@ pub const fn num_cols_poseidon_16() -> usize { #[inline] fn eval_2_full_rounds_16( - state: &mut [AB::IF; WIDTH], - post_full_round: &[AB::IF; WIDTH], - round_constants_1: &[F; WIDTH], - round_constants_2: &[F; WIDTH], + state: &mut [AB::IF; WIDTH_16], + post_full_round: &[AB::IF; WIDTH_16], + round_constants_1: &[F; WIDTH_16], + round_constants_2: &[F; WIDTH_16], builder: &mut AB, ) { for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { @@ -341,11 +341,11 @@ fn eval_2_full_rounds_16( #[inline] fn eval_last_2_full_rounds_16( - initial_state: &[AB::IF; WIDTH], - state: &mut [AB::IF; WIDTH], - outputs: &[AB::IF; WIDTH / 2], - round_constants_1: &[F; WIDTH], - round_constants_2: &[F; WIDTH], + initial_state: &[AB::IF; WIDTH_16], + state: &mut [AB::IF; WIDTH_16], + outputs: &[AB::IF; WIDTH_16 / 2], + round_constants_1: &[F; WIDTH_16], + round_constants_2: &[F; WIDTH_16], builder: &mut AB, ) { for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { @@ -369,11 +369,11 @@ fn eval_last_2_full_rounds_16( } #[inline] -fn dense_mat_vec_air_16(mat: &[[F; 16]; 16], state: &mut [A; WIDTH]) { +fn dense_mat_vec_air_16(mat: &[[F; 16]; 16], state: &mut [A; WIDTH_16]) { let input = *state; - for i in 0..WIDTH { + for i in 0..WIDTH_16 { let mut acc = A::ZERO; - for j in 0..WIDTH { + for j in 0..WIDTH_16 { acc += mul_kb(input[j], mat[i][j]); } state[i] = acc; @@ -382,17 +382,17 @@ fn dense_mat_vec_air_16(mat: &[[F; 16]; 16 #[inline] fn sparse_mat_air_16( - state: &mut [A; WIDTH], - first_row: &[F; WIDTH], - v: &[F; WIDTH], + state: &mut [A; WIDTH_16], + first_row: &[F; WIDTH_16], + v: &[F; WIDTH_16], ) { let old_s0 = state[0]; let mut new_s0 = A::ZERO; - for j in 0..WIDTH { + for j in 0..WIDTH_16 { new_s0 += mul_kb(state[j], first_row[j]); } state[0] = new_s0; - for i in 1..WIDTH { + for i in 1..WIDTH_16 { state[i] += mul_kb(old_s0, v[i - 1]); } } diff --git a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs index 04a7455c..62b5ecbb 100644 --- a/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs +++ b/crates/lean_vm/src/tables/poseidon_16/trace_gen.rs @@ -2,7 +2,7 @@ use tracing::instrument; use crate::{ F, ZERO_VEC_PTR, - tables::{Poseidon1Cols16, WIDTH, num_cols_poseidon_16}, + tables::{Poseidon1Cols16, WIDTH_16, num_cols_poseidon_16}, }; use backend::*; @@ -59,7 +59,7 @@ pub fn default_poseidon_16_row(null_hash_ptr: usize) -> Vec { } fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseidon1Cols16<&mut F>) { - let inputs: [F; WIDTH] = std::array::from_fn(|i| *perm.inputs[i]); + let inputs: [F; WIDTH_16] = std::array::from_fn(|i| *perm.inputs[i]); let mut state = inputs; // No initial linear layer for Poseidon1 (unlike Poseidon2) @@ -80,8 +80,8 @@ fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseido } let m_i = poseidon1_sparse_m_i(); let input_for_mi = state; - for i in 0..WIDTH { - let row: [F; WIDTH] = m_i[i].map(F::from); + for i in 0..WIDTH_16 { + let row: [F; WIDTH_16] = m_i[i].map(F::from); state[i] = F::dot_product(&input_for_mi, &row); } @@ -99,10 +99,10 @@ fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseido } // Sparse matrix let old_s0 = state[0]; - let row: [F; WIDTH] = first_rows[round].map(F::from); + let row: [F; WIDTH_16] = first_rows[round].map(F::from); let new_s0 = F::dot_product(&state, &row); state[0] = new_s0; - for i in 1..WIDTH { + for i in 1..WIDTH_16 { state[i] += old_s0 * v_vecs[round][i - 1]; } } @@ -128,10 +128,10 @@ fn generate_trace_rows_for_perm + Copy>(perm: &mut Poseido #[inline] fn generate_2_full_round + Copy>( - state: &mut [F; WIDTH], - post_full_round: &mut [&mut F; WIDTH], - round_constants_1: &[KoalaBear; WIDTH], - round_constants_2: &[KoalaBear; WIDTH], + state: &mut [F; WIDTH_16], + post_full_round: &mut [&mut F; WIDTH_16], + round_constants_1: &[KoalaBear; WIDTH_16], + round_constants_2: &[KoalaBear; WIDTH_16], ) { for (state_i, const_i) in state.iter_mut().zip(round_constants_1) { *state_i += *const_i; @@ -152,11 +152,11 @@ fn generate_2_full_round + Copy>( #[inline] fn generate_last_2_full_rounds + Copy>( - state: &mut [F; WIDTH], - inputs: &[F; WIDTH], - outputs: &mut [&mut F; WIDTH / 2], - round_constants_1: &[KoalaBear; WIDTH], - round_constants_2: &[KoalaBear; WIDTH], + state: &mut [F; WIDTH_16], + inputs: &[F; WIDTH_16], + outputs: &mut [&mut F; WIDTH_16 / 2], + round_constants_1: &[KoalaBear; WIDTH_16], + round_constants_2: &[KoalaBear; WIDTH_16], ) { for (state_i, const_i) in state.iter_mut().zip(round_constants_1) { *state_i += *const_i; diff --git a/crates/lean_vm/src/tables/poseidon_24/mod.rs b/crates/lean_vm/src/tables/poseidon_24/mod.rs new file mode 100644 index 00000000..47b14ec5 --- /dev/null +++ b/crates/lean_vm/src/tables/poseidon_24/mod.rs @@ -0,0 +1,466 @@ +use std::any::TypeId; + +use crate::*; +use backend::*; +use utils::{ToUsize, poseidon24_compress_0_9, poseidon24_permute_0_9, poseidon24_permute_9_18}; + +/// Dispatch `mds_circ_24` through concrete types. +/// For `SymbolicExpression` we use the dense form so the zkDSL generator can +/// emit `dot_product_be` precompile calls instead of Karatsuba arithmetic. +#[inline(always)] +fn mds_air_24(state: &mut [A; WIDTH_24]) { + if TypeId::of::() == TypeId::of::>() { + dense_mat_vec_air_24(mds_dense_24(), state); + return; + } + macro_rules! dispatch { + ($t:ty) => { + if TypeId::of::() == TypeId::of::<$t>() { + mds_circ_24::<$t>(unsafe { &mut *(state as *mut [A; WIDTH_24] as *mut [$t; WIDTH_24]) }); + return; + } + }; + } + dispatch!(F); + dispatch!(EF); + dispatch!(FPacking); + dispatch!(EFPacking); + unreachable!() +} + +fn mds_dense_24() -> &'static [[F; WIDTH_24]; WIDTH_24] { + use std::sync::OnceLock; + static MAT: OnceLock<[[KoalaBear; WIDTH_24]; WIDTH_24]> = OnceLock::new(); + MAT.get_or_init(|| { + let cols: [[F; WIDTH_24]; WIDTH_24] = std::array::from_fn(|j| { + let mut e = [F::ZERO; WIDTH_24]; + e[j] = F::ONE; + mds_circ_24(&mut e); + e + }); + std::array::from_fn(|i| std::array::from_fn(|j| cols[j][i])) + }) +} + +#[inline(always)] +fn add_kb_24(a: &mut A, value: F) { + macro_rules! dispatch { + ($t:ty) => { + if TypeId::of::() == TypeId::of::<$t>() { + *unsafe { &mut *(a as *mut A as *mut $t) } += value; + return; + } + }; + } + dispatch!(F); + dispatch!(EF); + dispatch!(FPacking); + dispatch!(EFPacking); + dispatch!(SymbolicExpression); + unreachable!() +} + +#[inline(always)] +fn mul_kb_24(a: A, value: F) -> A { + macro_rules! dispatch { + ($t:ty) => { + if TypeId::of::() == TypeId::of::<$t>() { + let r = unsafe { std::ptr::read(&a as *const A as *const $t) } * value; + return unsafe { std::ptr::read(&r as *const $t as *const A) }; + } + }; + } + dispatch!(F); + dispatch!(EF); + dispatch!(FPacking); + dispatch!(EFPacking); + dispatch!(SymbolicExpression); + unreachable!() +} + +mod trace_gen; +pub use trace_gen::default_poseidon_24_row; +pub use trace_gen::fill_trace_poseidon_24; + +pub(super) const WIDTH_24: usize = 24; +const HALF_INITIAL_FULL_ROUNDS_24: usize = POSEIDON1_HALF_FULL_ROUNDS_24 / 2; +const PARTIAL_ROUNDS_24: usize = POSEIDON1_PARTIAL_ROUNDS_24; +const HALF_FINAL_FULL_ROUNDS_24: usize = POSEIDON1_HALF_FULL_ROUNDS_24 / 2; + +pub const POSEIDON_24_PRECOMPILE_DATA_OFFSET: usize = 2; // domain separation: Poseidon16=1, Poseidon24= 2 or 3 or 4, ExtensionOp>=8 + +// 3 modes for Poseidon24 precompile: +// compress_0_9 (mode=0): feedforward + output[0..9] -> precompile_data = 2 +// permute_0_9 (mode=1): permutation + output[0..9] -> precompile_data = 3 +// permute_9_18 (mode=2): permutation + output[9..18]-> precompile_data = 4 +// 2 committed boolean columns: is_compress_0_9, is_permute_0_9 +// 3rd mode deduced: is_permute_9_18 = 1 - is_compress_0_9 - is_permute_0_9 +pub const POSEIDON_24_MODE_COMPRESS_0_9: usize = 0; +pub const POSEIDON_24_MODE_PERMUTE_0_9: usize = 1; +pub const POSEIDON_24_MODE_PERMUTE_9_18: usize = 2; + +pub const POSEIDON_24_INPUT_LEFT_SIZE: usize = 9; +pub const POSEIDON_24_INPUT_RIGHT_SIZE: usize = 15; +pub const POSEIDON_24_OUTPUT_SIZE: usize = 9; + +pub const POSEIDON_24_COL_FLAG: ColIndex = 0; +pub const POSEIDON_24_COL_IS_COMPRESS_0_9: ColIndex = 1; +pub const POSEIDON_24_COL_IS_PERMUTE_0_9: ColIndex = 2; +pub const POSEIDON_24_COL_INDEX_INPUT_LEFT: ColIndex = 3; +pub const POSEIDON_24_COL_INDEX_INPUT_RIGHT: ColIndex = 4; +pub const POSEIDON_24_COL_INDEX_RES: ColIndex = 5; +pub const POSEIDON_24_COL_INPUT_START: ColIndex = 6; +pub const POSEIDON_24_COL_OUTPUT_START: ColIndex = num_cols_poseidon_24() - POSEIDON_24_OUTPUT_SIZE; + +// virtual columns (not committed) +pub const POSEIDON_24_COL_PRECOMPILE_DATA: usize = num_cols_poseidon_24(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] +pub struct Poseidon24Precompile; + +impl TableT for Poseidon24Precompile { + fn name(&self) -> &'static str { + "poseidon24_compress" + } + + fn table(&self) -> Table { + Table::poseidon24() + } + + fn lookups(&self) -> Vec { + vec![ + LookupIntoMemory { + index: POSEIDON_24_COL_INDEX_INPUT_LEFT, + values: (POSEIDON_24_COL_INPUT_START..POSEIDON_24_COL_INPUT_START + POSEIDON_24_INPUT_LEFT_SIZE) + .collect(), + }, + LookupIntoMemory { + index: POSEIDON_24_COL_INDEX_INPUT_RIGHT, + values: (POSEIDON_24_COL_INPUT_START + POSEIDON_24_INPUT_LEFT_SIZE + ..POSEIDON_24_COL_INPUT_START + POSEIDON_24_INPUT_LEFT_SIZE + POSEIDON_24_INPUT_RIGHT_SIZE) + .collect(), + }, + LookupIntoMemory { + index: POSEIDON_24_COL_INDEX_RES, + values: (POSEIDON_24_COL_OUTPUT_START..POSEIDON_24_COL_OUTPUT_START + POSEIDON_24_OUTPUT_SIZE) + .collect(), + }, + ] + } + + fn bus(&self) -> Bus { + Bus { + direction: BusDirection::Pull, + selector: POSEIDON_24_COL_FLAG, + data: vec![ + BusData::Column(POSEIDON_24_COL_PRECOMPILE_DATA), + BusData::Column(POSEIDON_24_COL_INDEX_INPUT_LEFT), + BusData::Column(POSEIDON_24_COL_INDEX_INPUT_RIGHT), + BusData::Column(POSEIDON_24_COL_INDEX_RES), + ], + } + } + + fn n_columns_total(&self) -> usize { + self.n_columns() + 1 // +1 for POSEIDON_24_POSEIDON_24_COL_PRECOMPILE_DATA + } + + fn padding_row(&self) -> Vec { + // depends on null_poseidon_24_hash_ptr (cf lean_prover/trace_gen.rs) + unreachable!() + } + + #[inline(always)] + fn execute( + &self, + index_input_left: F, + index_input_right: F, + index_res: F, + mode: usize, + _: usize, + ctx: &mut InstructionContext<'_, M>, + ) -> Result<(), RunnerError> { + assert!(mode <= POSEIDON_24_MODE_PERMUTE_9_18, "invalid poseidon24 mode={mode}"); + let is_compress_0_9 = mode == POSEIDON_24_MODE_COMPRESS_0_9; + let is_permute_0_9 = mode == POSEIDON_24_MODE_PERMUTE_0_9; + let trace = ctx.traces.get_mut(&self.table()).unwrap(); + + let arg0 = ctx + .memory + .get_slice(index_input_left.to_usize(), POSEIDON_24_INPUT_LEFT_SIZE)?; + let arg1 = ctx + .memory + .get_slice(index_input_right.to_usize(), POSEIDON_24_INPUT_RIGHT_SIZE)?; + + let mut input = [F::ZERO; POSEIDON_24_INPUT_LEFT_SIZE + POSEIDON_24_INPUT_RIGHT_SIZE]; + input[..POSEIDON_24_INPUT_LEFT_SIZE].copy_from_slice(&arg0); + input[POSEIDON_24_INPUT_LEFT_SIZE..].copy_from_slice(&arg1); + + let result = match mode { + POSEIDON_24_MODE_COMPRESS_0_9 => poseidon24_compress_0_9(input), + POSEIDON_24_MODE_PERMUTE_0_9 => poseidon24_permute_0_9(input), + POSEIDON_24_MODE_PERMUTE_9_18 => poseidon24_permute_9_18(input), + _ => unreachable!(), + }; + + let res_a: [F; POSEIDON_24_OUTPUT_SIZE] = result[..POSEIDON_24_OUTPUT_SIZE].try_into().unwrap(); + + ctx.memory.set_slice(index_res.to_usize(), &res_a)?; + + trace.columns[POSEIDON_24_COL_FLAG].push(F::ONE); + trace.columns[POSEIDON_24_COL_IS_COMPRESS_0_9].push(F::from_bool(is_compress_0_9)); + trace.columns[POSEIDON_24_COL_IS_PERMUTE_0_9].push(F::from_bool(is_permute_0_9)); + trace.columns[POSEIDON_24_COL_INDEX_INPUT_LEFT].push(index_input_left); + trace.columns[POSEIDON_24_COL_INDEX_INPUT_RIGHT].push(index_input_right); + trace.columns[POSEIDON_24_COL_INDEX_RES].push(index_res); + for (i, value) in input.iter().enumerate() { + trace.columns[POSEIDON_24_COL_INPUT_START + i].push(*value); + } + trace.columns[POSEIDON_24_COL_PRECOMPILE_DATA].push(F::from_usize(POSEIDON_24_PRECOMPILE_DATA_OFFSET + mode)); + + // the rest of the trace is filled at the end of the execution (for parallelism + SIMD) + + Ok(()) + } +} + +impl Air for Poseidon24Precompile { + type ExtraData = ExtraDataForBuses; + fn n_columns(&self) -> usize { + num_cols_poseidon_24() + } + fn degree_air(&self) -> usize { + 10 + } + fn down_column_indexes(&self) -> Vec { + vec![] + } + fn n_constraints(&self) -> usize { + BUS as usize + 107 + } + fn eval(&self, builder: &mut AB, extra_data: &Self::ExtraData) { + let cols: Poseidon1Cols24 = { + let up = builder.up(); + let (prefix, shorts, suffix) = unsafe { up.align_to::>() }; + debug_assert!(prefix.is_empty(), "Alignment should match"); + debug_assert!(suffix.is_empty(), "Alignment should match"); + debug_assert_eq!(shorts.len(), 1); + unsafe { std::ptr::read(&shorts[0]) } + }; + + let precompile_data = AB::IF::from_usize(POSEIDON_24_PRECOMPILE_DATA_OFFSET) + + cols.is_compress_0_9 * AB::IF::from_usize(POSEIDON_24_MODE_COMPRESS_0_9) + + cols.is_permute_0_9 * AB::IF::from_usize(POSEIDON_24_MODE_PERMUTE_0_9) + + (AB::IF::ONE - cols.is_compress_0_9 - cols.is_permute_0_9) // is_permute_9_18 + * AB::IF::from_usize(POSEIDON_24_MODE_PERMUTE_9_18); + + if BUS { + builder.eval_virtual_column(eval_virtual_bus_column::( + extra_data, + cols.flag, + &[ + precompile_data, + cols.index_input_left, + cols.index_input_right, + cols.index_res, + ], + )); + } else { + builder.declare_values(std::slice::from_ref(&cols.flag)); + builder.declare_values(&[ + precompile_data, + cols.index_input_left, + cols.index_input_right, + cols.index_res, + ]); + } + + builder.assert_bool(cols.flag); + builder.assert_bool(cols.is_compress_0_9); + builder.assert_bool(cols.is_permute_0_9); + + let is_compress = cols.is_compress_0_9; + let is_output_0_9 = cols.is_compress_0_9 + cols.is_permute_0_9; + + eval_poseidon1_24(builder, &cols, is_compress, is_output_0_9) + } +} + +#[repr(C)] +#[derive(Debug)] +pub(super) struct Poseidon1Cols24 { + pub flag: T, + pub is_compress_0_9: T, + pub is_permute_0_9: T, + pub index_input_left: T, + pub index_input_right: T, + pub index_res: T, + + pub inputs: [T; WIDTH_24], + pub beginning_full_rounds: [[T; WIDTH_24]; HALF_INITIAL_FULL_ROUNDS_24], + pub partial_rounds: [T; PARTIAL_ROUNDS_24], + pub ending_full_rounds: [[T; WIDTH_24]; HALF_FINAL_FULL_ROUNDS_24 - 1], + pub outputs: [T; POSEIDON_24_OUTPUT_SIZE], +} + +fn eval_poseidon1_24( + builder: &mut AB, + local: &Poseidon1Cols24, + is_compress: AB::IF, + is_output_0_9: AB::IF, +) { + let mut state: [_; WIDTH_24] = local.inputs; + + // No initial linear layer for Poseidon1 + + let initial_constants = poseidon1_24_initial_constants(); + for round in 0..HALF_INITIAL_FULL_ROUNDS_24 { + eval_2_full_rounds_24( + &mut state, + &local.beginning_full_rounds[round], + &initial_constants[2 * round], + &initial_constants[2 * round + 1], + builder, + ); + } + + // --- Sparse partial rounds --- + let frc = poseidon1_24_sparse_first_round_constants(); + for (s, &c) in state.iter_mut().zip(frc.iter()) { + add_kb_24(s, c); + } + dense_mat_vec_air_24(poseidon1_24_sparse_m_i(), &mut state); + + let first_rows = poseidon1_24_sparse_first_row(); + let v_vecs = poseidon1_24_sparse_v(); + let scalar_rc = poseidon1_24_sparse_scalar_round_constants(); + for round in 0..PARTIAL_ROUNDS_24 { + // S-box on state[0] + state[0] = state[0].cube(); + builder.assert_eq(state[0], local.partial_rounds[round]); + state[0] = local.partial_rounds[round]; + // Scalar round constant (not on last round) + if round < PARTIAL_ROUNDS_24 - 1 { + add_kb_24(&mut state[0], scalar_rc[round]); + } + // Sparse matrix + sparse_mat_air_24(&mut state, &first_rows[round], &v_vecs[round]); + } + + let final_constants = poseidon1_24_final_constants(); + for round in 0..HALF_FINAL_FULL_ROUNDS_24 - 1 { + eval_2_full_rounds_24( + &mut state, + &local.ending_full_rounds[round], + &final_constants[2 * round], + &final_constants[2 * round + 1], + builder, + ); + } + + eval_last_2_full_rounds_24( + &local.inputs, + &mut state, + &local.outputs, + &final_constants[2 * (HALF_FINAL_FULL_ROUNDS_24 - 1)], + &final_constants[2 * (HALF_FINAL_FULL_ROUNDS_24 - 1) + 1], + is_compress, + is_output_0_9, + builder, + ); +} + +pub const fn num_cols_poseidon_24() -> usize { + size_of::>() +} + +#[inline] +fn eval_2_full_rounds_24( + state: &mut [AB::IF; WIDTH_24], + post_full_round: &[AB::IF; WIDTH_24], + round_constants_1: &[F; WIDTH_24], + round_constants_2: &[F; WIDTH_24], + builder: &mut AB, +) { + for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { + add_kb_24(s, *r); + *s = s.cube(); + } + mds_air_24(state); + for (s, r) in state.iter_mut().zip(round_constants_2.iter()) { + add_kb_24(s, *r); + *s = s.cube(); + } + mds_air_24(state); + for (state_i, post_i) in state.iter_mut().zip(post_full_round) { + builder.assert_eq(*state_i, *post_i); + *state_i = *post_i; + } +} + +#[inline] +#[allow(clippy::too_many_arguments)] +fn eval_last_2_full_rounds_24( + initial_state: &[AB::IF; WIDTH_24], + state: &mut [AB::IF; WIDTH_24], + outputs: &[AB::IF; POSEIDON_24_OUTPUT_SIZE], + round_constants_1: &[F; WIDTH_24], + round_constants_2: &[F; WIDTH_24], + is_compress: AB::IF, + is_output_0_9: AB::IF, + builder: &mut AB, +) { + for (s, r) in state.iter_mut().zip(round_constants_1.iter()) { + add_kb_24(s, *r); + *s = s.cube(); + } + mds_air_24(state); + for (s, r) in state.iter_mut().zip(round_constants_2.iter()) { + add_kb_24(s, *r); + *s = s.cube(); + } + mds_air_24(state); + // conditional feedforward: only for compress mode + for (state_i, init_state_i) in state.iter_mut().zip(initial_state) { + *state_i += *init_state_i * is_compress; + } + for ((output_i, state_i), state_9_plus_i) in outputs + .iter() + .zip(&state[..POSEIDON_24_OUTPUT_SIZE]) + .zip(&state[POSEIDON_24_OUTPUT_SIZE..][..POSEIDON_24_OUTPUT_SIZE]) + { + builder.assert_eq( + *output_i, + *state_i * is_output_0_9 + *state_9_plus_i * (AB::IF::ONE - is_output_0_9), + ); + } +} + +#[inline] +fn dense_mat_vec_air_24(mat: &[[F; 24]; 24], state: &mut [A; WIDTH_24]) { + let input = *state; + for i in 0..WIDTH_24 { + let mut acc = A::ZERO; + for j in 0..WIDTH_24 { + acc += mul_kb_24(input[j], mat[i][j]); + } + state[i] = acc; + } +} + +#[inline] +fn sparse_mat_air_24( + state: &mut [A; WIDTH_24], + first_row: &[F; WIDTH_24], + v: &[F; WIDTH_24], +) { + let old_s0 = state[0]; + let mut new_s0 = A::ZERO; + for j in 0..WIDTH_24 { + new_s0 += mul_kb_24(state[j], first_row[j]); + } + state[0] = new_s0; + for i in 1..WIDTH_24 { + state[i] += mul_kb_24(old_s0, v[i - 1]); + } +} diff --git a/crates/lean_vm/src/tables/poseidon_24/trace_gen.rs b/crates/lean_vm/src/tables/poseidon_24/trace_gen.rs new file mode 100644 index 00000000..0044ff98 --- /dev/null +++ b/crates/lean_vm/src/tables/poseidon_24/trace_gen.rs @@ -0,0 +1,198 @@ +use tracing::instrument; + +use crate::{ + F, POSEIDON_24_MODE_COMPRESS_0_9, ZERO_VEC_PTR, + tables::{ + POSEIDON_24_COL_PRECOMPILE_DATA, POSEIDON_24_OUTPUT_SIZE, POSEIDON_24_PRECOMPILE_DATA_OFFSET, Poseidon1Cols24, + WIDTH_24, num_cols_poseidon_24, + }, +}; +use backend::*; + +#[instrument(name = "generate Poseidon24 AIR trace", skip_all)] +pub fn fill_trace_poseidon_24(trace: &mut [Vec]) { + let n = trace.iter().map(|col| col.len()).max().unwrap(); + for col in trace.iter_mut() { + if col.len() != n { + col.resize(n, F::ZERO); + } + } + + let m = n - (n % packing_width::()); + let trace_packed: Vec<_> = trace.iter().map(|col| FPacking::::pack_slice(&col[..m])).collect(); + + // fill the packed rows + (0..m / packing_width::()).into_par_iter().for_each(|i| { + let ptrs: Vec<*mut FPacking> = trace_packed + .iter() + .map(|col| unsafe { (col.as_ptr() as *mut FPacking).add(i) }) + .collect(); + let perm: &mut Poseidon1Cols24<&mut FPacking> = + unsafe { &mut *(ptrs.as_ptr() as *mut Poseidon1Cols24<&mut FPacking>) }; + + generate_trace_rows_for_perm_24(perm); + }); + + // fill the remaining rows (non packed) + for i in m..n { + let ptrs: Vec<*mut F> = trace + .iter() + .map(|col| unsafe { (col.as_ptr() as *mut F).add(i) }) + .collect(); + let perm: &mut Poseidon1Cols24<&mut F> = unsafe { &mut *(ptrs.as_ptr() as *mut Poseidon1Cols24<&mut F>) }; + generate_trace_rows_for_perm_24(perm); + } +} + +pub fn default_poseidon_24_row(null_hash_ptr: usize) -> Vec { + let mut row = vec![F::ZERO; num_cols_poseidon_24() + 1]; + let ptrs: Vec<*mut F> = (0..num_cols_poseidon_24()) + .map(|i| unsafe { row.as_mut_ptr().add(i) }) + .collect(); + + let perm: &mut Poseidon1Cols24<&mut F> = unsafe { &mut *(ptrs.as_ptr() as *mut Poseidon1Cols24<&mut F>) }; + perm.inputs.iter_mut().for_each(|x| **x = F::ZERO); + *perm.flag = F::ZERO; + *perm.is_compress_0_9 = F::ONE; // convention + *perm.is_permute_0_9 = F::ZERO; + *perm.index_input_left = F::from_usize(ZERO_VEC_PTR); + *perm.index_input_right = F::from_usize(ZERO_VEC_PTR); + *perm.index_res = F::from_usize(null_hash_ptr); + + generate_trace_rows_for_perm_24(perm); + // virtual column + row[POSEIDON_24_COL_PRECOMPILE_DATA] = + F::from_usize(POSEIDON_24_PRECOMPILE_DATA_OFFSET + POSEIDON_24_MODE_COMPRESS_0_9); // ...following the above convention + row +} + +fn generate_trace_rows_for_perm_24 + Copy>(perm: &mut Poseidon1Cols24<&mut F>) { + let inputs: [F; WIDTH_24] = std::array::from_fn(|i| *perm.inputs[i]); + let mut state = inputs; + + // No initial linear layer for Poseidon1 + + for (full_round, constants) in perm + .beginning_full_rounds + .iter_mut() + .zip(poseidon1_24_initial_constants().chunks_exact(2)) + { + generate_2_full_round_24(&mut state, full_round, &constants[0], &constants[1]); + } + + // --- Sparse partial rounds --- + let frc = poseidon1_24_sparse_first_round_constants(); + for (s, &c) in state.iter_mut().zip(frc.iter()) { + *s += c; + } + let m_i = poseidon1_24_sparse_m_i(); + let input_for_mi = state; + for i in 0..WIDTH_24 { + let row: [F; WIDTH_24] = m_i[i].map(F::from); + state[i] = F::dot_product(&input_for_mi, &row); + } + + let first_rows = poseidon1_24_sparse_first_row(); + let v_vecs = poseidon1_24_sparse_v(); + let scalar_rc = poseidon1_24_sparse_scalar_round_constants(); + let n_partial = perm.partial_rounds.len(); + for round in 0..n_partial { + // S-box on state[0] + state[0] = state[0].cube(); + *perm.partial_rounds[round] = state[0]; + // Scalar round constant (not on last round) + if round < n_partial - 1 { + state[0] += scalar_rc[round]; + } + // Sparse matrix + let old_s0 = state[0]; + let row: [F; WIDTH_24] = first_rows[round].map(F::from); + let new_s0 = F::dot_product(&state, &row); + state[0] = new_s0; + for i in 1..WIDTH_24 { + state[i] += old_s0 * v_vecs[round][i - 1]; + } + } + + let n_ending_full_rounds = perm.ending_full_rounds.len(); + for (full_round, constants) in perm + .ending_full_rounds + .iter_mut() + .zip(poseidon1_24_final_constants().chunks_exact(2)) + { + generate_2_full_round_24(&mut state, full_round, &constants[0], &constants[1]); + } + + // Last 2 full rounds with conditional feedforward and output selection + let is_compress = *perm.is_compress_0_9; + let is_output_0_9 = *perm.is_compress_0_9 + *perm.is_permute_0_9; + generate_last_2_full_rounds_24( + &mut state, + &inputs, + &mut perm.outputs, + &poseidon1_24_final_constants()[2 * n_ending_full_rounds], + &poseidon1_24_final_constants()[2 * n_ending_full_rounds + 1], + is_compress, + is_output_0_9, + ); +} + +#[inline] +fn generate_2_full_round_24 + Copy>( + state: &mut [F; WIDTH_24], + post_full_round: &mut [&mut F; WIDTH_24], + round_constants_1: &[KoalaBear; WIDTH_24], + round_constants_2: &[KoalaBear; WIDTH_24], +) { + for (state_i, const_i) in state.iter_mut().zip(round_constants_1) { + *state_i += *const_i; + *state_i = state_i.cube(); + } + mds_circ_24(state); + + for (state_i, const_i) in state.iter_mut().zip(round_constants_2.iter()) { + *state_i += *const_i; + *state_i = state_i.cube(); + } + mds_circ_24(state); + + post_full_round.iter_mut().zip(*state).for_each(|(post, x)| { + **post = x; + }); +} + +#[inline] +fn generate_last_2_full_rounds_24 + Copy>( + state: &mut [F; WIDTH_24], + inputs: &[F; WIDTH_24], + outputs: &mut [&mut F; POSEIDON_24_OUTPUT_SIZE], + round_constants_1: &[KoalaBear; WIDTH_24], + round_constants_2: &[KoalaBear; WIDTH_24], + is_compress: F, + is_output_0_9: F, +) { + for (state_i, const_i) in state.iter_mut().zip(round_constants_1) { + *state_i += *const_i; + *state_i = state_i.cube(); + } + mds_circ_24(state); + + for (state_i, const_i) in state.iter_mut().zip(round_constants_2.iter()) { + *state_i += *const_i; + *state_i = state_i.cube(); + } + mds_circ_24(state); + + // Conditional feedforward: only for compress mode + for (state_i, input_i) in state.iter_mut().zip(inputs) { + *state_i += *input_i * is_compress; + } + // Select output[0..9] or output[9..18] based on is_output_0_9 + for ((output, first), second) in outputs + .iter_mut() + .zip(&state[..POSEIDON_24_OUTPUT_SIZE]) + .zip(&state[POSEIDON_24_OUTPUT_SIZE..][..POSEIDON_24_OUTPUT_SIZE]) + { + **output = *first * is_output_0_9 + *second * (F::ONE - is_output_0_9); + } +} diff --git a/crates/lean_vm/src/tables/table_enum.rs b/crates/lean_vm/src/tables/table_enum.rs index 046f55ff..7f3af663 100644 --- a/crates/lean_vm/src/tables/table_enum.rs +++ b/crates/lean_vm/src/tables/table_enum.rs @@ -3,16 +3,22 @@ use backend::*; use crate::execution::memory::MemoryAccess; use crate::*; -pub const N_TABLES: usize = 3; -pub const ALL_TABLES: [Table; N_TABLES] = [Table::execution(), Table::extension_op(), Table::poseidon16()]; +pub const N_TABLES: usize = 4; +pub const ALL_TABLES: [Table; N_TABLES] = [ + Table::execution(), + Table::extension_op(), + Table::poseidon16(), + Table::poseidon24(), +]; pub const MAX_PRECOMPILE_BUS_WIDTH: usize = 4; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)] #[repr(usize)] pub enum Table { Execution(ExecutionTable), ExtensionOp(ExtensionOpPrecompile), Poseidon16(Poseidon16Precompile), + Poseidon24(Poseidon24Precompile), } #[macro_export] @@ -22,6 +28,7 @@ macro_rules! delegate_to_inner { match $self { Self::ExtensionOp(p) => p.$method($($($arg),*)?), Self::Poseidon16(p) => p.$method($($($arg),*)?), + Self::Poseidon24(p) => p.$method($($($arg),*)?), Self::Execution(p) => p.$method($($($arg),*)?), } }; @@ -30,6 +37,7 @@ macro_rules! delegate_to_inner { match $self { Table::ExtensionOp(p) => $macro_name!(p), Table::Poseidon16(p) => $macro_name!(p), + Table::Poseidon24(p) => $macro_name!(p), Table::Execution(p) => $macro_name!(p), } }; @@ -45,6 +53,9 @@ impl Table { pub const fn poseidon16() -> Self { Self::Poseidon16(Poseidon16Precompile) } + pub const fn poseidon24() -> Self { + Self::Poseidon24(Poseidon24Precompile) + } pub fn embed(&self) -> PF { PF::from_usize(self.index()) } diff --git a/crates/leansig_wrapper/Cargo.toml b/crates/leansig_wrapper/Cargo.toml new file mode 100644 index 00000000..73b4d1cf --- /dev/null +++ b/crates/leansig_wrapper/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "leansig_wrapper" +version.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +leansig.workspace = true +leansig_fast_keygen.workspace = true +backend.workspace = true +rand.workspace = true +p3-field = { git = "https://github.com/Plonky3/Plonky3.git" } +ssz = { package = "ethereum_ssz", version = "0.10.0" } + +[features] +test-config = [] \ No newline at end of file diff --git a/crates/leansig_wrapper/src/lib.rs b/crates/leansig_wrapper/src/lib.rs new file mode 100644 index 00000000..38644e08 --- /dev/null +++ b/crates/leansig_wrapper/src/lib.rs @@ -0,0 +1,183 @@ +use backend::{KoalaBear, integers::QuotientMap}; +use leansig::{ + inc_encoding::target_sum::TargetSumEncoding, + signature::{ + SignatureScheme, + generalized_xmss::{ + GeneralizedXMSSPublicKey, GeneralizedXMSSSecretKey, GeneralizedXMSSSignature, + GeneralizedXMSSSignatureScheme, + }, + }, + symmetric::{ + message_hash::{aborting::AbortingHypercubeMessageHash, encode_message}, + prf::shake_to_field::ShakePRFtoF, + tweak_hash::poseidon::PoseidonTweakHash, + }, +}; +use leansig_fast_keygen::signature::SignatureScheme as FastKeyGenSignatureScheme; +use p3_field::PrimeField32; +use std::array; + +#[cfg(feature = "test-config")] +pub const V: usize = 4; +#[cfg(not(feature = "test-config"))] +pub const V: usize = 46; +pub const BASE: usize = 1 << W; +const Z: usize = 8; +const Q: usize = 127; +#[cfg(feature = "test-config")] +pub const TARGET_SUM: usize = 6; +#[cfg(not(feature = "test-config"))] +pub const TARGET_SUM: usize = 200; +pub const RAND_LEN_FE: usize = 7; +pub const HASH_LEN_FE: usize = 8; +pub const MSG_LEN_FE: usize = 9; +pub const PARAMETER_LEN: usize = 5; +pub const TWEAK_LEN_FE: usize = 2; + +pub const W: usize = 3; +pub const MESSAGE_LENGTH: usize = 32; +pub const POSEIDON24_CAPACITY: usize = 9; +pub const POSEIDON24_RATE: usize = 15; + +#[cfg(feature = "test-config")] +pub const LOG_LIFETIME: usize = 8; +#[cfg(not(feature = "test-config"))] +pub const LOG_LIFETIME: usize = 32; + +pub const SIG_SIZE_FE: usize = RAND_LEN_FE + (V + LOG_LIFETIME) * HASH_LEN_FE; + +pub(crate) type F = KoalaBear; + +#[cfg(feature = "test-config")] +pub const WOTS_PUBKET_SPONGE_DOMAIN_SEP: [F; POSEIDON24_CAPACITY] = F::new_array([ + 627826400, 1244476188, 370678638, 978729783, 1996000804, 1380088873, 1753334201, 433326939, 1294775677, +]); +#[cfg(not(feature = "test-config"))] +pub const WOTS_PUBKET_SPONGE_DOMAIN_SEP: [F; POSEIDON24_CAPACITY] = F::new_array([ + 2060061975, 916902315, 229801915, 83751504, 2093549181, 1743125625, 721042244, 1252069948, 1192880636, +]); + +pub use leansig::symmetric::tweak_hash::TweakableHash; +use rand::CryptoRng; + +pub type LeanSigTH = PoseidonTweakHash; + +type MH = + AbortingHypercubeMessageHash; +type TH = PoseidonTweakHash; +type PrF = ShakePRFtoF; +type IE = TargetSumEncoding; + +pub type LeanSigScheme = GeneralizedXMSSSignatureScheme; +pub type XmssPublicKey = GeneralizedXMSSPublicKey; +pub type XmssSecretKey = GeneralizedXMSSSecretKey; +pub type XmssSignature = GeneralizedXMSSSignature; + +#[cfg(feature = "test-config")] +pub type FastKeyGenScheme = leansig_fast_keygen::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_8::SchemeAbortingTargetSumLifetime8Dim46Base8; +#[cfg(feature = "test-config")] +pub type FastKeyGenSecretKey = leansig_fast_keygen::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_8::SecretKeyAbortingTargetSumLifetime8Dim46Base8; +#[cfg(not(feature = "test-config"))] +pub type FastKeyGenScheme = leansig_fast_keygen::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::SchemeAbortingTargetSumLifetime32Dim46Base8; +#[cfg(not(feature = "test-config"))] +pub type FastKeyGenSecretKey = leansig_fast_keygen::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::SecretKeyAbortingTargetSumLifetime32Dim46Base8; + +pub fn pubkey_merkle_root(pub_keys: &XmssPublicKey) -> [F; HASH_LEN_FE] { + assert_eq!(pub_keys.root().len(), HASH_LEN_FE); + array::from_fn(|i| F::from_canonical_checked(pub_keys.root()[i].as_canonical_u32()).unwrap()) +} + +pub fn pubkey_public_parameter(pub_keys: &XmssPublicKey) -> [F; PARAMETER_LEN] { + assert_eq!(pub_keys.parameter().len(), PARAMETER_LEN); + array::from_fn(|i| F::from_canonical_checked(pub_keys.parameter()[i].as_canonical_u32()).unwrap()) +} + +pub fn chain_tweak(slot: u32, chain_idx: u32, step: u32) -> [F; TWEAK_LEN_FE] { + let [t0, t1] = LeanSigTH::chain_tweak(slot, chain_idx as u8, step as u8).to_field_elements(); + [ + F::from_canonical_checked(t0.as_canonical_u32()).unwrap(), + F::from_canonical_checked(t1.as_canonical_u32()).unwrap(), + ] +} + +pub fn merkle_tweak(level: usize, pos_in_level: u32) -> [F; TWEAK_LEN_FE] { + let [t0, t1] = LeanSigTH::tree_tweak(level as u8, pos_in_level).to_field_elements(); + [ + F::from_canonical_checked(t0.as_canonical_u32()).unwrap(), + F::from_canonical_checked(t1.as_canonical_u32()).unwrap(), + ] +} + +pub fn xmss_merkle_path(sig: &XmssSignature) -> &Vec<[F; HASH_LEN_FE]> { + unsafe { std::mem::transmute(sig.path()) } +} + +pub fn xmss_randomness(sig: &XmssSignature) -> &[F; RAND_LEN_FE] { + unsafe { std::mem::transmute(sig.rho()) } +} + +pub fn xmmss_revealed_chain_tips(sig: &XmssSignature) -> &Vec<[F; HASH_LEN_FE]> { + unsafe { std::mem::transmute(sig.hashes()) } +} + +#[allow(clippy::result_unit_err)] +pub fn xmss_public_key_from_ssz(bytes: &[u8]) -> Result { + use ssz::Decode; + XmssPublicKey::from_ssz_bytes(bytes).map_err(|_| ()) +} + +pub fn xmss_public_key_to_ssz(pk: &XmssPublicKey) -> Vec { + use ssz::Encode; + pk.as_ssz_bytes() +} + +#[allow(clippy::result_unit_err)] +pub fn xmss_signature_from_ssz(bytes: &[u8]) -> Result { + use ssz::Decode; + XmssSignature::from_ssz_bytes(bytes).map_err(|_| ()) +} + +pub fn xmss_signature_to_ssz(sig: &XmssSignature) -> Vec { + use ssz::Encode; + sig.as_ssz_bytes() +} + +#[allow(clippy::result_unit_err)] +pub fn xmss_verify( + pk: &XmssPublicKey, + slot: u32, + message: &[u8; MESSAGE_LENGTH], + sig: &XmssSignature, +) -> Result<(), ()> { + if LeanSigScheme::verify(pk, slot, message, sig) { + Ok(()) + } else { + Err(()) + } +} + +pub fn xmss_encode_message(message: &[u8; MESSAGE_LENGTH]) -> [F; MSG_LEN_FE] { + let encoded = encode_message::(message); + array::from_fn(|i| F::from_canonical_checked(encoded[i].as_canonical_u32()).unwrap()) +} + +pub fn xmss_keygen_fast( + rng: &mut R, + activation_epoch: u32, + num_active_epochs: u32, +) -> (FastKeyGenSecretKey, XmssPublicKey) { + let (pk, sk) = FastKeyGenScheme::key_gen(rng, activation_epoch as usize, num_active_epochs as usize); + #[allow(clippy::missing_transmute_annotations)] + let pk = unsafe { std::mem::transmute(pk) }; + (sk, pk) +} + +#[allow(clippy::result_unit_err)] +pub fn xmss_sign_fast( + sk: &FastKeyGenSecretKey, + message: &[u8; MESSAGE_LENGTH], + slot: u32, +) -> Result { + unsafe { std::mem::transmute(FastKeyGenScheme::sign(sk, slot, message).map_err(|_| ())?) } +} diff --git a/crates/rec_aggregation/Cargo.toml b/crates/rec_aggregation/Cargo.toml index b1c7ca9a..53803089 100644 --- a/crates/rec_aggregation/Cargo.toml +++ b/crates/rec_aggregation/Cargo.toml @@ -8,12 +8,12 @@ workspace = true [features] prox-gaps-conjecture = ["lean_prover/prox-gaps-conjecture"] +test-config = ["leansig_wrapper/test-config"] [dependencies] utils.workspace = true -xmss.workspace = true rand.workspace = true - +leansig_wrapper.workspace = true tracing.workspace = true air.workspace = true sub_protocols.workspace = true @@ -24,3 +24,4 @@ backend.workspace = true postcard.workspace = true lz4_flex.workspace = true serde.workspace = true +sha3.workspace = true \ No newline at end of file diff --git a/crates/rec_aggregation/cached_bytecode.bin b/crates/rec_aggregation/cached_bytecode.bin new file mode 100644 index 00000000..ca8eaa10 Binary files /dev/null and b/crates/rec_aggregation/cached_bytecode.bin differ diff --git a/crates/rec_aggregation/cached_bytecode_prox_gaps.bin b/crates/rec_aggregation/cached_bytecode_prox_gaps.bin new file mode 100644 index 00000000..962e1927 Binary files /dev/null and b/crates/rec_aggregation/cached_bytecode_prox_gaps.bin differ diff --git a/crates/rec_aggregation/cached_bytecode_test_config.bin b/crates/rec_aggregation/cached_bytecode_test_config.bin new file mode 100644 index 00000000..b6d5fad2 Binary files /dev/null and b/crates/rec_aggregation/cached_bytecode_test_config.bin differ diff --git a/crates/rec_aggregation/main.py b/crates/rec_aggregation/main.py index f7635c31..3235ed7f 100644 --- a/crates/rec_aggregation/main.py +++ b/crates/rec_aggregation/main.py @@ -8,7 +8,7 @@ MAX_N_DUPS = 2**15 INNER_PUB_MEM_SIZE = 2**INNER_PUBLIC_MEMORY_LOG_SIZE -BYTECODE_CLAIM_OFFSET = 1 + DIGEST_LEN + 2 + MESSAGE_LEN + N_MERKLE_CHUNKS +BYTECODE_CLAIM_OFFSET = 1 + DIGEST_LEN + MESSAGE_LEN + N_MERKLE_CHUNKS + N_ALL_TWEAKS def main(): @@ -19,10 +19,8 @@ def main(): assert n_sigs - 1 < MAX_N_SIGS pubkeys_hash_expected = pub_mem + 1 message = pubkeys_hash_expected + DIGEST_LEN - slot_ptr = message + MESSAGE_LEN - slot_lo = slot_ptr[0] - slot_hi = slot_ptr[1] - merkle_chunks_for_slot = slot_ptr + 2 + merkle_chunks_for_slot = message + MESSAGE_LEN + all_tweaks = merkle_chunks_for_slot + N_MERKLE_CHUNKS bytecode_claim_output = pub_mem + BYTECODE_CLAIM_OFFSET priv_start: Imu @@ -51,7 +49,7 @@ def main(): bytecode_value_hint = source_data + 1 + n_sub inner_pub_mem = bytecode_value_hint + DIM proof_transcript = inner_pub_mem + INNER_PUB_MEM_SIZE - non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sigs, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem) + non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sigs, message, merkle_chunks_for_slot, all_tweaks, pub_mem) copy_8(non_reserved_inner + 1, pubkeys_hash_expected) bytecode_claims = Array(2) bytecode_claims[0] = non_reserved_inner + BYTECODE_CLAIM_OFFSET @@ -59,9 +57,20 @@ def main(): reduce_bytecode_claims(bytecode_claims, 2, bytecode_claim_output, bytecode_sumcheck_proof) return - # General path - computed_pubkeys_hash = slice_hash_with_iv_dynamic_unroll(all_pubkeys, n_sigs * DIGEST_LEN, MAX_LOG_MEMORY_SIZE) - copy_8(computed_pubkeys_hash, pubkeys_hash_expected) + # Hash pubkeys via Poseidon24 sponge: capacity(9) || root(8) || pp(5) || zeros(2) + pk_hash_cap: Mut = Array(9) + set_to_9_zeros(pk_hash_cap) + for i in range(0, n_sigs): + pk = all_pubkeys + i * PUBKEY_SIZE + rate = Array(15) + copy_8(pk, rate) + copy_5(pk + DIGEST_LEN, rate + 8) + rate[13] = 0 + rate[14] = 0 + new_cap = Array(9) + poseidon24_compress_0_9(pk_hash_cap, rate, new_cap) + pk_hash_cap = new_cap + copy_8(pk_hash_cap, pubkeys_hash_expected) # Buffer for partition verification n_total = n_sigs + n_dup @@ -76,10 +85,11 @@ def main(): assert idx < n_total buffer[idx] = i # Verify raw XMSS signatures - pk = all_pubkeys + idx * DIGEST_LEN + pk = all_pubkeys + idx * PUBKEY_SIZE + pp = pk + DIGEST_LEN sig = Array(SIG_SIZE) hint_xmss(sig) - xmss_verify(pk, message, sig, slot_lo, slot_hi, merkle_chunks_for_slot) + xmss_verify(pk, pp, message, sig, all_tweaks, merkle_chunks_for_slot) counter: Mut = n_raw_xmss @@ -97,25 +107,24 @@ def main(): inner_pub_mem = bytecode_value_hint + DIM proof_transcript = inner_pub_mem + INNER_PUB_MEM_SIZE - idx0 = sub_indices[0] - assert idx0 < n_total - buffer[idx0] = counter - counter += 1 - pk0 = all_pubkeys + idx0 * DIGEST_LEN - running_hash: Mut = Array(DIGEST_LEN) - poseidon16_compress(ZERO_VEC_PTR, pk0, running_hash) - - for j in dynamic_unroll(1, n_sub, log2_ceil(MAX_N_SIGS)): + running_hash: Mut = Array(9) + set_to_9_zeros(running_hash) + for j in range(0, n_sub): idx = sub_indices[j] assert idx < n_total buffer[idx] = counter counter += 1 - pk = all_pubkeys + idx * DIGEST_LEN - new_hash = Array(DIGEST_LEN) - poseidon16_compress(running_hash, pk, new_hash) - running_hash = new_hash - - non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sub, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem) + pk = all_pubkeys + idx * PUBKEY_SIZE + rate = Array(15) + copy_8(pk, rate) + copy_5(pk + DIGEST_LEN, rate + 8) + rate[13] = 0 + rate[14] = 0 + new_cap = Array(9) + poseidon24_compress_0_9(running_hash, rate, new_cap) + running_hash = new_cap + + non_reserved_inner = verify_inner_pub_mem(inner_pub_mem, n_sub, message, merkle_chunks_for_slot, all_tweaks, pub_mem) copy_8(running_hash, non_reserved_inner + 1) # Collect inner bytecode claim from inner pub mem @@ -182,7 +191,7 @@ def reduce_bytecode_claims(bytecode_claims, n_bytecode_claims, bytecode_claim_ou return -def verify_inner_pub_mem(inner_pub_mem, n_sub, message, slot_lo, slot_hi, merkle_chunks_for_slot, pub_mem): +def verify_inner_pub_mem(inner_pub_mem, n_sub, message, merkle_chunks_for_slot, all_tweaks, pub_mem): debug_assert(NONRESERVED_PROGRAM_INPUT_START % DIM == 0) for i in unroll(0, NONRESERVED_PROGRAM_INPUT_START / DIM): copy_5(i * DIM, inner_pub_mem + i * DIM) @@ -192,10 +201,15 @@ def verify_inner_pub_mem(inner_pub_mem, n_sub, message, slot_lo, slot_hi, merkle debug_assert(MESSAGE_LEN <= 2 * DIM) copy_5(message, inner_msg) copy_5(message + (MESSAGE_LEN - DIM), inner_msg + (MESSAGE_LEN - DIM)) - inner_msg[MESSAGE_LEN] = slot_lo - inner_msg[MESSAGE_LEN + 1] = slot_hi for k in unroll(0, N_MERKLE_CHUNKS): - inner_msg[MESSAGE_LEN + 2 + k] = merkle_chunks_for_slot[k] + inner_msg[MESSAGE_LEN+ k] = merkle_chunks_for_slot[k] + # Copy all pre-computed tweaks to inner pub mem + inner_all_tweaks = inner_msg + MESSAGE_LEN + N_MERKLE_CHUNKS + n_tweak_copy5 = div_floor(N_ALL_TWEAKS, DIM) + for k in unroll(0, n_tweak_copy5): + copy_5(all_tweaks + k * DIM, inner_all_tweaks + k * DIM) + for k in unroll(0, N_ALL_TWEAKS % DIM): + inner_all_tweaks[n_tweak_copy5 * DIM + k] = all_tweaks[n_tweak_copy5 * DIM + k] own_bytecode_hash = pub_mem + BYTECODE_HASH_OFFSET copy_8(own_bytecode_hash, non_reserved_inner + BYTECODE_HASH_OFFSET) return non_reserved_inner diff --git a/crates/rec_aggregation/recursion.py b/crates/rec_aggregation/recursion.py index 52964da4..96db58b2 100644 --- a/crates/rec_aggregation/recursion.py +++ b/crates/rec_aggregation/recursion.py @@ -171,11 +171,13 @@ def recursion(inner_public_memory, proof_transcript, bytecode_value_hint): ) offset += two_exp(log_bytecode_padded) - # Dispatch based on table height ordering (sorted by descending height) + # Dispatch based on table height ordering (sorted by descending height). + # Table 3 (poseidon24) is always last (smallest) if maximum(table_log_heights[1], table_log_heights[2]) == table_log_heights[1]: continue_recursion_ordered( 1, 2, + 3, fs, offset, retrieved_numerators_value, @@ -207,6 +209,7 @@ def recursion(inner_public_memory, proof_transcript, bytecode_value_hint): continue_recursion_ordered( 2, 1, + 3, fs, offset, retrieved_numerators_value, @@ -242,6 +245,7 @@ def recursion(inner_public_memory, proof_transcript, bytecode_value_hint): def continue_recursion_ordered( second_table, third_table, + fourth_table, fs, offset, retrieved_numerators_value, @@ -278,8 +282,8 @@ def continue_recursion_ordered( for i in unroll(0, N_TABLES): pcs_values.push(DynArray([])) pcs_values[i].push(DynArray([])) - total_num_cols = NUM_COLS_AIR[i] - for _ in unroll(0, total_num_cols): + total_num_cols_for_logup = NUM_COLS_AIR[i] + for _ in unroll(0, total_num_cols_for_logup): pcs_values[i][0].push(DynArray([])) for sorted_pos in unroll(0, N_TABLES): @@ -290,6 +294,8 @@ def continue_recursion_ordered( table_index = second_table if sorted_pos == 2: table_index = third_table + if sorted_pos == 3: + table_index = fourth_table # I] Bus (data flow between tables) log_n_rows = table_log_heights[table_index] @@ -339,7 +345,6 @@ def continue_recursion_ordered( for i in unroll(0, len(LOOKUPS_VALUES[table_index][lookup_f_index])): fs, value_eval = fs_receive_ef_inlined(fs, 1) col_index = LOOKUPS_VALUES[table_index][lookup_f_index][i] - debug_assert(len(pcs_values[table_index][0][col_index]) == 0) pcs_values[table_index][0][col_index].push(value_eval) pref = multilinear_location_prefix(offset / n_rows, n_vars_logup_gkr - log_n_rows, point_gkr) # TODO there is some duplication here @@ -383,6 +388,8 @@ def continue_recursion_ordered( table_index = second_table if sorted_pos == 2: table_index = third_table + if sorted_pos == 3: + table_index = fourth_table log_n_rows = table_log_heights[table_index] bus_numerator_value = bus_numerators_values[sorted_pos] bus_denominator_value = bus_denominators_values[sorted_pos] @@ -480,6 +487,8 @@ def continue_recursion_ordered( table_index = second_table if sorted_pos == 2: table_index = third_table + if sorted_pos == 3: + table_index = fourth_table debug_assert(len(pcs_points[table_index]) == len(pcs_values[table_index])) for i in unroll(0, len(pcs_values[table_index])): for j in unroll(0, len(pcs_values[table_index][i])): @@ -581,6 +590,8 @@ def continue_recursion_ordered( table_index = second_table if sorted_pos == 2: table_index = third_table + if sorted_pos == 3: + table_index = fourth_table log_n_rows = table_log_heights[table_index] n_rows = table_heights[table_index] total_num_cols = NUM_COLS_AIR[table_index] @@ -672,7 +683,9 @@ def verify_gkr_quotient_step(fs: Mut, n_vars, point, claim_num, claim_den): alpha_mul_claim_den = mul_extension_ret(alpha, claim_den) num_plus_alpha_mul_claim_den = add_extension_ret(claim_num, alpha_mul_claim_den) postponed_point = Array((n_vars + 1) * DIM) - fs, postponed_value = sumcheck_verify_helper(fs, n_vars, num_plus_alpha_mul_claim_den, 3, postponed_point + DIM) + fs, postponed_value = match_range( + n_vars, range(0, 31), lambda s: sumcheck_verify_helper(fs, s, num_plus_alpha_mul_claim_den, 3, postponed_point + DIM) + ) fs, inner_evals = fs_receive_ef_inlined(fs, 4) a_num = inner_evals b_num = inner_evals + DIM @@ -730,7 +743,10 @@ def evaluate_air_constraints(table_index, inner_evals, air_alpha_powers, bus_bet res = evaluate_air_constraints_table_1(inner_evals, air_alpha_powers, bus_beta, logup_alphas_eq_poly) case 2: res = evaluate_air_constraints_table_2(inner_evals, air_alpha_powers, bus_beta, logup_alphas_eq_poly) + case 3: + res = evaluate_air_constraints_table_3(inner_evals, air_alpha_powers, bus_beta, logup_alphas_eq_poly) return res + EVALUATE_AIR_FUNCTIONS_PLACEHOLDER diff --git a/crates/rec_aggregation/src/benchmark.rs b/crates/rec_aggregation/src/benchmark.rs index 522bef14..6f374ce5 100644 --- a/crates/rec_aggregation/src/benchmark.rs +++ b/crates/rec_aggregation/src/benchmark.rs @@ -1,12 +1,12 @@ use backend::*; use lean_vm::*; +use leansig_wrapper::{XmssPublicKey, XmssSignature}; use std::io::{self, Write}; use std::time::Instant; use utils::ansi as s; -use xmss::signers_cache::{BENCHMARK_SLOT, get_benchmark_signatures, message_for_benchmark}; -use xmss::{XmssPublicKey, XmssSignature}; use crate::compilation::{get_aggregation_bytecode, init_aggregation_bytecode}; +use crate::signatures_cache::{BENCHMARK_MESSAGE, BENCHMARK_SLOT, get_benchmark_signatures}; use crate::{AggregatedXMSS, AggregationTopology, count_signers, xmss_aggregate}; fn count_nodes(topology: &AggregationTopology) -> usize { @@ -254,7 +254,7 @@ fn build_aggregation( let (global_pub_keys, result) = xmss_aggregate( &children, raw_xmss, - &message_for_benchmark(), + &BENCHMARK_MESSAGE, BENCHMARK_SLOT, topology.log_inv_rate, ); @@ -329,13 +329,7 @@ pub fn run_aggregation_benchmark(topology: &AggregationTopology, overlap: usize, build_aggregation(topology, 0, &mut display, &pub_keys, &signatures, overlap, tracing); // Verify root proof - crate::xmss_verify_aggregation( - &global_pub_keys, - &aggregated_sigs, - &message_for_benchmark(), - BENCHMARK_SLOT, - ) - .unwrap(); + crate::xmss_verify_aggregation(global_pub_keys, &aggregated_sigs, &BENCHMARK_MESSAGE, BENCHMARK_SLOT).unwrap(); time } diff --git a/crates/rec_aggregation/src/compilation.rs b/crates/rec_aggregation/src/compilation.rs index 3748ac6a..53120984 100644 --- a/crates/rec_aggregation/src/compilation.rs +++ b/crates/rec_aggregation/src/compilation.rs @@ -1,21 +1,38 @@ use backend::*; +use lean_compiler::instruction_encoder::field_representation; use lean_compiler::{CompilationFlags, ProgramSource, compile_program_with_flags}; use lean_prover::{ GRINDING_BITS, MAX_NUM_VARIABLES_TO_SEND_COEFFS, RS_DOMAIN_INITIAL_REDUCTION_FACTOR, WHIR_INITIAL_FOLDING_FACTOR, WHIR_SUBSEQUENT_FOLDING_FACTOR, default_whir_config, }; use lean_vm::*; +use leansig_wrapper::{ + LOG_LIFETIME, MSG_LEN_FE, PARAMETER_LEN, RAND_LEN_FE, TARGET_SUM, TWEAK_LEN_FE, V, W, WOTS_PUBKET_SPONGE_DOMAIN_SEP, +}; +use sha3::{Digest, Sha3_256}; use std::collections::{BTreeMap, HashMap}; use std::path::Path; use std::sync::OnceLock; use sub_protocols::{min_stacked_n_vars, total_whir_statements}; use tracing::instrument; use utils::Counter; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, RANDOMNESS_LEN_FE, TARGET_SUM, V, V_GRINDING, W}; use crate::{MERKLE_LEVELS_PER_CHUNK_FOR_SLOT, N_MERKLE_CHUNKS_FOR_SLOT}; static BYTECODE: OnceLock = OnceLock::new(); +const BYTECODE_GUESSED_LOG_SIZE: usize = 19; + +/// Format: `[32 bytes: SHA3-256 fingerprint][lz4(postcard(bytecode))]` +#[cfg(not(any(feature = "prox-gaps-conjecture", feature = "test-config")))] +const CACHED_BYTECODE_BYTES: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/cached_bytecode.bin")); +#[cfg(feature = "prox-gaps-conjecture")] +const CACHED_BYTECODE_BYTES: &[u8] = + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/cached_bytecode_prox_gaps.bin")); +#[cfg(feature = "test-config")] +const CACHED_BYTECODE_BYTES: &[u8] = + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/cached_bytecode_test_config.bin")); + +const FINGERPRINT_SIZE: usize = 32; // in bytes pub fn get_aggregation_bytecode() -> &'static Bytecode { BYTECODE @@ -24,36 +41,122 @@ pub fn get_aggregation_bytecode() -> &'static Bytecode { } pub fn init_aggregation_bytecode() { - BYTECODE.get_or_init(compile_main_program_self_referential); + BYTECODE.get_or_init(load_or_compile); } -fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> Bytecode { - let bytecode_point_n_vars = inner_program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); - let claim_data_size = (bytecode_point_n_vars + 1) * DIMENSION; - let claim_data_size_padded = claim_data_size.next_multiple_of(DIGEST_LEN); - // pub_input layout: n_sigs(1) + slice_hash(8) + slot_low(1) + slot_high(1) - // + message + merkle_chunks_for_slot + bytecode_claim_padded + bytecode_hash(8) - let pub_input_size = - 1 + DIGEST_LEN + 2 + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + claim_data_size_padded + DIGEST_LEN; - let inner_public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); - let replacements = build_replacements( - inner_program_log_size, - inner_public_memory_log_size, - bytecode_zero_eval, - pub_input_size, - ); +fn compute_source_fingerprint() -> [u8; FINGERPRINT_SIZE] { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + + // Collect all .py files in the crate root, sorted by name for determinism. + let mut py_files: Vec<(String, String)> = Vec::new(); + for entry in std::fs::read_dir(manifest_dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "py") { + let name = path.file_name().unwrap().to_string_lossy().to_string(); + let content = std::fs::read_to_string(&path).unwrap(); + py_files.push((name, content)); + } + } + py_files.sort(); - let filepath = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("main.py") - .to_str() - .unwrap() - .to_string(); - compile_program_with_flags(&ProgramSource::Filepath(filepath), CompilationFlags { replacements }) + let replacements = compute_replacements(BYTECODE_GUESSED_LOG_SIZE, F::ONE); + + let mut hasher = Sha3_256::new(); + for (name, content) in &py_files { + hasher.update(name.as_bytes()); + hasher.update(b"\0"); + hasher.update(content.as_bytes()); + hasher.update(b"\0"); + } + for (key, value) in &replacements { + hasher.update(key.as_bytes()); + hasher.update(b"\0"); + hasher.update(value.as_bytes()); + hasher.update(b"\0"); + } + hasher.finalize().into() +} + +/// Returns the cache file name for the active feature configuration. +fn cache_file_name() -> &'static str { + #[cfg(feature = "prox-gaps-conjecture")] + { + assert!( + !cfg!(feature = "test-config"), + "features `prox-gaps-conjecture` and `test-config` cannot be enabled at the same time" + ); + "cached_bytecode_prox_gaps.bin" + } + #[cfg(feature = "test-config")] + { + "cached_bytecode_test_config.bin" + } + #[cfg(not(any(feature = "prox-gaps-conjecture", feature = "test-config")))] + { + "cached_bytecode.bin" + } +} + +fn cache_file_path() -> std::path::PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join(cache_file_name()) +} + +fn load_or_compile() -> Bytecode { + let current_fp = compute_source_fingerprint(); + + if CACHED_BYTECODE_BYTES.len() > FINGERPRINT_SIZE { + let stored_fp: [u8; FINGERPRINT_SIZE] = CACHED_BYTECODE_BYTES[..FINGERPRINT_SIZE].try_into().unwrap(); + if current_fp == stored_fp { + let cache_data = &CACHED_BYTECODE_BYTES[FINGERPRINT_SIZE..]; + return deserialize_cache(cache_data).unwrap(); + } + } + let bytecode = compile_from_source(); + write_cache_file(&bytecode, ¤t_fp); + bytecode +} + +fn deserialize_cache(data: &[u8]) -> Option { + let decompressed = lz4_flex::decompress_size_prepended(data).ok()?; + let mut bytecode: Bytecode = postcard::from_bytes(&decompressed).ok()?; + rebuild_derived_fields(&mut bytecode); + Some(bytecode) +} + +fn write_cache_file(bytecode: &Bytecode, fingerprint: &[u8; FINGERPRINT_SIZE]) { + let mut out = Vec::from(fingerprint.as_slice()); + let encoded = postcard::to_allocvec(bytecode).expect("postcard serialization failed"); + out.extend_from_slice(&lz4_flex::compress_prepend_size(&encoded)); + std::fs::write(cache_file_path(), &out).expect("failed to write bytecode cache file"); +} + +fn rebuild_derived_fields(bytecode: &mut Bytecode) { + let padded_cols = N_INSTRUCTION_COLUMNS.next_power_of_two(); + let mut instructions_multilinear: Vec = bytecode + .instructions + .par_iter() + .flat_map_iter(|instr| { + let encoded = field_representation(instr); + encoded + .into_iter() + .chain(std::iter::repeat_n(F::ZERO, padded_cols - N_INSTRUCTION_COLUMNS)) + }) + .collect(); + instructions_multilinear.resize(instructions_multilinear.len().next_power_of_two(), F::ZERO); + let instructions_multilinear_packed = pack_extension( + &instructions_multilinear + .par_iter() + .map(|&pf| EF::from(pf)) + .collect::>(), + ); + bytecode.instructions_multilinear = instructions_multilinear; + bytecode.instructions_multilinear_packed = instructions_multilinear_packed; } #[instrument(skip_all)] -fn compile_main_program_self_referential() -> Bytecode { - let mut log_size_guess = 19; +fn compile_from_source() -> Bytecode { + let mut log_size_guess = BYTECODE_GUESSED_LOG_SIZE; let bytecode_zero_eval = F::ONE; loop { let bytecode = compile_main_program(log_size_guess, bytecode_zero_eval); @@ -71,6 +174,33 @@ fn compile_main_program_self_referential() -> Bytecode { } } +fn compute_replacements(inner_program_log_size: usize, bytecode_zero_eval: F) -> BTreeMap { + let bytecode_point_n_vars = inner_program_log_size + log2_ceil_usize(N_INSTRUCTION_COLUMNS); + let claim_data_size = ((bytecode_point_n_vars + 1) * DIMENSION).next_multiple_of(DIGEST_LEN); + let claim_data_size_padded = claim_data_size.next_multiple_of(DIGEST_LEN); + let n_all_tweaks_fe = (1 + V * (1 << W) + 1 + LOG_LIFETIME) * TWEAK_LEN_FE; // encoding + chain + leaf + merkle + // pub_input layout: n_sigs(1) + slice_hash(8) + message(9) + merkle_chunks(8) + all_tweaks + bytecode_claim(padded) + bytecode_hash(8) + let pub_input_size = + 1 + DIGEST_LEN + MSG_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + n_all_tweaks_fe + claim_data_size_padded + DIGEST_LEN; + let inner_public_memory_log_size = log2_ceil_usize(NONRESERVED_PROGRAM_INPUT_START + pub_input_size); + build_replacements( + inner_program_log_size, + inner_public_memory_log_size, + bytecode_zero_eval, + pub_input_size, + ) +} + +fn compile_main_program(inner_program_log_size: usize, bytecode_zero_eval: F) -> Bytecode { + let replacements = compute_replacements(inner_program_log_size, bytecode_zero_eval); + let filepath = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("main.py") + .to_str() + .unwrap() + .to_string(); + compile_program_with_flags(&ProgramSource::Filepath(filepath), CompilationFlags { replacements }) +} + fn build_replacements( inner_program_log_size: usize, inner_public_memory_log_size: usize, @@ -344,16 +474,28 @@ fn build_replacements( // XMSS-specific replacements replacements.insert("V_PLACEHOLDER".to_string(), V.to_string()); - replacements.insert("V_GRINDING_PLACEHOLDER".to_string(), V_GRINDING.to_string()); replacements.insert("W_PLACEHOLDER".to_string(), W.to_string()); + replacements.insert("PUBLIC_PARAM_LEN_PLACEHOLDER".to_string(), PARAMETER_LEN.to_string()); + replacements.insert("TWEAK_LEN_PLACEHOLDER".to_string(), TWEAK_LEN_FE.to_string()); replacements.insert("TARGET_SUM_PLACEHOLDER".to_string(), TARGET_SUM.to_string()); replacements.insert("LOG_LIFETIME_PLACEHOLDER".to_string(), LOG_LIFETIME.to_string()); - replacements.insert("MESSAGE_LEN_PLACEHOLDER".to_string(), MESSAGE_LEN_FE.to_string()); - replacements.insert("RANDOMNESS_LEN_PLACEHOLDER".to_string(), RANDOMNESS_LEN_FE.to_string()); + replacements.insert("MESSAGE_LEN_PLACEHOLDER".to_string(), MSG_LEN_FE.to_string()); + replacements.insert("RANDOMNESS_LEN_PLACEHOLDER".to_string(), RAND_LEN_FE.to_string()); replacements.insert( "MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER".to_string(), MERKLE_LEVELS_PER_CHUNK_FOR_SLOT.to_string(), ); + replacements.insert( + "WOTS_PUBKET_SPONGE_DOMAIN_SEP_PLACEHOLDER".to_string(), + format!( + "[{}]", + WOTS_PUBKET_SPONGE_DOMAIN_SEP + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(", ") + ), + ); // Bytecode zero eval replacements.insert( @@ -369,6 +511,7 @@ fn all_air_evals_in_zk_dsl() -> String { res += &air_eval_in_zk_dsl(ExecutionTable:: {}); res += &air_eval_in_zk_dsl(ExtensionOpPrecompile:: {}); res += &air_eval_in_zk_dsl(Poseidon16Precompile:: {}); + res += &air_eval_in_zk_dsl(Poseidon24Precompile:: {}); res } diff --git a/crates/rec_aggregation/src/lib.rs b/crates/rec_aggregation/src/lib.rs index 9fc484f9..31aa5a0d 100644 --- a/crates/rec_aggregation/src/lib.rs +++ b/crates/rec_aggregation/src/lib.rs @@ -5,9 +5,10 @@ use lean_prover::prove_execution::prove_execution; use lean_prover::verify_execution::ProofVerificationDetails; use lean_prover::verify_execution::verify_execution; use lean_vm::*; +use leansig_wrapper::*; use tracing::instrument; +use utils::poseidon24_compress_0_9; use utils::{build_prover_state, get_poseidon16, poseidon_compress_slice, poseidon16_compress_pair}; -use xmss::{LOG_LIFETIME, MESSAGE_LEN_FE, SIG_SIZE_FE, XmssPublicKey, XmssSignature, slot_to_field_elements}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -16,8 +17,12 @@ pub use crate::compilation::{get_aggregation_bytecode, init_aggregation_bytecode pub mod benchmark; mod compilation; +pub mod signatures_cache; const MERKLE_LEVELS_PER_CHUNK_FOR_SLOT: usize = 4; +const _: () = { + assert!(LOG_LIFETIME.is_multiple_of(MERKLE_LEVELS_PER_CHUNK_FOR_SLOT)); +}; const N_MERKLE_CHUNKS_FOR_SLOT: usize = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK_FOR_SLOT; #[derive(Debug, Clone)] @@ -33,9 +38,18 @@ pub(crate) fn count_signers(topology: &AggregationTopology, overlap: usize) -> u topology.raw_xmss + child_count - overlap * n_overlaps } +/// Hash pubkeys via Poseidon24 sponge: capacity(9) || root(8) || pp(5) || zeros(2) per key. pub fn hash_pubkeys(pub_keys: &[XmssPublicKey]) -> [F; DIGEST_LEN] { - let flat: Vec = pub_keys.iter().flat_map(|pk| pk.merkle_root.iter().copied()).collect(); - poseidon_compress_slice(&flat, true) + let mut capacity = [F::ZERO; 9]; + for pk in pub_keys { + let mut input = [F::ZERO; 24]; + input[..9].copy_from_slice(&capacity); + input[9..17].copy_from_slice(&pubkey_merkle_root(pk)); + input[17..22].copy_from_slice(&pubkey_public_parameter(pk)); + // input[22..24] = zeros (padding) + capacity = poseidon24_compress_0_9(input); + } + capacity[..DIGEST_LEN].try_into().unwrap() } fn compute_merkle_chunks_for_slot(slot: u32) -> Vec { @@ -54,10 +68,40 @@ fn compute_merkle_chunks_for_slot(slot: u32) -> Vec { chunks } +/// Pre-compute ALL tweaks for a slot: +/// encoding(2) + chain(V*CHAIN_LENGTH*2) + leaf_tweak(2) + merkle(LOG_LIFETIME*2). +fn compute_all_tweaks_for_slot(slot: u32) -> Vec { + let n = TWEAK_LEN_FE + V * BASE * TWEAK_LEN_FE + TWEAK_LEN_FE + LOG_LIFETIME * TWEAK_LEN_FE; + let mut tweaks = Vec::with_capacity(n); + // Encoding tweak: encode_epoch(slot) = ((slot << 8) | TWEAK_SEPARATOR_MSG) in base-p + let acc = ((slot as u64) << 8) | 0x02u64; + let [t0, t1] = [F::from_u64(acc % F::ORDER_U64), F::from_u64(acc / F::ORDER_U64)]; + tweaks.extend([t0, t1]); + // Chain tweaks + for chain_idx in 0..V { + for step in 0..BASE { + let [t0, t1] = chain_tweak(slot, chain_idx as u32, step as u32); + tweaks.extend([t0, t1]); + } + } + // Leaf tweak: tree_tweak(0, slot) for hashing chain ends into a leaf node + let [t0, t1] = merkle_tweak(0, slot); + tweaks.extend([t0, t1]); + // Merkle tweaks: tree_tweak(l+1, slot >> (l+1)) for each level l + for level in 0..LOG_LIFETIME { + let parent_level = level + 1; + let parent_index = if parent_level < 32 { slot >> parent_level } else { 0 }; + let [t0, t1] = merkle_tweak(parent_level, parent_index); + tweaks.extend([t0, t1]); + } + assert_eq!(tweaks.len(), n); + tweaks +} + fn build_non_reserved_public_input( n_sigs: usize, slice_hash: &[F; DIGEST_LEN], - message: &[F; MESSAGE_LEN_FE], + message: &[u8; MESSAGE_LENGTH], slot: u32, bytecode_claim_output: &[F], bytecode_hash: &[F; DIGEST_LEN], @@ -65,11 +109,9 @@ fn build_non_reserved_public_input( let mut pi = vec![]; pi.push(F::from_usize(n_sigs)); pi.extend_from_slice(slice_hash); - pi.extend_from_slice(message); - let [slot_lo, slot_hi] = slot_to_field_elements(slot); - pi.push(slot_lo); - pi.push(slot_hi); + pi.extend(xmss_encode_message(message)); pi.extend(compute_merkle_chunks_for_slot(slot)); + pi.extend(compute_all_tweaks_for_slot(slot)); pi.extend_from_slice(bytecode_claim_output); pi.extend(std::iter::repeat_n( F::ZERO, @@ -81,9 +123,9 @@ fn build_non_reserved_public_input( fn encode_xmss_signature(sig: &XmssSignature) -> Vec { let mut data = vec![]; - data.extend(sig.wots_signature.randomness.to_vec()); - data.extend(sig.wots_signature.chain_tips.iter().flat_map(|digest| digest.to_vec())); - for neighbor in &sig.merkle_proof { + data.extend_from_slice(xmss_randomness(sig)); + data.extend(xmmss_revealed_chain_tips(sig).iter().flat_map(|digest| digest.to_vec())); + for neighbor in xmss_merkle_path(sig) { data.extend(neighbor.to_vec()); } assert_eq!(data.len(), SIG_SIZE_FE); @@ -110,7 +152,7 @@ impl AggregatedXMSS { postcard::from_bytes(&decompressed).ok() } - pub fn public_input(&self, pub_keys: &[XmssPublicKey], message: &[F; MESSAGE_LEN_FE], slot: u32) -> Vec { + pub fn public_input(&self, pub_keys: &[XmssPublicKey], message: &[u8; MESSAGE_LENGTH], slot: u32) -> Vec { let bytecode = get_aggregation_bytecode(); let bytecode_point_n_vars = bytecode.log_size() + log2_ceil_usize(N_INSTRUCTION_COLUMNS); let bytecode_claim_size = (bytecode_point_n_vars + 1) * DIMENSION; @@ -128,7 +170,11 @@ impl AggregatedXMSS { claim } }; + let bytecode_claim_size_padded = bytecode_claim_size.next_multiple_of(DIGEST_LEN); assert_eq!(bytecode_claim_output.len(), bytecode_claim_size); + // Pad to match the public input layout + let mut bytecode_claim_output = bytecode_claim_output; + bytecode_claim_output.resize(bytecode_claim_size_padded, F::ZERO); let slice_hash = hash_pubkeys(pub_keys); @@ -144,15 +190,15 @@ impl AggregatedXMSS { } pub fn xmss_verify_aggregation( - pub_keys: &[XmssPublicKey], + pub_keys: Vec, agg_sig: &AggregatedXMSS, - message: &[F; MESSAGE_LEN_FE], + message: &[u8; MESSAGE_LENGTH], slot: u32, ) -> Result { - if !pub_keys.is_sorted() { - return Err(ProofError::InvalidProof); - } - let public_input = agg_sig.public_input(pub_keys, message, slot); + let mut pub_keys = pub_keys; + pub_keys.sort(); + + let public_input = agg_sig.public_input(&pub_keys, message, slot); let bytecode = get_aggregation_bytecode(); verify_execution(bytecode, &public_input, agg_sig.proof.clone()).map(|(details, _)| details) } @@ -162,12 +208,21 @@ pub fn xmss_verify_aggregation( pub fn xmss_aggregate( children: &[(&[XmssPublicKey], AggregatedXMSS)], mut raw_xmss: Vec<(XmssPublicKey, XmssSignature)>, - message: &[F; MESSAGE_LEN_FE], + message: &[u8; MESSAGE_LENGTH], slot: u32, log_inv_rate: usize, ) -> (Vec, AggregatedXMSS) { raw_xmss.sort_by(|(a, _), (b, _)| a.cmp(b)); - raw_xmss.dedup_by(|(a, _), (b, _)| a.merkle_root == b.merkle_root); + raw_xmss.dedup_by(|(a, _), (b, _)| a == b); + + let children: Vec<(Vec, &AggregatedXMSS)> = children + .iter() + .map(|&(pks, ref agg)| { + let mut v = pks.to_vec(); + v.sort(); + (v, agg) + }) + .collect(); let n_recursions = children.len(); let raw_count = raw_xmss.len(); @@ -179,9 +234,9 @@ pub fn xmss_aggregate( // Build global_pub_keys as sorted deduplicated union let mut global_pub_keys: Vec = raw_xmss.iter().map(|(pk, _)| pk.clone()).collect(); - for (child_pub_keys, _) in children.iter() { - assert!(child_pub_keys.is_sorted(), "child pub_keys must be sorted"); - global_pub_keys.extend_from_slice(child_pub_keys); + for (child_pubkeys, _) in children.iter() { + assert!(child_pubkeys.is_sorted(), "child pub_keys must be sorted"); + global_pub_keys.extend_from_slice(child_pubkeys); } global_pub_keys.sort(); global_pub_keys.dedup(); @@ -191,8 +246,8 @@ pub fn xmss_aggregate( let mut child_pub_inputs = vec![]; let mut child_bytecode_evals = vec![]; let mut child_raw_proofs = vec![]; - for (child_pub_keys, child) in children { - let child_pub_input = child.public_input(child_pub_keys, message, slot); + for (child_pubkeys, child) in &children { + let child_pub_input = child.public_input(child_pubkeys, message, slot); let (verif, raw_proof) = verify_execution(bytecode, &child_pub_input, child.proof.clone()).unwrap(); child_bytecode_evals.push(verif.bytecode_evaluation); child_pub_inputs.push(child_pub_input); @@ -201,7 +256,8 @@ pub fn xmss_aggregate( // Bytecode sumcheck reduction let (bytecode_claim_output, bytecode_point, final_sumcheck_transcript) = if n_recursions > 0 { - let bytecode_claim_offset = 1 + DIGEST_LEN + 2 + MESSAGE_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT; + let n_all_tweaks_fe = TWEAK_LEN_FE + V * BASE * TWEAK_LEN_FE + TWEAK_LEN_FE + LOG_LIFETIME * TWEAK_LEN_FE; + let bytecode_claim_offset = 1 + DIGEST_LEN + MSG_LEN_FE + N_MERKLE_CHUNKS_FOR_SLOT + n_all_tweaks_fe; let mut claims = vec![]; for (i, _child) in children.iter().enumerate() { let first_claim = extract_bytecode_claim_from_public_input( @@ -308,15 +364,15 @@ pub fn xmss_aggregate( } // Sources 1..n_recursions: recursive children - for (i, (child_pub_keys, _)) in children.iter().enumerate() { - let mut block = vec![F::from_usize(child_pub_keys.len())]; - for pubkey in *child_pub_keys { - if claimed.insert(pubkey.clone()) { - let pos = global_pub_keys.binary_search(pubkey).unwrap(); + for (i, (child_pubkeys, _)) in children.iter().enumerate() { + let mut block = vec![F::from_usize(child_pubkeys.len())]; + for pubkeykey in child_pubkeys { + if claimed.insert(pubkeykey.clone()) { + let pos = global_pub_keys.binary_search(pubkeykey).unwrap(); block.push(F::from_usize(pos)); } else { block.push(F::from_usize(n_sigs + dup_pub_keys.len())); - dup_pub_keys.push(pubkey.clone()); + dup_pub_keys.push(pubkeykey.clone()); } } @@ -332,7 +388,8 @@ pub fn xmss_aggregate( } let n_dup = dup_pub_keys.len(); - let pubkeys_block_size = n_sigs * DIGEST_LEN + n_dup * DIGEST_LEN; + let pubkey_fe_size = DIGEST_LEN + PARAMETER_LEN; + let pubkeys_block_size = (n_sigs + n_dup) * pubkey_fe_size; // Compute absolute memory addresses for each source block let sources_start = pubkeys_start + pubkeys_block_size; @@ -355,10 +412,12 @@ pub fn xmss_aggregate( assert_eq!(private_input.len(), header_size); for pk in &global_pub_keys { - private_input.extend_from_slice(&pk.merkle_root); + private_input.extend_from_slice(&pubkey_merkle_root(pk)); + private_input.extend_from_slice(&pubkey_public_parameter(pk)); } for pk in &dup_pub_keys { - private_input.extend_from_slice(&pk.merkle_root); + private_input.extend_from_slice(&pubkey_merkle_root(pk)); + private_input.extend_from_slice(&pubkey_public_parameter(pk)); } for block in &source_blocks { private_input.extend_from_slice(block); diff --git a/crates/xmss/src/signers_cache.rs b/crates/rec_aggregation/src/signatures_cache.rs similarity index 86% rename from crates/xmss/src/signers_cache.rs rename to crates/rec_aggregation/src/signatures_cache.rs index adaa1038..d42285f7 100644 --- a/crates/xmss/src/signers_cache.rs +++ b/crates/rec_aggregation/src/signatures_cache.rs @@ -6,8 +6,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Instant; use backend::*; +use rand::SeedableRng; use rand::rngs::StdRng; -use rand::{RngExt, SeedableRng}; use serde::{Deserialize, Serialize}; use crate::*; @@ -19,12 +19,12 @@ pub fn get_benchmark_signatures() -> &'static Vec<(XmssPublicKey, XmssSignature) } pub const BENCHMARK_SLOT: u32 = 111; +pub const BENCHMARK_MESSAGE: [u8; MESSAGE_LENGTH] = [ + 78, 32, 21, 11, 1, 76, 255, 254, 0, 0, 22, 11, 11, 87, 87, 32, 11, 32, 11, 76, 23, 12, 11, 2, 2, 2, 2, 2, 2, 3, 4, + 5, +]; pub const NUM_BENCHMARK_SIGNERS: usize = 10000; -pub fn message_for_benchmark() -> [F; MESSAGE_LEN_FE] { - std::array::from_fn(F::from_usize) -} - #[derive(Serialize, Deserialize)] struct SignersCacheFile { signatures: Vec<(XmssPublicKey, XmssSignature)>, @@ -32,10 +32,11 @@ struct SignersCacheFile { fn cache_footprint(first_pubkey: &XmssPublicKey) -> u64 { let mut hasher = DefaultHasher::new(); + cfg!(feature = "test-config").hash(&mut hasher); NUM_BENCHMARK_SIGNERS.hash(&mut hasher); BENCHMARK_SLOT.hash(&mut hasher); - message_for_benchmark().hash(&mut hasher); - first_pubkey.merkle_root.hash(&mut hasher); + BENCHMARK_MESSAGE.hash(&mut hasher); + first_pubkey.hash(&mut hasher); hasher.finish() } @@ -49,8 +50,8 @@ fn compute_signer(index: usize) -> (XmssPublicKey, XmssSignature) { let mut rng = StdRng::seed_from_u64(index as u64); let key_start = BENCHMARK_SLOT; let key_end = BENCHMARK_SLOT + 1; - let (sk, pk) = xmss_key_gen(rng.random(), key_start, key_end).unwrap(); - let sig = xmss_sign(&mut rng, &sk, &message_for_benchmark(), BENCHMARK_SLOT).unwrap(); + let (sk, pk) = xmss_keygen_fast(&mut rng, key_start, key_end); + let sig = xmss_sign_fast(&sk, &BENCHMARK_MESSAGE, BENCHMARK_SLOT).unwrap(); (pk, sig) } @@ -111,7 +112,7 @@ fn gen_benchmark_signers_cache() -> Vec<(XmssPublicKey, XmssSignature)> { fn test_signature_cache() { let signatures = get_benchmark_signatures(); signatures.par_iter().enumerate().for_each(|(i, (pk, sig))| { - xmss_verify(pk, &message_for_benchmark(), sig, BENCHMARK_SLOT) + xmss_verify(pk, BENCHMARK_SLOT, &BENCHMARK_MESSAGE, sig) .unwrap_or_else(|_| panic!("Signature {} failed to verify", i)); }); } diff --git a/crates/rec_aggregation/utils.py b/crates/rec_aggregation/utils.py index 18242ceb..33193071 100644 --- a/crates/rec_aggregation/utils.py +++ b/crates/rec_aggregation/utils.py @@ -17,10 +17,10 @@ def div_ceil_dynamic(a, b: Const): def powers(alpha, n): # alpha: EF # n: F - assert n < 400 + assert n < 512 assert 0 < n # 2**log2_ceil(i) is not really necessary but helps reduce byetcode size (traedoff cycles / bytecode size) - res = match_range(n, range(1, 400), lambda i: powers_const(alpha, 2**log2_ceil(i))) + res = match_range(n, range(1, 512), lambda i: powers_const(alpha, 2**log2_ceil(i))) return res @@ -337,6 +337,14 @@ def set_to_8_zeros(a): return +@inline +def set_to_9_zeros(a): + zero_ptr = ZERO_VEC_PTR + dot_product_ee(a, ONE_EF_PTR, zero_ptr) + dot_product_ee(a + (9 - DIM), ONE_EF_PTR, zero_ptr) + return + + @inline def copy_8(a, b): dot_product_ee(a, ONE_EF_PTR, b) @@ -344,6 +352,14 @@ def copy_8(a, b): return +@inline +def copy_15(a, b): + copy_5(a, b) + copy_5(a + 5, b + 5) + copy_5(a + 10, b + 10) + return + + @inline def copy_16(a, b): dot_product_ee(a, ONE_EF_PTR, b) diff --git a/crates/rec_aggregation/whir.py b/crates/rec_aggregation/whir.py index 0ae71ca9..133cad9d 100644 --- a/crates/rec_aggregation/whir.py +++ b/crates/rec_aggregation/whir.py @@ -167,14 +167,15 @@ def whir_open( return fs, folding_randomness_global, s, final_value, end_sum -def sumcheck_verify(fs: Mut, n_steps, claimed_sum, degree: Const): +@inline +def sumcheck_verify(fs, n_steps, claimed_sum, degree): challenges = Array(n_steps * DIM) - fs, new_claimed_sum = sumcheck_verify_helper(fs, n_steps, claimed_sum, degree, challenges) - return fs, challenges, new_claimed_sum + new_fs, new_claimed_sum = match_range(n_steps, range(0, 31), lambda s: sumcheck_verify_helper(fs, s, claimed_sum, degree, challenges)) + return new_fs, challenges, new_claimed_sum -def sumcheck_verify_helper(fs: Mut, n_steps, claimed_sum: Mut, degree: Const, challenges): - for sc_round in range(0, n_steps): +def sumcheck_verify_helper(fs: Mut, n_steps: Const, claimed_sum: Mut, degree: Const, challenges): + for sc_round in unroll(0, n_steps): fs, poly = fs_receive_ef_inlined(fs, degree + 1) sum_over_boolean_hypercube = polynomial_sum_at_0_and_1(poly, degree) copy_5(sum_over_boolean_hypercube, claimed_sum) diff --git a/crates/rec_aggregation/xmss_aggregate.py b/crates/rec_aggregation/xmss_aggregate.py index 88ecdce9..4e9a88a2 100644 --- a/crates/rec_aggregation/xmss_aggregate.py +++ b/crates/rec_aggregation/xmss_aggregate.py @@ -2,131 +2,203 @@ from utils import * V = V_PLACEHOLDER -V_GRINDING = V_GRINDING_PLACEHOLDER W = W_PLACEHOLDER CHAIN_LENGTH = 2**W TARGET_SUM = TARGET_SUM_PLACEHOLDER LOG_LIFETIME = LOG_LIFETIME_PLACEHOLDER MESSAGE_LEN = MESSAGE_LEN_PLACEHOLDER RANDOMNESS_LEN = RANDOMNESS_LEN_PLACEHOLDER +PUBLIC_PARAM_LEN = PUBLIC_PARAM_LEN_PLACEHOLDER +TWEAK_LEN = TWEAK_LEN_PLACEHOLDER +PUBKEY_SIZE = DIGEST_LEN + PUBLIC_PARAM_LEN SIG_SIZE = RANDOMNESS_LEN + (V + LOG_LIFETIME) * DIGEST_LEN -NUM_ENCODING_FE = div_ceil((V + V_GRINDING), (24 / W)) # 24 should be divisible by W (works for W=2,3,4) MERKLE_LEVELS_PER_CHUNK = MERKLE_LEVELS_PER_CHUNK_PLACEHOLDER N_MERKLE_CHUNKS = LOG_LIFETIME / MERKLE_LEVELS_PER_CHUNK +WOTS_PUBKET_SPONGE_DOMAIN_SEP = WOTS_PUBKET_SPONGE_DOMAIN_SEP_PLACEHOLDER + +POSEIDON24_CAP = 9 +POSEIDON24_RATE = 15 +CHUNKS_PER_FE = 24 / W # 8 +NUM_ENCODING_FE = div_ceil(V, CHUNKS_PER_FE) # ceil(V/8) +Q = 127 # Rejection parameter: p = Q * CHAIN_LENGTH^CHUNKS_PER_FE + 1 = 127 * 8^8 + 1 + +# All tweaks layout: encoding_tweak(2) + chain_tweaks(V*CHAIN_LENGTH*2) + leaf_tweak(2) + merkle_tweaks(LOG_LIFETIME*2) +N_ALL_TWEAKS = TWEAK_LEN + V * CHAIN_LENGTH * TWEAK_LEN + TWEAK_LEN + LOG_LIFETIME * TWEAK_LEN +CHAIN_TWEAKS_OFFSET = TWEAK_LEN +LEAF_TWEAK_OFFSET = TWEAK_LEN + V * CHAIN_LENGTH * TWEAK_LEN +MERKLE_TWEAKS_OFFSET = TWEAK_LEN + V * CHAIN_LENGTH * TWEAK_LEN + TWEAK_LEN @inline -def xmss_verify(merkle_root, message, signature, slot_lo, slot_hi, merkle_chunks): - # signature: randomness | chain_tips | merkle_path - # return the hashed xmss public key +def xmss_verify(merkle_root, public_param, message, signature, all_tweaks, merkle_chunks): + # signature layout: randomness | chain_tips | merkle_path randomness = signature chain_starts = signature + RANDOMNESS_LEN merkle_path = chain_starts + V * DIGEST_LEN - # 1) We encode message_hash + randomness into the layer of the hypercube with target sum = TARGET_SUM - - a_input_right = Array(DIGEST_LEN) - b_input = Array(DIGEST_LEN * 2) - a_input_right[0] = message[DIGEST_LEN] - copy_7(randomness, a_input_right + 1) - poseidon16_compress(message, a_input_right, b_input) - b_input[DIGEST_LEN] = slot_lo - b_input[DIGEST_LEN + 1] = slot_hi - copy_6(merkle_root, b_input + DIGEST_LEN + 2) - encoding_fe = Array(DIGEST_LEN) - poseidon16_compress(b_input, b_input + DIGEST_LEN, encoding_fe) - - encoding = Array(NUM_ENCODING_FE * 24 / (2 * W)) + # Tweak pointers (all pre-computed in public input) + encoding_tweak = all_tweaks + chain_tweaks = all_tweaks + CHAIN_TWEAKS_OFFSET + leaf_tweak = all_tweaks + LEAF_TWEAK_OFFSET + merkle_tweaks = all_tweaks + MERKLE_TWEAKS_OFFSET + + # 1) Encode: poseidon24_compress_0_9(message(9) || pp(5) || slot(2) || randomness(7) || 0) + enc_rate = Array(15) + copy_5(public_param, enc_rate) + enc_rate[5] = encoding_tweak[0] + enc_rate[6] = encoding_tweak[1] + copy_7(randomness, enc_rate + 7) + enc_rate[14] = 0 + + encoding_fe = Array(POSEIDON24_CAP) + poseidon24_compress_0_9(message, enc_rate, encoding_fe) + + # 2) Decompose encoding_fe into chain indices (only first NUM_ENCODING_FE elements) + encoding = Array(NUM_ENCODING_FE * CHUNKS_PER_FE) remaining = Array(NUM_ENCODING_FE) + hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, W) - hint_decompose_bits_xmss(encoding, remaining, encoding_fe, NUM_ENCODING_FE, 2 * W) - - # check that the decomposition is correct for i in unroll(0, NUM_ENCODING_FE): - for j in unroll(0, 24 / (2 * W)): - assert encoding[i * (24 / (2 * W)) + j] < CHAIN_LENGTH**2 - - assert remaining[i] < 2**7 - 1 # ensures uniformity + prevent overflow - - partial_sum: Mut = remaining[i] * 2**24 - for j in unroll(0, 24 / (2 * W)): - partial_sum += encoding[i * (24 / (2 * W)) + j] * (CHAIN_LENGTH**2) ** j + for j in unroll(0, CHUNKS_PER_FE): + assert encoding[i * CHUNKS_PER_FE + j] < CHAIN_LENGTH + assert remaining[i] < Q + partial_sum: Mut = remaining[i] + for j in unroll(0, CHUNKS_PER_FE): + partial_sum += encoding[i * CHUNKS_PER_FE + j] * (Q * CHAIN_LENGTH ** j) assert partial_sum == encoding_fe[i] - # grinding - debug_assert(V_GRINDING % 2 == 0) - debug_assert(V % 2 == 0) - for i in unroll(V / 2, (V + V_GRINDING) / 2): - assert encoding[i] == CHAIN_LENGTH**2 - 1 - + # 3) Chain hashing with Poseidon16 + pre-computed tweaks target_sum: Mut = 0 - wots_public_key = Array(V * DIGEST_LEN) - local_zero_buff = Array(DIGEST_LEN) - set_to_8_zeros(local_zero_buff) - - for i in unroll(0, V / 2): - # num_hashes = (CHAIN_LENGTH - 1) - encoding[i] - chain_start = chain_starts + i * (DIGEST_LEN * 2) - chain_end = wots_public_key + i * (DIGEST_LEN * 2) - pair_chain_length_sum_ptr = Array(1) + for i in unroll(0, V): + chain_start = chain_starts + i * DIGEST_LEN + chain_end = wots_public_key + i * DIGEST_LEN + enc_val_ptr = encoding + i + chain_sum_ptr = Array(1) match_range( - encoding[i], range(0, CHAIN_LENGTH**2), lambda n: chain_hash(chain_start, n, chain_end, pair_chain_length_sum_ptr, local_zero_buff) + enc_val_ptr[0], range(0, CHAIN_LENGTH), + lambda n: chain_hash(chain_start, n, chain_end, chain_sum_ptr, public_param, chain_tweaks, i) ) - target_sum += pair_chain_length_sum_ptr[0] + target_sum += chain_sum_ptr[0] assert target_sum == TARGET_SUM - wots_pubkey_hashed = slice_hash(wots_public_key, V) + # 4) WOTS PK hash with Poseidon24 sponge (parameter + leaf_tweak prefix) + wots_pk_hashed = wots_pk_hash_p24(wots_public_key, public_param, leaf_tweak) - xmss_merkle_verify(wots_pubkey_hashed, merkle_path, merkle_chunks, merkle_root) + # 5) Merkle verify with Poseidon24 + pre-computed tweaks + xmss_merkle_verify_p24(wots_pk_hashed, merkle_path, merkle_chunks, merkle_root, public_param, merkle_tweaks) return @inline -def chain_hash(input_left, n, output_left, pair_chain_length_sum_ptr, local_zero_buff): - debug_assert(n < CHAIN_LENGTH**2) +def make_chain_right(public_param, chain_tweaks, chain_index, step): + right = Array(DIGEST_LEN) + tweak_idx = (chain_index * CHAIN_LENGTH + step) * TWEAK_LEN + copy_5(public_param, right) + right[5] = chain_tweaks[tweak_idx] + right[6] = chain_tweaks[tweak_idx + 1] + right[7] = 0 + return right - raw_left = n % CHAIN_LENGTH - raw_right = (n - raw_left) / CHAIN_LENGTH - n_left = (CHAIN_LENGTH - 1) - raw_left - if n_left == 0: - copy_8(input_left, output_left) - elif n_left == 1: - poseidon16_compress(input_left, local_zero_buff, output_left) - else: - states_left = Array((n_left - 1) * DIGEST_LEN) - poseidon16_compress(input_left, local_zero_buff, states_left) - for i in unroll(1, n_left - 1): - poseidon16_compress(states_left + (i - 1) * DIGEST_LEN, local_zero_buff, states_left + i * DIGEST_LEN) - poseidon16_compress(states_left + (n_left - 2) * DIGEST_LEN, local_zero_buff, output_left) - - n_right = (CHAIN_LENGTH - 1) - raw_right - debug_assert(raw_right < CHAIN_LENGTH) - input_right = input_left + DIGEST_LEN - output_right = output_left + DIGEST_LEN - if n_right == 0: - copy_8(input_right, output_right) - elif n_right == 1: - poseidon16_compress(input_right, local_zero_buff, output_right) +@inline +def chain_hash(input_ptr, n, output_ptr, chain_sum_ptr, public_param, chain_tweaks, chain_index): + num_hashes = (CHAIN_LENGTH - 1) - n + start_step = n + 1 + + if num_hashes == 0: + copy_8(input_ptr, output_ptr) + elif num_hashes == 1: + right = make_chain_right(public_param, chain_tweaks, chain_index, start_step) + poseidon16_compress(input_ptr, right, output_ptr) else: - states_right = Array((n_right - 1) * DIGEST_LEN) - poseidon16_compress(input_right, local_zero_buff, states_right) - for i in unroll(1, n_right - 1): - poseidon16_compress(states_right + (i - 1) * DIGEST_LEN, local_zero_buff, states_right + i * DIGEST_LEN) - poseidon16_compress(states_right + (n_right - 2) * DIGEST_LEN, local_zero_buff, output_right) + states = Array((num_hashes - 1) * DIGEST_LEN) + right0 = make_chain_right(public_param, chain_tweaks, chain_index, start_step) + poseidon16_compress(input_ptr, right0, states) + for j in unroll(1, num_hashes - 1): + right_j = make_chain_right(public_param, chain_tweaks, chain_index, start_step + j) + poseidon16_compress(states + (j - 1) * DIGEST_LEN, right_j, states + j * DIGEST_LEN) + right_last = make_chain_right(public_param, chain_tweaks, chain_index, start_step + num_hashes - 1) + poseidon16_compress(states + (num_hashes - 2) * DIGEST_LEN, right_last, output_ptr) + + chain_sum_ptr[0] = n + return + + +@inline +def wots_pk_hash_p24(wots_pk, public_param, leaf_tweak): + # Sponge input: parameter(5) | leaf_tweak(2) | chain_ends(V*8) + PREFIX_LEN = PUBLIC_PARAM_LEN + TWEAK_LEN # 7 + capacity: Mut = Array(POSEIDON24_CAP) + for i in unroll(0, POSEIDON24_CAP): + capacity[i] = WOTS_PUBKET_SPONGE_DOMAIN_SEP[i] + # First chunk: parameter(5) | leaf_tweak(2) | wots_pk[0..8] + first_rate = Array(POSEIDON24_RATE) + copy_5(public_param, first_rate) + first_rate[5] = leaf_tweak[0] + first_rate[6] = leaf_tweak[1] + copy_8(wots_pk, first_rate + PREFIX_LEN) + new_capacity = Array(POSEIDON24_CAP) + poseidon24_permute_0_9(capacity, first_rate, new_capacity) + capacity = new_capacity + # Remaining data: wots_pk[8..] = V*DIGEST_LEN - 8 elements + WOTS_PK_OFFSET = POSEIDON24_RATE - PREFIX_LEN # 8 + REMAINING = V * DIGEST_LEN - WOTS_PK_OFFSET + REMAINDER = REMAINING % POSEIDON24_RATE + N_FULL_STEPS = div_floor(REMAINING, POSEIDON24_RATE) + for step in unroll(0, N_FULL_STEPS): + src = wots_pk + WOTS_PK_OFFSET + step * POSEIDON24_RATE + new_capacity = Array(POSEIDON24_CAP) + if step == N_FULL_STEPS - 1: + if REMAINDER == 0: + poseidon24_permute_9_18(capacity, src, new_capacity) + else: + poseidon24_permute_0_9(capacity, src, new_capacity) + else: + poseidon24_permute_0_9(capacity, src, new_capacity) + capacity = new_capacity + if REMAINDER != 0: + src = wots_pk + WOTS_PK_OFFSET + N_FULL_STEPS * POSEIDON24_RATE + remainder_rate = Array(POSEIDON24_RATE) + for i in unroll(0, REMAINDER): + remainder_rate[i] = src[i] + for i in unroll(REMAINDER, POSEIDON24_RATE): + remainder_rate[i] = 0 + remainder_capacity = Array(POSEIDON24_CAP) + poseidon24_permute_9_18(capacity, remainder_rate, remainder_capacity) + capacity = remainder_capacity + return capacity - pair_chain_length_sum_ptr[0] = raw_left + raw_right +@inline +def xmss_merkle_verify_p24(leaf_digest, merkle_path, merkle_chunks, expected_root, public_param, merkle_tweaks): + states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) + + match_range(merkle_chunks[0], range(0, 16), lambda b: + do_4_merkle_levels_p24(b, leaf_digest, merkle_path, states, public_param, merkle_tweaks, 0)) + + for j in unroll(1, N_MERKLE_CHUNKS - 1): + match_range(merkle_chunks[j], range(0, 16), lambda b: + do_4_merkle_levels_p24( + b, states + (j - 1) * DIGEST_LEN, + merkle_path + j * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, + states + j * DIGEST_LEN, + public_param, merkle_tweaks, j * MERKLE_LEVELS_PER_CHUNK)) + + match_range(merkle_chunks[N_MERKLE_CHUNKS - 1], range(0, 16), lambda b: + do_4_merkle_levels_p24( + b, states + (N_MERKLE_CHUNKS - 2) * DIGEST_LEN, + merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, + expected_root, + public_param, merkle_tweaks, (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK)) return @inline -def do_4_merkle_levels(b, state_in, path_chunk, state_out): - # Extract bits of b (compile-time; each division is exact so field div == integer div) +def do_4_merkle_levels_p24(b, state_in, path_chunk, state_out, public_param, merkle_tweaks, base_level): b0 = b % 2 r1 = (b - b0) / 2 b1 = r1 % 2 @@ -137,57 +209,33 @@ def do_4_merkle_levels(b, state_in, path_chunk, state_out): temps = Array(3 * DIGEST_LEN) - # Level 0: state_in -> temps - if b0 == 0: - poseidon16_compress(path_chunk, state_in, temps) - else: - poseidon16_compress(state_in, path_chunk, temps) - - # Level 1 - if b1 == 0: - poseidon16_compress(path_chunk + 1 * DIGEST_LEN, temps, temps + DIGEST_LEN) - else: - poseidon16_compress(temps, path_chunk + 1 * DIGEST_LEN, temps + DIGEST_LEN) - - # Level 2 - if b2 == 0: - poseidon16_compress(path_chunk + 2 * DIGEST_LEN, temps + DIGEST_LEN, temps + 2 * DIGEST_LEN) - else: - poseidon16_compress(temps + DIGEST_LEN, path_chunk + 2 * DIGEST_LEN, temps + 2 * DIGEST_LEN) - - # Level 3: -> state_out - if b3 == 0: - poseidon16_compress(path_chunk + 3 * DIGEST_LEN, temps + 2 * DIGEST_LEN, state_out) - else: - poseidon16_compress(temps + 2 * DIGEST_LEN, path_chunk + 3 * DIGEST_LEN, state_out) + merkle_p24_one_level(b0, state_in, path_chunk, temps, public_param, merkle_tweaks, base_level) + merkle_p24_one_level(b1, temps, path_chunk + DIGEST_LEN, temps + DIGEST_LEN, public_param, merkle_tweaks, base_level + 1) + merkle_p24_one_level(b2, temps + DIGEST_LEN, path_chunk + 2 * DIGEST_LEN, temps + 2 * DIGEST_LEN, public_param, merkle_tweaks, base_level + 2) + merkle_p24_one_level(b3, temps + 2 * DIGEST_LEN, path_chunk + 3 * DIGEST_LEN, state_out, public_param, merkle_tweaks, base_level + 3) return @inline -def xmss_merkle_verify(leaf_digest, merkle_path, merkle_chunks, expected_root): - states = Array((N_MERKLE_CHUNKS - 1) * DIGEST_LEN) - - # First chunk: leaf_digest -> states - match_range(merkle_chunks[0], range(0, 16), lambda b: do_4_merkle_levels(b, leaf_digest, merkle_path, states)) - - # Middle chunks - for j in unroll(1, N_MERKLE_CHUNKS - 1): - match_range( - merkle_chunks[j], - range(0, 16), - lambda b: do_4_merkle_levels( - b, states + (j - 1) * DIGEST_LEN, merkle_path + j * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, states + j * DIGEST_LEN - ), - ) - - # Last chunk: -> expected_root - match_range( - merkle_chunks[N_MERKLE_CHUNKS - 1], - range(0, 16), - lambda b: do_4_merkle_levels( - b, states + (N_MERKLE_CHUNKS - 2) * DIGEST_LEN, merkle_path + (N_MERKLE_CHUNKS - 1) * MERKLE_LEVELS_PER_CHUNK * DIGEST_LEN, expected_root - ), - ) +def merkle_p24_one_level(is_left_bit, current, neighbour, output, public_param, merkle_tweaks, child_level): + tweak_ptr = merkle_tweaks + child_level * TWEAK_LEN + + input_buf = Array(24) + copy_5(public_param, input_buf) + input_buf[5] = tweak_ptr[0] + input_buf[6] = tweak_ptr[1] + if is_left_bit == 0: + copy_8(neighbour, input_buf + 7) + copy_8(current, input_buf + 15) + else: + copy_8(current, input_buf + 7) + copy_8(neighbour, input_buf + 15) + input_buf[23] = 0 + + merkle_output = Array(POSEIDON24_CAP) + poseidon24_compress_0_9(input_buf, input_buf + 9, merkle_output) + for k in unroll(0, DIGEST_LEN): + output[k] = merkle_output[k] return @@ -196,10 +244,3 @@ def copy_7(x, y): dot_product_ee(x, ONE_EF_PTR, y) dot_product_ee(x + (7 - DIM), ONE_EF_PTR, y + (7 - DIM)) return - - -@inline -def copy_6(x, y): - dot_product_ee(x, ONE_EF_PTR, y) - y[DIM] = x[DIM] - return diff --git a/crates/sub_protocols/src/stacked_pcs.rs b/crates/sub_protocols/src/stacked_pcs.rs index d7ba6565..acb9d6fa 100644 --- a/crates/sub_protocols/src/stacked_pcs.rs +++ b/crates/sub_protocols/src/stacked_pcs.rs @@ -197,7 +197,7 @@ pub fn total_whir_statements() -> usize { + ALL_TABLES .iter() .map(|table| { - // AIR + // AIR table.n_columns() + table.n_down_columns() // Lookups into memory diff --git a/crates/utils/src/misc.rs b/crates/utils/src/misc.rs index fa21a39f..69f69b64 100644 --- a/crates/utils/src/misc.rs +++ b/crates/utils/src/misc.rs @@ -59,3 +59,11 @@ impl Counter { Self(0) } } + +pub fn decode_hex(s: &str) -> Vec { + let s = s.strip_prefix("0x").unwrap_or(s); + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} diff --git a/crates/utils/src/poseidon.rs b/crates/utils/src/poseidon.rs index bde7db7d..28aa0a1e 100644 --- a/crates/utils/src/poseidon.rs +++ b/crates/utils/src/poseidon.rs @@ -1,13 +1,20 @@ +use backend::symmetric::Permutation; use backend::*; use std::sync::OnceLock; pub type Poseidon16 = Poseidon1KoalaBear16; +pub type Poseidon24 = Poseidon1KoalaBear24; pub const HALF_FULL_ROUNDS_16: usize = POSEIDON1_HALF_FULL_ROUNDS; pub const PARTIAL_ROUNDS_16: usize = POSEIDON1_PARTIAL_ROUNDS; +pub const HALF_FULL_ROUNDS_24: usize = POSEIDON1_HALF_FULL_ROUNDS_24; +pub const PARTIAL_ROUNDS_24: usize = POSEIDON1_PARTIAL_ROUNDS_24; + static POSEIDON_16_INSTANCE: OnceLock = OnceLock::new(); static POSEIDON_16_OF_ZERO: OnceLock<[KoalaBear; 8]> = OnceLock::new(); +static POSEIDON_24_INSTANCE: OnceLock = OnceLock::new(); +static POSEIDON_24_OF_ZERO: OnceLock<[KoalaBear; 9]> = OnceLock::new(); #[inline(always)] pub fn get_poseidon16() -> &'static Poseidon16 { @@ -31,6 +38,43 @@ pub fn poseidon16_compress_pair(left: &[KoalaBear; 8], right: &[KoalaBear; 8]) - poseidon16_compress(input) } +#[inline(always)] +pub fn get_poseidon24() -> &'static Poseidon24 { + POSEIDON_24_INSTANCE.get_or_init(default_koalabear_poseidon1_24) +} + +#[inline(always)] +pub fn get_poseidon_24_of_zero() -> &'static [KoalaBear; 9] { + POSEIDON_24_OF_ZERO.get_or_init(|| poseidon24_compress_0_9([KoalaBear::default(); 24])) +} + +#[inline(always)] +pub fn poseidon24_compress_0_9(input: [KoalaBear; 24]) -> [KoalaBear; 9] { + get_poseidon24().compress(input)[0..9].try_into().unwrap() +} + +#[inline(always)] +pub fn poseidon24_compress_9_18(input: [KoalaBear; 24]) -> [KoalaBear; 9] { + get_poseidon24().compress(input)[9..18].try_into().unwrap() +} + +#[inline(always)] +pub fn poseidon24_permute_0_9(input: [KoalaBear; 24]) -> [KoalaBear; 9] { + get_poseidon24().permute(input)[0..9].try_into().unwrap() +} + +#[inline(always)] +pub fn poseidon24_permute_9_18(input: [KoalaBear; 24]) -> [KoalaBear; 9] { + get_poseidon24().permute(input)[9..18].try_into().unwrap() +} + +pub fn poseidon24_compress_0_9_pair(left: [KoalaBear; 9], right: [KoalaBear; 15]) -> [KoalaBear; 9] { + let mut input = [KoalaBear::default(); 24]; + input[..9].copy_from_slice(&left); + input[9..].copy_from_slice(&right); + poseidon24_compress_0_9(input) +} + /// If `use_iv` is false, the length of the slice must be constant (not malleable). pub fn poseidon_compress_slice(data: &[KoalaBear], use_iv: bool) -> [KoalaBear; 8] { assert!(!data.is_empty()); diff --git a/crates/xmss/Cargo.toml b/crates/xmss/Cargo.toml deleted file mode 100644 index 6b54442f..00000000 --- a/crates/xmss/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "xmss" -version.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] - -rand.workspace = true -utils.workspace = true -backend.workspace = true -serde.workspace = true -lz4_flex.workspace = true -postcard.workspace = true - -[dev-dependencies] -postcard.workspace = true \ No newline at end of file diff --git a/crates/xmss/params.md b/crates/xmss/params.md deleted file mode 100644 index 71b39aee..00000000 --- a/crates/xmss/params.md +++ /dev/null @@ -1,72 +0,0 @@ -# XMSS parameters (WIP) - -> **Warning:** The current implementation does not match the [leanSig](https://github.com/leanEthereum/leanSig) paper and does not provide 128-bit security in the Standard Model (though it may still be secure in the ROM/QROM). Expect changes in the future. - -## 1. Field and Hash - -**Field:** KoalaBear, p = 2^31 - 2^24 + 1. Each field element fits in a u32. - -**Hash:** Poseidon2 (width 16) in compression mode: `compress: [F; 16] -> [F; 8]`. Applies the Poseidon2 permutation, adds the input (feed-forward), and returns the first 8 elements. - -**Digest:** 8 field elements (~248 bits). Used for tree nodes, and chain values. - -**Chain step:** `chain_step(x) = compress(x, 0)`. Iterated n times: `iterate_hash(x, n) = chain_step^n(x)`. - -## 2. WOTS - -| Parameter | Symbol | Value | -|---|---|---| -| Chains | V | 40 | -| Winternitz parameter | W | 3 | -| Chain length | CHAIN_LENGTH | 2^W = 8 | -| Verifier chain hashes | NUM_CHAIN_HASHES | 120 | -| Signer chain hashes | TARGET_SUM | 160 (= V*(CHAIN_LENGTH-1) - NUM_CHAIN_HASHES) | -| Grinding chains | V_GRINDING | 3 | -| Message length | MESSAGE_LEN_FE | 9 | -| Randomness length | RANDOMNESS_LEN_FE | 7 | -| Truncated root length | TRUNCATED_MERKLE_ROOT_LEN_FE | 6 | - -### 2.1 Encoding - -Converts (message, randomness, slot, truncated_merkle_root) into 40 chain indices via a **fixed-sum encoding** (indices sum to TARGET_SUM, eliminating the need for checksum chains). - -1. `A = compress(message[0..8], [message[8], randomness[0..7]])` -2. `B = compress(A, [slot_lo, slot_hi, merkle_root[0..6]])` where slot is split into two 16-bit field elements. -3. Reject if any element of B equals -1 (uniformity guard). -4. Extract 24 bits per element of B (little-endian), split into 3-bit chunks, take first 43. -5. Valid iff: first 40 sum to 160, last 3 all equal 7. Otherwise retry with new randomness. - -(Note: adding part of the merkle root to the encoding computation contributes to multi-user security via domain-separation, otherwise the security of the encoding W * (V + V_GRINDING) would degrade bellow 128 bits with multiple users.) - -### 2.2 Keys - -- **Secret key:** 40 random pre-image digests. -- **Public key:** `pk[i] = iterate_hash(pre_image[i], 7)` for each chain. -- **Public key hash:** sequential left fold: `compress(compress(...compress(pk[0], pk[1])..., pk[38]), pk[39])` (39 compressions). - -### 2.3 Sign and Verify - -**Sign:** Find randomness r yielding a valid encoding, then `chain_tip[i] = iterate_hash(pre_image[i], encoding[i])`. Signature = (chain_tips, r). - -**Verify (public key recovery):** Recompute encoding from (message, slot, truncated_root, r), then `recovered_pk[i] = iterate_hash(chain_tip[i], 7 - encoding[i])`. - -## 3. XMSS - -**Tree:** Binary Merkle tree of depth LOG_LIFETIME = 32 (2^32 slots). Nodes = `compress(left, right)`. - -### 3.1 Key Generation - -Inputs: seed (32 bytes), slot range [start, end]. Only WOTS leaves for [start, end] are generated; Merkle nodes outside this range are filled with deterministic random digests (derived from the seed). To an observer, the resulting tree is indistinguishable from a full 2^32-leaf tree. - -**Public key:** the Merkle root (single digest). - - -... -TODO - -## 4. Properties - -- public key size: 31 bytes -- num. hashes at signing: < 2^16 (mostly grinding at encoding) -- num. hashes at verification: 2 (encoding) + NUM_CHAIN_HASHES + V + LOG_LIFETIME = 194 -- sig. size : RANDOMNESS_LEN_FE + 8 * (V + LOG_LIFETIME) = 583 field elements = 2.21 KiB \ No newline at end of file diff --git a/crates/xmss/src/lib.rs b/crates/xmss/src/lib.rs deleted file mode 100644 index 7e5fe8d2..00000000 --- a/crates/xmss/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![cfg_attr(not(test), warn(unused_crate_dependencies))] -pub mod signers_cache; -mod wots; -use backend::KoalaBear; -pub use wots::*; -mod xmss; -pub use xmss::*; - -pub(crate) const DIGEST_SIZE: usize = 8; - -type F = KoalaBear; -type Digest = [F; DIGEST_SIZE]; - -// WOTS -pub const V: usize = 42; -pub const W: usize = 3; -pub const CHAIN_LENGTH: usize = 1 << W; -pub const NUM_CHAIN_HASHES: usize = 110; -pub const TARGET_SUM: usize = V * (CHAIN_LENGTH - 1) - NUM_CHAIN_HASHES; -pub const V_GRINDING: usize = 2; -pub const LOG_LIFETIME: usize = 32; -pub const RANDOMNESS_LEN_FE: usize = 7; -pub const MESSAGE_LEN_FE: usize = 9; -pub const TRUNCATED_MERKLE_ROOT_LEN_FE: usize = 6; - -pub const SIG_SIZE_FE: usize = RANDOMNESS_LEN_FE + (V + LOG_LIFETIME) * DIGEST_SIZE; diff --git a/crates/xmss/src/wots.rs b/crates/xmss/src/wots.rs deleted file mode 100644 index e34bf9a7..00000000 --- a/crates/xmss/src/wots.rs +++ /dev/null @@ -1,172 +0,0 @@ -use backend::*; -use rand::{CryptoRng, RngExt}; -use serde::{Deserialize, Serialize}; -use utils::{ToUsize, poseidon16_compress_pair, to_little_endian_bits}; - -use crate::*; - -#[derive(Debug)] -pub struct WotsSecretKey { - pub pre_images: [Digest; V], - public_key: WotsPublicKey, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct WotsPublicKey(pub [Digest; V]); - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct WotsSignature { - #[serde( - with = "backend::array_serialization", - bound(serialize = "F: Serialize", deserialize = "F: Deserialize<'de>") - )] - pub chain_tips: [Digest; V], - pub randomness: [F; RANDOMNESS_LEN_FE], -} - -impl WotsSecretKey { - pub fn random(rng: &mut impl CryptoRng) -> Self { - Self::new(rng.random()) - } - - pub fn new(pre_images: [Digest; V]) -> Self { - Self { - pre_images, - public_key: WotsPublicKey(std::array::from_fn(|i| iterate_hash(&pre_images[i], CHAIN_LENGTH - 1))), - } - } - - pub const fn public_key(&self) -> &WotsPublicKey { - &self.public_key - } - - pub fn sign_with_randomness( - &self, - message: &[F; MESSAGE_LEN_FE], - slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - randomness: [F; RANDOMNESS_LEN_FE], - ) -> WotsSignature { - let encoding = wots_encode(message, slot, truncated_merkle_root, &randomness).unwrap(); - self.sign_with_encoding(randomness, &encoding) - } - - fn sign_with_encoding(&self, randomness: [F; RANDOMNESS_LEN_FE], encoding: &[u8; V]) -> WotsSignature { - WotsSignature { - chain_tips: std::array::from_fn(|i| iterate_hash(&self.pre_images[i], encoding[i] as usize)), - randomness, - } - } -} - -impl WotsSignature { - pub fn recover_public_key( - &self, - message: &[F; MESSAGE_LEN_FE], - slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - signature: &Self, - ) -> Option { - let encoding = wots_encode(message, slot, truncated_merkle_root, &signature.randomness)?; - Some(WotsPublicKey(std::array::from_fn(|i| { - iterate_hash(&self.chain_tips[i], CHAIN_LENGTH - 1 - encoding[i] as usize) - }))) - } -} - -impl WotsPublicKey { - pub fn hash(&self) -> Digest { - let init = poseidon16_compress_pair(&self.0[0], &self.0[1]); - self.0[2..] - .iter() - .fold(init, |digest, chunk| poseidon16_compress_pair(&digest, chunk)) - } -} - -pub fn iterate_hash(a: &Digest, n: usize) -> Digest { - (0..n).fold(*a, |acc, _| poseidon16_compress_pair(&acc, &Default::default())) -} - -pub fn find_randomness_for_wots_encoding( - message: &[F; MESSAGE_LEN_FE], - slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - rng: &mut impl CryptoRng, -) -> ([F; RANDOMNESS_LEN_FE], [u8; V], usize) { - let mut num_iters = 0; - loop { - num_iters += 1; - let randomness = rng.random(); - if let Some(encoding) = wots_encode(message, slot, truncated_merkle_root, &randomness) { - return (randomness, encoding, num_iters); - } - } -} - -pub fn wots_encode( - message: &[F; MESSAGE_LEN_FE], - slot: u32, - truncated_merkle_root: &[F; TRUNCATED_MERKLE_ROOT_LEN_FE], - randomness: &[F; RANDOMNESS_LEN_FE], -) -> Option<[u8; V]> { - // Encode slot as 2 field elements (16 bits each) - let [slot_lo, slot_hi] = slot_to_field_elements(slot); - - // A = poseidon(message (9 fe), randomness (7 fe)) - let mut a_input_right = [F::default(); 8]; - a_input_right[0] = message[8]; - a_input_right[1..1 + RANDOMNESS_LEN_FE].copy_from_slice(randomness); - let a = poseidon16_compress_pair(message[..8].try_into().unwrap(), &a_input_right); - - // B = poseidon(A (8 fe), slot (2 fe), truncated_merkle_root (6 fe)) - let mut b_input_right = [F::default(); 8]; - b_input_right[0] = slot_lo; - b_input_right[1] = slot_hi; - b_input_right[2..8].copy_from_slice(truncated_merkle_root); - let compressed = poseidon16_compress_pair(&a, &b_input_right); - - if compressed.iter().any(|&kb| kb == -F::ONE) { - // ensures uniformity of encoding - return None; - } - let all_indices: Vec<_> = compressed - .iter() - .flat_map(|kb| to_little_endian_bits(kb.to_usize(), 24)) - .collect::>() - .chunks_exact(W) - .take(V + V_GRINDING) - .map(|chunk| { - chunk - .iter() - .enumerate() - .fold(0u8, |acc, (i, &bit)| acc | (u8::from(bit) << i)) - }) - .collect(); - is_valid_encoding(&all_indices).then(|| all_indices[..V].try_into().unwrap()) -} - -fn is_valid_encoding(encoding: &[u8]) -> bool { - if encoding.len() != V + V_GRINDING { - return false; - } - // All indices must be < CHAIN_LENGTH - if !encoding.iter().all(|&x| (x as usize) < CHAIN_LENGTH) { - return false; - } - // First V indices must sum to TARGET_SUM - if encoding[..V].iter().map(|&x| x as usize).sum::() != TARGET_SUM { - return false; - } - // Last V_GRINDING indices must all be CHAIN_LENGTH-1 (grinding constraint) - if !encoding[V..].iter().all(|&x| x as usize == CHAIN_LENGTH - 1) { - return false; - } - true -} - -pub fn slot_to_field_elements(slot: u32) -> [F; 2] { - [ - F::from_usize((slot & 0xFFFF) as usize), - F::from_usize(((slot >> 16) & 0xFFFF) as usize), - ] -} diff --git a/crates/xmss/src/xmss.rs b/crates/xmss/src/xmss.rs deleted file mode 100644 index 88f9e6f2..00000000 --- a/crates/xmss/src/xmss.rs +++ /dev/null @@ -1,206 +0,0 @@ -use backend::*; -use rand::{CryptoRng, RngExt, SeedableRng, rngs::StdRng}; -use serde::{Deserialize, Serialize}; -use utils::poseidon16_compress_pair; - -use crate::*; - -#[derive(Debug)] -pub struct XmssSecretKey { - pub(crate) slot_start: u32, // inclusive - pub(crate) slot_end: u32, // inclusive - pub(crate) seed: [u8; 20], - // At level l, stored indices go from (slot_start >> l) to (slot_end >> l). - pub(crate) merkle_tree: Vec>, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct XmssSignature { - pub wots_signature: WotsSignature, - pub merkle_proof: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct XmssPublicKey { - pub merkle_root: Digest, -} - -fn gen_wots_secret_key(seed: &[u8; 20], slot: u32) -> WotsSecretKey { - let mut rng_seed = [0u8; 32]; - rng_seed[..20].copy_from_slice(seed); - rng_seed[20] = 0x00; - rng_seed[21..25].copy_from_slice(&slot.to_le_bytes()); - let mut rng = StdRng::from_seed(rng_seed); - WotsSecretKey::random(&mut rng) -} - -/// Deterministic pseudo-random digest for an out-of-range tree node. -fn gen_random_node(seed: &[u8; 20], level: usize, index: u32) -> Digest { - let mut rng_seed = [0u8; 32]; - rng_seed[..20].copy_from_slice(seed); - rng_seed[20] = 0x01; - rng_seed[21] = level as u8; - rng_seed[22..26].copy_from_slice(&index.to_le_bytes()); - let mut rng = StdRng::from_seed(rng_seed); - rng.random() -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum XmssKeyGenError { - InvalidRange, -} - -pub fn xmss_key_gen( - seed: [u8; 20], - slot_start: u32, - slot_end: u32, -) -> Result<(XmssSecretKey, XmssPublicKey), XmssKeyGenError> { - if slot_start > slot_end { - return Err(XmssKeyGenError::InvalidRange); - } - let perm = default_koalabear_poseidon1_16(); - // Level 0: WOTS leaf hashes for slots in [slot_start, slot_end] - let leaves: Vec = (slot_start..=slot_end) - .into_par_iter() - .map(|slot| { - let wots = gen_wots_secret_key(&seed, slot); - wots.public_key().hash() - }) - .collect(); - let mut merkle_tree = vec![leaves]; - // Build levels 1..=LOG_LIFETIME. - // At level l, we store nodes with index in [(slot_start >> l), (slot_end >> l)]. - // Children outside [slot_start, slot_end]'s subtree are replaced by gen_random_node. - for level in 1..=LOG_LIFETIME { - let base = u64::from(slot_start) >> level; - let top = u64::from(slot_end) >> level; - let prev_base = u64::from(slot_start) >> (level - 1); - let prev_top = u64::from(slot_end) >> (level - 1); - let nodes: Vec = { - let prev = &merkle_tree[level - 1]; - (base..=top) - .into_par_iter() - .map(|i| { - let left_idx = 2 * i; - let right_idx = 2 * i + 1; - let left = if left_idx >= prev_base && left_idx <= prev_top { - prev[(left_idx - prev_base) as usize] - } else { - assert!(left_idx < 1u64 << 32); - gen_random_node(&seed, level - 1, left_idx as u32) - }; - let right = if right_idx >= prev_base && right_idx <= prev_top { - prev[(right_idx - prev_base) as usize] - } else { - assert!(right_idx < 1u64 << 32); - gen_random_node(&seed, level - 1, right_idx as u32) - }; - compress(&perm, [left, right]) - }) - .collect() - }; - merkle_tree.push(nodes); - } - let pub_key = XmssPublicKey { - merkle_root: merkle_tree.last().unwrap()[0], - }; - let secret_key = XmssSecretKey { - slot_start, - slot_end, - seed, - merkle_tree, - }; - Ok((secret_key, pub_key)) -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum XmssSignatureError { - SlotOutOfRange, -} - -pub fn xmss_sign( - rng: &mut R, - secret_key: &XmssSecretKey, - message: &[F; MESSAGE_LEN_FE], - slot: u32, -) -> Result { - let merkle_root = secret_key.public_key().merkle_root; - let truncated_merkle_root = merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); - let (randomness, _, _) = find_randomness_for_wots_encoding(message, slot, truncated_merkle_root, rng); - xmss_sign_with_randomness(secret_key, message, slot, randomness) -} - -pub fn xmss_sign_with_randomness( - secret_key: &XmssSecretKey, - message: &[F; MESSAGE_LEN_FE], - slot: u32, - randomness: [F; RANDOMNESS_LEN_FE], -) -> Result { - if slot < secret_key.slot_start || slot > secret_key.slot_end { - return Err(XmssSignatureError::SlotOutOfRange); - } - let wots_secret_key = gen_wots_secret_key(&secret_key.seed, slot); - let merkle_root = secret_key.public_key().merkle_root; - let truncated_merkle_root = merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); - let wots_signature = wots_secret_key.sign_with_randomness(message, slot, &truncated_merkle_root, randomness); - let merkle_proof = (0..LOG_LIFETIME) - .map(|level| { - let neighbour_index = (slot >> level) ^ 1; - let base = secret_key.slot_start >> level; - let top = secret_key.slot_end >> level; - if neighbour_index >= base && neighbour_index <= top { - secret_key.merkle_tree[level][(neighbour_index - base) as usize] - } else { - gen_random_node(&secret_key.seed, level, neighbour_index) - } - }) - .collect(); - Ok(XmssSignature { - wots_signature, - merkle_proof, - }) -} - -impl XmssSecretKey { - pub fn public_key(&self) -> XmssPublicKey { - XmssPublicKey { - merkle_root: self.merkle_tree.last().unwrap()[0], - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum XmssVerifyError { - InvalidWots, - InvalidMerklePath, -} - -pub fn xmss_verify( - pub_key: &XmssPublicKey, - message: &[F; MESSAGE_LEN_FE], - signature: &XmssSignature, - slot: u32, -) -> Result<(), XmssVerifyError> { - let truncated_merkle_root = pub_key.merkle_root[0..TRUNCATED_MERKLE_ROOT_LEN_FE].try_into().unwrap(); - let wots_public_key = signature - .wots_signature - .recover_public_key(message, slot, &truncated_merkle_root, &signature.wots_signature) - .ok_or(XmssVerifyError::InvalidWots)?; - let mut current_hash = wots_public_key.hash(); - if signature.merkle_proof.len() != LOG_LIFETIME { - return Err(XmssVerifyError::InvalidMerklePath); - } - for (level, neighbour) in signature.merkle_proof.iter().enumerate() { - let is_left = ((slot >> level) & 1) == 0; - if is_left { - current_hash = poseidon16_compress_pair(¤t_hash, neighbour); - } else { - current_hash = poseidon16_compress_pair(neighbour, ¤t_hash); - } - } - if current_hash == pub_key.merkle_root { - Ok(()) - } else { - Err(XmssVerifyError::InvalidMerklePath) - } -} diff --git a/crates/xmss/tests/xmss_tests.rs b/crates/xmss/tests/xmss_tests.rs deleted file mode 100644 index 40bbb637..00000000 --- a/crates/xmss/tests/xmss_tests.rs +++ /dev/null @@ -1,61 +0,0 @@ -use backend::*; -use rand::{SeedableRng, rngs::StdRng}; -use xmss::*; - -type F = KoalaBear; - -#[test] -fn test_xmss_serialize_deserialize() { - let keygen_seed: [u8; 20] = std::array::from_fn(|i| i as u8); - let message: [F; MESSAGE_LEN_FE] = std::array::from_fn(|i| F::from_usize(i * 3 + 7)); - let slot_start = 100; - let slot_end = 115; - let slot = 110; - - let (sk, pk) = xmss_key_gen(keygen_seed, slot_start, slot_end).unwrap(); - let sig = xmss_sign(&mut StdRng::seed_from_u64(slot as u64), &sk, &message, slot).unwrap(); - - let pk_bytes = postcard::to_allocvec(&pk).unwrap(); - let pk2: XmssPublicKey = postcard::from_bytes(&pk_bytes).unwrap(); - assert_eq!(pk, pk2); - - let sig_bytes = postcard::to_allocvec(&sig).unwrap(); - let sig2: XmssSignature = postcard::from_bytes(&sig_bytes).unwrap(); - assert_eq!(sig, sig2); - - xmss_verify(&pk2, &message, &sig2, slot).unwrap(); -} - -#[test] -fn keygen_sign_verify() { - let keygen_seed: [u8; 20] = std::array::from_fn(|i| i as u8); - let message: [F; MESSAGE_LEN_FE] = std::array::from_fn(|i| F::from_usize(i * 3 + 7)); - - let slot_start = 100; - let slot_end = 115; - let (sk, pk) = xmss_key_gen(keygen_seed, slot_start, slot_end).unwrap(); - for slot in slot_start..=slot_end { - let sig = xmss_sign(&mut StdRng::seed_from_u64(u64::from(slot)), &sk, &message, slot).unwrap(); - xmss_verify(&pk, &message, &sig, slot).unwrap(); - } -} - -#[test] -#[ignore] -fn encoding_grinding_bits() { - let n = 100; - let total_iters = (0..n) - .into_par_iter() - .map(|i| { - let message: [F; MESSAGE_LEN_FE] = Default::default(); - let slot = i as u32; - let truncated_merkle_root: [F; TRUNCATED_MERKLE_ROOT_LEN_FE] = Default::default(); - let mut rng = StdRng::seed_from_u64(i as u64); - let (_randomness, _encoding, num_iters) = - find_randomness_for_wots_encoding(&message, slot, &truncated_merkle_root, &mut rng); - num_iters - }) - .sum::(); - let grinding = ((total_iters as f64) / (n as f64)).log2(); - println!("Average grinding bits: {:.1}", grinding); -} diff --git a/src/lib.rs b/src/lib.rs index 092f00a0..2aebccd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,6 @@ use backend::*; pub use backend::ProofError; pub use rec_aggregation::{AggregatedXMSS, AggregationTopology, xmss_aggregate, xmss_verify_aggregation}; -pub use xmss::{MESSAGE_LEN_FE, XmssPublicKey, XmssSecretKey, XmssSignature, xmss_key_gen, xmss_sign, xmss_verify}; pub type F = KoalaBear; diff --git a/src/main.rs b/src/main.rs index 0320717d..7831d796 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use clap::Parser; use rec_aggregation::{AggregationTopology, benchmark::run_aggregation_benchmark}; mod prove_poseidons; -use crate::prove_poseidons::benchmark_prove_poseidon_16; +use crate::prove_poseidons::{benchmark_prove_poseidon_16, benchmark_prove_poseidon_24}; #[derive(Parser)] enum Cli { @@ -28,6 +28,8 @@ enum Cli { Poseidon { #[arg(long, help = "log2(number of Poseidons)", default_value = "20")] log_n_perms: usize, + #[arg(long, help = "state width (16 or 24)", default_value = "16")] + state_width: usize, #[arg(long, help = "Enable tracing")] tracing: bool, }, @@ -72,10 +74,13 @@ fn main() { } Cli::Poseidon { log_n_perms: log_count, + state_width, tracing, - } => { - benchmark_prove_poseidon_16(log_count, tracing); - } + } => match state_width { + 16 => benchmark_prove_poseidon_16(log_count, tracing), + 24 => benchmark_prove_poseidon_24(log_count, tracing), + _ => panic!("unsupported state width: {state_width} (expected 16 or 24)"), + }, Cli::FancyAggregation {} => { let topology = AggregationTopology { raw_xmss: 0, diff --git a/src/prove_poseidons.rs b/src/prove_poseidons.rs index ee3a8899..afa53dca 100644 --- a/src/prove_poseidons.rs +++ b/src/prove_poseidons.rs @@ -1,39 +1,89 @@ -use air::{check_air_validity, prove_air, verify_air}; +use air::{prove_air, verify_air}; use backend::*; use lean_vm::{ EF, ExtraDataForBuses, F, POSEIDON_16_COL_FLAG, POSEIDON_16_COL_INDEX_INPUT_LEFT, POSEIDON_16_COL_INDEX_INPUT_RES, - POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, Poseidon16Precompile, ZERO_VEC_PTR, - fill_trace_poseidon_16, num_cols_poseidon_16, + POSEIDON_16_COL_INDEX_INPUT_RIGHT, POSEIDON_16_COL_INPUT_START, POSEIDON_24_COL_INDEX_INPUT_LEFT, + POSEIDON_24_COL_INDEX_INPUT_RIGHT, POSEIDON_24_COL_INDEX_RES, POSEIDON_24_COL_INPUT_START, + POSEIDON_24_COL_IS_COMPRESS_0_9, POSEIDON_24_COL_IS_PERMUTE_0_9, Poseidon16Precompile, Poseidon24Precompile, + ZERO_VEC_PTR, fill_trace_poseidon_16, fill_trace_poseidon_24, num_cols_poseidon_16, num_cols_poseidon_24, }; use rand::{RngExt, SeedableRng, rngs::StdRng}; -use utils::{ - build_prover_state, build_verifier_state, collect_refs, init_tracing, padd_with_zero_to_next_power_of_two, -}; - -const WIDTH: usize = 16; +use utils::{build_prover_state, build_verifier_state, init_tracing, padd_with_zero_to_next_power_of_two}; #[test] fn test_benchmark_air_poseidon_16() { benchmark_prove_poseidon_16(11, false); } -#[allow(clippy::too_many_lines)] +#[test] +fn test_benchmark_air_poseidon_24() { + benchmark_prove_poseidon_24(11, false); +} + pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { if tracing { init_tracing(); } let n_rows = 1 << log_n_rows; + let n_cols = num_cols_poseidon_16(); let mut rng = StdRng::seed_from_u64(0); - let mut trace = vec![vec![F::ZERO; n_rows]; num_cols_poseidon_16()]; - for t in trace.iter_mut().skip(POSEIDON_16_COL_INPUT_START).take(WIDTH) { + + let mut trace = vec![vec![F::ZERO; n_rows]; n_cols]; + for t in trace.iter_mut().skip(POSEIDON_16_COL_INPUT_START).take(16) { *t = (0..n_rows).map(|_| rng.random()).collect(); } - trace[POSEIDON_16_COL_FLAG] = (0..n_rows).map(|_| F::ONE).collect(); - trace[POSEIDON_16_COL_INDEX_INPUT_RES] = (0..n_rows).map(|_| F::ZERO).collect(); // useless - trace[POSEIDON_16_COL_INDEX_INPUT_LEFT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); - trace[POSEIDON_16_COL_INDEX_INPUT_RIGHT] = (0..n_rows).map(|_| F::from_usize(ZERO_VEC_PTR)).collect(); + trace[POSEIDON_16_COL_FLAG] = vec![F::ONE; n_rows]; + trace[POSEIDON_16_COL_INDEX_INPUT_LEFT] = vec![F::from_usize(ZERO_VEC_PTR); n_rows]; + trace[POSEIDON_16_COL_INDEX_INPUT_RIGHT] = vec![F::from_usize(ZERO_VEC_PTR); n_rows]; + trace[POSEIDON_16_COL_INDEX_INPUT_RES] = vec![F::ZERO; n_rows]; + fill_trace_poseidon_16(&mut trace); + benchmark_prove_poseidons(log_n_rows, &Poseidon16Precompile::, &trace, n_cols, 16); +} + +pub fn benchmark_prove_poseidon_24(log_n_rows: usize, tracing: bool) { + if tracing { + init_tracing(); + } + let n_rows = 1 << log_n_rows; + let n_committed_cols = num_cols_poseidon_24(); + let n_total_cols = n_committed_cols + 1; // +1 virtual column (precompile_data) + let mut rng = StdRng::seed_from_u64(0); + + let mut trace = vec![vec![F::ZERO; n_rows]; n_total_cols]; + for t in trace.iter_mut().skip(POSEIDON_24_COL_INPUT_START).take(24) { + *t = (0..n_rows).map(|_| rng.random()).collect(); + } + trace[0] = vec![F::ONE; n_rows]; // FLAG + trace[POSEIDON_24_COL_IS_COMPRESS_0_9] = vec![F::ONE; n_rows]; // compress_0_9 mode for benchmark + trace[POSEIDON_24_COL_IS_PERMUTE_0_9] = vec![F::ZERO; n_rows]; + trace[POSEIDON_24_COL_INDEX_INPUT_LEFT] = vec![F::from_usize(ZERO_VEC_PTR); n_rows]; + trace[POSEIDON_24_COL_INDEX_INPUT_RIGHT] = vec![F::from_usize(ZERO_VEC_PTR); n_rows]; + trace[POSEIDON_24_COL_INDEX_RES] = vec![F::ZERO; n_rows]; + + fill_trace_poseidon_24(&mut trace); + + benchmark_prove_poseidons(log_n_rows, &Poseidon24Precompile::, &trace, n_committed_cols, 24); +} + +fn benchmark_prove_poseidons( + log_n_rows: usize, + air: &impl Air>, + trace: &[Vec], + n_committed_cols: usize, + width: usize, +) { + let n_rows = 1 << log_n_rows; + let committed_trace = &trace[..n_committed_cols]; + + // Verify AIR validity + #[cfg(test)] + { + let trace_refs: Vec<&[F]> = committed_trace.iter().map(Vec::as_slice).collect(); + air::check_air_validity::<_, EF>(air, &ExtraDataForBuses::::default(), &trace_refs).unwrap(); + } + let whir_config = WhirConfigBuilder { folding_factor: FoldingFactor::new(7, 4), soundness_type: SecurityAssumption::JohnsonBound, @@ -44,24 +94,19 @@ pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { starting_log_inv_rate: 1, }; - let air = Poseidon16Precompile::; - - check_air_validity::<_, EF>(&air, &ExtraDataForBuses::default(), &collect_refs(&trace)).unwrap(); - let mut prover_state = build_prover_state(); - - let packed_n_vars = log2_ceil_usize(num_cols_poseidon_16() << log_n_rows); + let packed_n_vars = log2_ceil_usize(n_committed_cols << log_n_rows); let whir_config = WhirConfig::new(&whir_config, packed_n_vars); let time = std::time::Instant::now(); { - let mut commitmed_pol = F::zero_vec((num_cols_poseidon_16() << log_n_rows).next_power_of_two()); - for (i, col) in trace.iter().enumerate() { - commitmed_pol[i << log_n_rows..(i + 1) << log_n_rows].copy_from_slice(col); + let mut committed_pol = F::zero_vec(1 << packed_n_vars); + for (i, col) in committed_trace.iter().enumerate() { + committed_pol[i << log_n_rows..(i + 1) << log_n_rows].copy_from_slice(col); } - let committed_pol = MleOwned::Base(commitmed_pol); - let witness = whir_config.commit(&mut prover_state, &committed_pol, num_cols_poseidon_16() << log_n_rows); + let committed_pol = MleOwned::Base(committed_pol); + let witness = whir_config.commit(&mut prover_state, &committed_pol, n_committed_cols << log_n_rows); let alpha = prover_state.sample(); let air_alpha_powers: Vec = alpha.powers().collect_n(air.n_constraints() + 1); @@ -69,31 +114,26 @@ pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { alpha_powers: air_alpha_powers, ..Default::default() }; + let air_claims = prove_air::(&mut prover_state, air, extra_data, committed_trace, None, true); - let air_claims = prove_air::(&mut prover_state, &air, extra_data, &collect_refs(&trace), None, true); - assert!(air_claims.down_point.is_none()); - assert_eq!(air_claims.evals.len(), air.n_columns()); - - let betas = prover_state.sample_vec(log2_ceil_usize(num_cols_poseidon_16())); - let packed_point = MultilinearPoint([betas.clone(), air_claims.point.0].concat()); - let packed_eval = padd_with_zero_to_next_power_of_two(&air_claims.evals).evaluate(&MultilinearPoint(betas)); - - whir_config.prove( - &mut prover_state, - vec![SparseStatement::dense(packed_point, packed_eval)], - witness, - &committed_pol.by_ref(), + let betas = MultilinearPoint( + (0..log2_ceil_usize(n_committed_cols)) + .map(|_| prover_state.sample()) + .collect(), ); + let packed_point = MultilinearPoint([betas.0.clone(), air_claims.point.0].concat()); + let packed_eval = padd_with_zero_to_next_power_of_two(&air_claims.evals).evaluate(&MultilinearPoint(betas.0)); + + let statements = vec![SparseStatement::dense(packed_point, packed_eval)]; + whir_config.prove(&mut prover_state, statements, witness, &committed_pol.by_ref()); } - println!( - "{} Poseidons / s", - (n_rows as f64 / time.elapsed().as_secs_f64()) as usize - ); + println!("{} Poseidon-{width} / s", { + (f64::from(n_rows) / time.elapsed().as_secs_f64()) as usize + }); { let mut verifier_state = build_verifier_state(prover_state).unwrap(); - let parsed_commitment = whir_config.parse_commitment::(&mut verifier_state).unwrap(); let alpha = verifier_state.sample(); @@ -102,18 +142,19 @@ pub fn benchmark_prove_poseidon_16(log_n_rows: usize, tracing: bool) { alpha_powers: air_alpha_powers, ..Default::default() }; - let air_claims = verify_air(&mut verifier_state, &air, extra_data, log2_ceil_usize(n_rows), None).unwrap(); + let air_claims = verify_air(&mut verifier_state, air, extra_data, log_n_rows, None).unwrap(); - let betas = verifier_state.sample_vec(log2_ceil_usize(num_cols_poseidon_16())); - let packed_point = MultilinearPoint([betas.clone(), air_claims.point.0].concat()); - let packed_eval = padd_with_zero_to_next_power_of_two(&air_claims.evals).evaluate(&MultilinearPoint(betas)); + let betas = MultilinearPoint( + (0..log2_ceil_usize(n_committed_cols)) + .map(|_| verifier_state.sample()) + .collect(), + ); + let packed_point = MultilinearPoint([betas.0.clone(), air_claims.point.0].concat()); + let packed_eval = padd_with_zero_to_next_power_of_two(&air_claims.evals).evaluate(&MultilinearPoint(betas.0)); + let statements = vec![SparseStatement::dense(packed_point, packed_eval)]; whir_config - .verify( - &mut verifier_state, - &parsed_commitment, - vec![SparseStatement::dense(packed_point, packed_eval)], - ) + .verify(&mut verifier_state, &parsed_commitment, statements) .unwrap(); } } diff --git a/tests/test_lean_multisig.rs b/tests/test_lean_multisig.rs index d013a779..5726d99c 100644 --- a/tests/test_lean_multisig.rs +++ b/tests/test_lean_multisig.rs @@ -1,21 +1,55 @@ use lean_multisig::{AggregatedXMSS, setup_prover, xmss_aggregate, xmss_verify_aggregation}; -use rand::{RngExt, SeedableRng, rngs::StdRng}; -use xmss::{ - signers_cache::{BENCHMARK_SLOT, get_benchmark_signatures, message_for_benchmark}, - xmss_key_gen, xmss_sign, xmss_verify, -}; +use leansig_wrapper::{MESSAGE_LENGTH, xmss_keygen_fast, xmss_sign_fast, xmss_verify}; +use rand::{SeedableRng, rngs::StdRng}; +use rec_aggregation::signatures_cache::{BENCHMARK_MESSAGE, BENCHMARK_SLOT, get_benchmark_signatures}; +use utils::decode_hex; + +fn verify_test_vector(filename: &str) { + use leansig_wrapper::{xmss_public_key_from_ssz, xmss_signature_from_ssz}; + + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/test_vectors") + .join(filename); + let json: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + + let pk_bytes = decode_hex(json["public_key"].as_str().unwrap()); + let pk = xmss_public_key_from_ssz(&pk_bytes).unwrap(); + + let slot = json["slot"].as_u64().unwrap() as u32; + + let msg_bytes = decode_hex(json["message"].as_str().unwrap()); + let mut message = [0u8; MESSAGE_LENGTH]; + message[..msg_bytes.len()].copy_from_slice(&msg_bytes); + + let sig_bytes = decode_hex(json["signature_ssz"].as_str().unwrap()); + let sig = xmss_signature_from_ssz(&sig_bytes).unwrap(); + + xmss_verify(&pk, slot, &message, &sig).expect("test vector verify failed"); +} + +#[cfg(feature = "test-config")] +#[test] +fn test_xmss_test_vector() { + verify_test_vector("xmss_test_vector.json"); +} + +#[cfg(not(feature = "test-config"))] +#[test] +fn test_xmss_prod_vector() { + verify_test_vector("xmss_prod_test_vector.json"); +} #[test] fn test_xmss_signature() { - let start_slot = 111; - let end_slot = 200; - let slot: u32 = 124; + let activation_epoch = 111; + let num_active_epochs = 39; + let slot = 124; let mut rng: StdRng = StdRng::seed_from_u64(0); - let msg = rng.random(); + let message_hash: [u8; MESSAGE_LENGTH] = std::array::from_fn(|i| i as u8); - let (secret_key, pub_key) = xmss_key_gen(rng.random(), start_slot, end_slot).unwrap(); - let signature = xmss_sign(&mut rng, &secret_key, &msg, slot).unwrap(); - xmss_verify(&pub_key, &msg, &signature, slot).unwrap(); + let (secret_key, pub_key) = xmss_keygen_fast(&mut rng, activation_epoch, num_active_epochs); + let signature = xmss_sign_fast(&secret_key, &message_hash, slot).unwrap(); + xmss_verify(&pub_key, slot, &message_hash, &signature).unwrap(); } #[test] @@ -23,7 +57,7 @@ fn test_recursive_aggregation() { setup_prover(); let log_inv_rate = 2; // [1, 2, 3 or 4] (lower = faster but bigger proofs) - let message = message_for_benchmark(); + let message = BENCHMARK_MESSAGE; let slot: u32 = BENCHMARK_SLOT; let signatures = get_benchmark_signatures(); @@ -43,5 +77,5 @@ fn test_recursive_aggregation() { println!("Serialized aggregated final: {} KiB", serialized_final.len() / 1024); let deserialized_final = AggregatedXMSS::deserialize(&serialized_final).unwrap(); - xmss_verify_aggregation(&final_pub_keys, &deserialized_final, &message, slot).unwrap(); + xmss_verify_aggregation(final_pub_keys, &deserialized_final, &message, slot).unwrap(); } diff --git a/tests/test_vectors/xmss_prod_test_vector.json b/tests/test_vectors/xmss_prod_test_vector.json new file mode 100644 index 00000000..5c799872 --- /dev/null +++ b/tests/test_vectors/xmss_prod_test_vector.json @@ -0,0 +1,6 @@ +{ + "public_key": "0x5caf047c20695a512c5b606a1c702761cb92fd505d63345b126b8a36b54dc3313fd11056749de15f276ca24ee799d921e1ab9c37", + "slot": 5, + "message": "0xabababababababababababababababababababababababababababababababab", + "signature_ssz": "0x2400000075871b720d712619164d6966f8066d125d40c34a35df5f006da060672804000004000000c94d0d18492f4e728e435927a595163716bbcf6150aa8b5c9d48367d7e0d911f13b89b4bbffe9a597dc64839b641836e0662114051879e1d41285919b6e4fe7db2c36665ab4b461d10fbb4288b918610f0c3273b2d44760e74091c642729ed0c525d3e35e61bd84df378710c9bce8928314ef56b8e24715b3a07d401a791d8094a88dd47a70af0563cd0960e04d3261a8d898c5fb1d96a5d9a890d6cb48a0311884d9844787b2a35cd938c2ca3d0b41e1cf30e275760367efdfd237985d5296b3d76d32c965ff872cff01a411df6912b9836d34b1e13324abac09d243cc963497056ac7abbbb9d65d82001707cae060b8f873d37be7c1e1788978a260796f7663de1fc0ef2281c1f1be2a55728ad7255228186557344473dd54fd20a6b59e4004982d665df81e46f4edc5a4f2b6aba48283d9872ab07f1289fc74c73051cfb34a8047d384dd3574a0c564b0b3274fe3b4f08e607bfa9020ced67d13954b2c4694a01e84cad2d3e06d7f00f175e6a06347337605ddb6a3d73999cd63ecf61cc6894d83467cba5f45ce9a4755f6b671e07f3e4b8531e1d773c301ee63e06558a1a7791e46ec4769548f49f75002d6a3c2ff468b078699f711fbf204d6671e42c5829b69e3931939e222250fd37a16c8530d268986a57bc3c0aac5f2a3d9c050851f134af1c93212e4cf7647f7763a3da30d3e3dc6e69057c3596e8c96fbea02337ab273f6e4b4d65343cdeb8793e1b0230766e6774b0052a1676482c02319c5603a035505acb89094b10a3810f1bd27034abdfd95a9429d92ab5b59856997e3f2d1709c6794b67ed13b16a091d74ac0c626686d94143fc3a2fb37d9443d3b1bc06f18e6b79a950fc123631b959cda7cb3a33c4d17c6eba386169b6f87559ec3466ceccf01152c4491624a9470b51c37269d3fc0e0040c2bb189a1e48651499de046a20ae7d211fd961ec834025375a0b43a19498483b3ac4768618384a24182101bf2bc67cf649b757e657cf7a12e6ef09e46d311b96a9f64151d76770ec65fb347bec7c1dce9edc4917edd5285711be2d124ad7603b31e54863647474f82ab56c4c954b1d9dd8ba1dafbba61b61aed803124b106a0240bb4a566681314eb44d294d4aa64d3a3d7b1dd0b18739893d284a53f4e913d227617a6373ba436be7051994ab1c077d003f509d588f3ceac96f76ed1110151f526032b21a0c1f00734f466e83060d90715818f7248f6b30372953b2dfbb6990104c26f05d441d866a507ca07a3b2ee9c3130aacd9b3326b9681476299613eeb43313512897743d9367137a772b8457e287e4c4356502bb0cedc3c7d02527b5f594e7a50acd00ed8c7944b6ead6d6e32daec29f2f08a212b7d6416b36dcc71a40b8d39d7ab9844f3624c5f2896185c2af21857d5fe6a6d1d995743c9a497707dde7b2c2a7a9866fa36150825c18f308b71424321aed4711d789e022156b84b9becf42d645b137b0cd6bb3d320db77ef7afe92a9c906f04c8fcf865a49617150e59992dad75366e0bd48165a241ab2ea1534b2d2d4ac53ffa77e2505fef0f77fc86d23d4ea0a56fa8b5d86cecb9c21c4b71c6616539e268ba9bde1920efca7eb47c671e245bb144a4d05d49a21a760dba6e971aa1af1856484da976ad1494451734f51400b1a433c3e52b2df423cd1642236c6757cbc62296c58e487d9dde78e7c0c7591ee1c84729910725bad3bc5015fb8b0a4cd1371eb56ab66eb676af344c61ef17f54e3c300ff2c305120b4455e3b35572f869dc7b394bae67c1ee5a04af3ecc5063d6d55e3344c60bfc5d444c589628139974c4457956360f3ef0a75b2cc77327918018088ef8fa3e55b7f45fc937780da2cd481bd6a6ae373a382e190ed87a26eaa8a76a1c7090493024f913ed01b83e9cdde0667d28cf095561a056f298003262a6172268503d53e0984a3f602f8930b6283d337d730b4c05b639413767306f0bbee362373f95222639927182d302023d1a527c567f9e3731f9bf0e24d3ac2add41c1030ead1559df0f7e6eef674a5dfd1e817cecaec91c3483890975ebf74b5ff8c05145706038cade10328f1fc65b35828357a195a971b32cb92e94d331614810b20a8c3178610fa72e69261cca4631404c79822e8e36a6e9e0042d8be530759c5a63b4d89116da07b066be038577f6e9122a7eb5a868a02db7565807a026470e7b2c0ccfa63cb314ad04ea1d9016d5bded4019a61467f6778e59b8083204762c716d1416ce070c01bc2469429a160b7bc82a56aa56508ed4195ce386c520f141316ea76b261eddca9303aa379e77d16a6d65cc6c7c2e4415b670050c332fc7d2d36c1be2800d39b1f306d2144775ca0d544d93d0ce5e18daf03982b0c2408c655f4f3c48d851d1726873471b99165f2e30063151803f9dcd2537436407480a8cb95cdb2cfc643deb393ea4021e76fbae5c0bea38a51891a2e320d032ad619067262fc1c44f506823da10a3e38f55d69cd95fead33d2a7c42f912c03ae64ad65db07ddcf95f728fe06838ebdb4b4cf3a65f3061819920efcd5115e3fa5e0fa63cff4b2baf371c85226360639e1709c6625e0288671f263ed84a09a95cce2aa826bc534e959e2162ee596512510c456abd6d3a57cf8763f709c24e05bc223544bea6473b3d526ac76fed313cbc3251d3c3ad1f58f9de3d2bd3ef3d9b55a24caacf600b2c232a398f9918610118fd2ef0a8691a406c5a5094ffb42e0ad1f164b2f0f60117aa2246acba444222365c34c7884d2c19d0e14204c3ab6254cc36043b5d1114fc6722250485031d63aa974eacee2e46c0c7ea23fed3c30774edc360db1b5529bed9f9080ee061174d75f930e9a7ef6a903b96062d7db1628baf5c1ad4f7171156466c33ff0b446bcd722a5e025ffe3024b688209082da36f899a12b781ab6574383502aaff4504161564f282fb6fd26151c1d2b7d2a9e53da491e3de92a0168b3f17c76337952792d8c75318a70f33fd8db77731961f4297fbb446dce9efb6ebff6175d69ffac4c89c0985d1682d252d1f18d74e1a4306c13741149c8728d2337cd621a76075b4582947c7bd3d948409def8739b9fcc52ddfff5472b5f5130171c40d200f0db706a85ec7054cea874f7491a815ab8f8229339940724fb754489ff1fd6cec3a997e1465e15cafb564786a64563183e4dd1b86e55c32b4e6cc7a7a09372ce4be746f3af52b44dde43e587f34036a30b41d48e5969142b0d5b3009f9de8682a4b081ceee2c50296c3fc4ddee73475293c5650fc34103b3e1c205480b0080d6a1c4277372f7e40a995af5626b1004a0ac4485a0dc87f5bb6a0455413392814cb3adf39ffb3c616b94b3e3a343d27189a179e17c47a556d03cffb54be101d265703e60f729f9e34d3c09d2cf9d98647ab0a5d4e262771682e1d0e21dfd28e7bbf462950e0561a79ace93b3884aeba64a515f402250b1744355c0f2f418f0d2f3a69db46983042316691fd7a4303701026a4aa727f75ef245e15ec047fc7e209" +} \ No newline at end of file diff --git a/tests/test_vectors/xmss_test_vector.json b/tests/test_vectors/xmss_test_vector.json new file mode 100644 index 00000000..e4d36a21 --- /dev/null +++ b/tests/test_vectors/xmss_test_vector.json @@ -0,0 +1,6 @@ +{ + "public_key": "0xa952704f32175406c216be5af21834765545ef4aa13c06633059954c34aff83d68e86d059be8f01d2f57e73427654d192973d93f", + "slot": 4, + "message": "0xabababababababababababababababababababababababababababababababab", + "signature_ssz": "0x24000000d4aefd0aa1d828289c6f6544f9b7d35a4fd45a2833ef934d38b9bf2128010000040000008d234c504472c07a0d972037fa8d4b713991ff0724aa0c42bbb3f015f14a753f9e53203de989140f1235826fdf907a3067c98a6a2b967722fef1e573fe153a24f9f6232c0b4931757e849d3267bf476484b033740bd1242fb136b8761b2e5152e3bf951519061b3dd7975a0083e67975857964095b1da40971eeed3061e9160bb815f7734a1cf071ee704966e36e577bf3489256e16e070899119327ea567963600ab260d16adb63a417ff177eb8f02fb46c66132fcaf169e225847745d1fd67913a207da6af054c13ad7d44878fcb08bc902c73f26d644925b8063c6a99361e68fa3b06815c8325cc16b119a346681637a66440804e0849c0828770bfcc3a4436884e5deb882949677c700e2fe53d6ae42c752f7a7eea26a68d122293e0830d31377c197854fc6c9186bb5261adc86e7c17fa25a1fec51965fe060ffbf52a7e8390742a78f98d26d4ba825459cc4d786a5bb14a6ceefc20bad1fe55cee81c35d0ce0a37b0d1961bb09a0f0cadb1d24b216b5d069a5d1f6e73438e680797d219" +}