diff --git a/Cargo.lock b/Cargo.lock index 5cc2ae9e7..ebbaa0a2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,9 +95,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-consensus" -version = "1.0.36" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd9d29a6a0bb8d4832ff7685dcbb430011b832f2ccec1af9571a0e75c1f7e9c" +checksum = "83447eeb17816e172f1dfc0db1f9dc0b7c5d069bd1f7cecbecceb382bf931015" dependencies = [ "alloy-eips", "alloy-primitives", @@ -106,12 +106,13 @@ dependencies = [ "alloy-trie", "alloy-tx-macros", "auto_impl", + "borsh", "c-kzg", "derive_more 2.0.1", "either", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", @@ -133,54 +134,84 @@ dependencies = [ [[package]] name = "alloy-eip2930" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.6.1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" dependencies = [ "alloy-primitives", "alloy-rlp", + "borsh", + "once_cell", "serde", "thiserror 2.0.12", ] +[[package]] +name = "alloy-eip7928" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b12337f74cbfa451cb04dac173974814a6ff463079e1793aa09600ba8813ab" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "once_cell", + "thiserror 2.0.12", +] + [[package]] name = "alloy-eips" -version = "1.0.36" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfec530782b30151e2564edf3c900f1fa6852128b7a993e458e8e3815d8b915" +checksum = "0dca4c89ace90684b4b77366d00631ed498c9af962079af2a5dbc593a0618a77" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", + "alloy-eip7928 0.3.7", "alloy-primitives", "alloy-rlp", "alloy-serde", "auto_impl", + "borsh", "c-kzg", "derive_more 2.0.1", "either", "serde", "serde_with", "sha2", - "thiserror 2.0.12", ] [[package]] name = "alloy-primitives" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b77f7d5e60ad8ae6bd2200b8097919712a07a6db622a4b201e7ead6166f02e5" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", "bytes", @@ -188,7 +219,7 @@ dependencies = [ "const-hex", "derive_more 2.0.1", "foldhash", - "hashbrown 0.16.0", + "hashbrown 0.17.1", "indexmap 2.9.0", "itoa", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -196,18 +227,19 @@ dependencies = [ "paste", "proptest", "rand 0.9.2", + "rapidhash", "ruint", "rustc-hash", + "secp256k1 0.31.1", "serde", - "sha3", - "tiny-keccak", + "sha3 0.11.0", ] [[package]] name = "alloy-rlp" -version = "0.3.12" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -216,9 +248,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.12" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +checksum = "9d4311c03125e8a18296504560b9de3d75ecbd0dcda7f71e6cf2a196d57e6fba" dependencies = [ "proc-macro2", "quote", @@ -227,9 +259,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.36" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3835bdc128f2f3418f5d6c76aec63a245d72973e0eaacc9720aa0787225c5" +checksum = "c1e97b3e0b9f816b25083045dcfa69431bd059a078e828e4d82d296d1949b96c" dependencies = [ "alloy-primitives", "serde", @@ -238,28 +270,27 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", "derive_more 2.0.1", "nybbles", "serde", "smallvec", + "thiserror 2.0.12", "tracing", ] [[package]] name = "alloy-tx-macros" -version = "1.0.36" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc79013f9ac3a8ddeb60234d43da09e6d6abfc1c9dd29d3fe97adfbece3f4a08" +checksum = "406bc1183f6843e0aba09f7b3365e828b597213d60793ba5cb41befc863e3a78" dependencies = [ - "alloy-primitives", - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.101", @@ -771,9 +802,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" @@ -816,6 +847,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bls12_381" version = "0.7.1" @@ -841,6 +881,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" +dependencies = [ + "once_cell", + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -1035,7 +1099,7 @@ dependencies = [ [[package]] name = "ceno_crypto_primitives" version = "0.1.0" -source = "git+https://github.com/scroll-tech/ceno-patch.git?branch=main#6e90bc85bceefe09003f84ca5cc1afbe2911a2db" +source = "git+https://github.com/scroll-tech/ceno-patch.git?branch=main#48f82923063a75af9a8adb65949fe3ec6463e705" dependencies = [ "ceno_syscall", "elliptic-curve", @@ -1182,7 +1246,7 @@ dependencies = [ [[package]] name = "ceno_syscall" version = "0.1.0" -source = "git+https://github.com/scroll-tech/ceno-patch.git?branch=main#6e90bc85bceefe09003f84ca5cc1afbe2911a2db" +source = "git+https://github.com/scroll-tech/ceno-patch.git?branch=main#48f82923063a75af9a8adb65949fe3ec6463e705" [[package]] name = "ceno_zkvm" @@ -1250,6 +1314,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chrono" version = "0.4.42" @@ -1364,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -1449,6 +1519,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.3.0" @@ -1584,6 +1663,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "csv" version = "1.3.1" @@ -1655,6 +1743,16 @@ dependencies = [ "darling_macro 0.21.3", ] +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + [[package]] name = "darling_core" version = "0.20.11" @@ -1683,6 +1781,19 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.101", +] + [[package]] name = "darling_macro" version = "0.20.11" @@ -1705,6 +1816,17 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.101", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -1956,12 +2078,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.6", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -2062,9 +2194,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -2242,7 +2374,7 @@ dependencies = [ [[package]] name = "ff_ext" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "once_cell", "p3", @@ -2532,12 +2664,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "foldhash", "serde", + "serde_core", ] [[package]] @@ -2591,6 +2724,15 @@ 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 = "iana-time-zone" version = "0.1.64" @@ -2926,7 +3068,7 @@ dependencies = [ [[package]] name = "k256" version = "0.13.4" -source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#17adc274db2fb10510449026ec785ae4fc234540" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#1f165141f9e6aee785381ec592447aeffcd2d666" dependencies = [ "ceno_crypto_primitives", "ceno_syscall", @@ -2944,14 +3086,24 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures", + "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.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3164,7 +3316,7 @@ dependencies = [ [[package]] name = "mpcs" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "bincode 1.3.3", "clap", @@ -3187,7 +3339,7 @@ dependencies = [ [[package]] name = "multilinear_extensions" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "either", "ff_ext", @@ -3234,6 +3386,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3377,17 +3535,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive 0.7.4", - "rustversion", + "num_enum_derive", ] [[package]] @@ -3402,17 +3550,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "num_threads" version = "0.1.7" @@ -3891,7 +4028,7 @@ dependencies = [ [[package]] name = "p256" version = "0.13.2" -source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#17adc274db2fb10510449026ec785ae4fc234540" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#1f165141f9e6aee785381ec592447aeffcd2d666" dependencies = [ "ceno_crypto_primitives", "ceno_syscall", @@ -3904,7 +4041,7 @@ dependencies = [ [[package]] name = "p3" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "p3-air", "p3-baby-bear", @@ -4415,7 +4552,7 @@ dependencies = [ [[package]] name = "poseidon" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "ff_ext", "p3", @@ -4497,7 +4634,7 @@ dependencies = [ [[package]] name = "primeorder" version = "0.13.6" -source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#17adc274db2fb10510449026ec785ae4fc234540" +source = "git+https://github.com/scroll-tech/elliptic-curves?branch=ceno%2Fk256-13.4#1f165141f9e6aee785381ec592447aeffcd2d666" dependencies = [ "elliptic-curve", ] @@ -4693,6 +4830,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -4821,11 +4967,49 @@ dependencies = [ "tstr", ] +[[package]] +name = "revm-bytecode" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b378c2653331fe60969d9745e802cd773d82a20d8aaced914dfcf26ab8f0d9" +dependencies = [ + "bitvec", + "revm-primitives", +] + +[[package]] +name = "revm-context-interface" +version = "19.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c13f1dfc79425931fd184b6bd373dfac7baba50859b01107d5c0e20549cbb" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database-interface" +version = "12.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2656187f9f9c22ef9dd9300ed71aeaeca3506a6a0a229a07f264649b960d68" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "thiserror 2.0.12", +] + [[package]] name = "revm-precompile" -version = "27.0.0" +version = "36.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" +checksum = "191db865091e07ecb80b12ce3048192c76071ca3d2b0a315b111b271cd4ced37" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -4837,6 +5021,7 @@ dependencies = [ "cfg-if", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "p256 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "revm-context-interface", "revm-primitives", "ripemd", "sha2", @@ -4844,15 +5029,27 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "20.2.1" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" +checksum = "fe5102d804892908d4ebf68da29b8562895922dffa26c230ff2c4dadcf93916f" dependencies = [ "alloy-primitives", - "num_enum 0.7.4", "once_cell", ] +[[package]] +name = "revm-state" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eff6067185cf80932e06f6a9c8045b012ecb6f99a8d6edc618ec2792373e14" +dependencies = [ + "alloy-eip7928 0.4.5", + "bitflags", + "nonmax", + "revm-bytecode", + "revm-primitives", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -4907,7 +5104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3372685893a9f67d18e98e792d690017287fd17379a83d798d958e517d380fa9" dependencies = [ "downcast-rs", - "num_enum 0.5.11", + "num_enum", "paste", ] @@ -5114,7 +5311,7 @@ checksum = "85ed54b1141d8cec428d8a4abf01282755ba4e4c8a621dd23fa2e0ed761814c2" dependencies = [ "base16ct", "once_cell", - "secp256k1", + "secp256k1 0.30.0", "subtle", ] @@ -5126,7 +5323,18 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys 0.11.0", ] [[package]] @@ -5138,6 +5346,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "0.11.0" @@ -5256,7 +5473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -5267,14 +5484,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.7", - "keccak", + "keccak 0.1.5", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] name = "sha3-asm" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" dependencies = [ "cc", "cfg-if", @@ -5333,7 +5560,7 @@ dependencies = [ [[package]] name = "sp1-curves" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "cfg-if", "dashu", @@ -5487,7 +5714,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sumcheck" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "either", "ff_ext", @@ -5505,7 +5732,7 @@ dependencies = [ [[package]] name = "sumcheck_macro" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "itertools 0.13.0", "p3", @@ -5877,7 +6104,7 @@ dependencies = [ [[package]] name = "transcript" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "ff_ext", "itertools 0.13.0", @@ -6188,7 +6415,7 @@ dependencies = [ [[package]] name = "whir" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "bincode 1.3.3", "clap", @@ -6475,7 +6702,7 @@ dependencies = [ [[package]] name = "witness" version = "0.1.0" -source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.34#74efcb2499c036bf8a1c0bc8cb10a5fdcdca0cd2" +source = "git+https://github.com/scroll-tech/gkr-backend.git?tag=v1.0.0-alpha.35#5c9c8a61f25b6f52f0393caf837ef3be5f1a7ef8" dependencies = [ "ff_ext", "multilinear_extensions", @@ -6655,7 +6882,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "sha3", + "sha3 0.10.8", "subtle", ] @@ -6682,7 +6909,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2", - "sha3", + "sha3 0.10.8", "subtle", ] diff --git a/Cargo.toml b/Cargo.toml index de6249b6f..9140f9d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,24 +27,24 @@ version = "0.1.0" ceno_crypto_primitives = { git = "https://github.com/scroll-tech/ceno-patch.git", package = "ceno_crypto_primitives", branch = "main" } ceno_syscall = { git = "https://github.com/scroll-tech/ceno-patch.git", package = "ceno_syscall", branch = "main" } -ff_ext = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "ff_ext", tag = "v1.0.0-alpha.34" } -mpcs = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "mpcs", tag = "v1.0.0-alpha.34", features = ["whir"] } -multilinear_extensions = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "multilinear_extensions", tag = "v1.0.0-alpha.34" } -p3 = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "p3", tag = "v1.0.0-alpha.34" } +ff_ext = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "ff_ext", tag = "v1.0.0-alpha.35" } +mpcs = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "mpcs", tag = "v1.0.0-alpha.35", features = ["whir"] } +multilinear_extensions = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "multilinear_extensions", tag = "v1.0.0-alpha.35" } +p3 = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "p3", tag = "v1.0.0-alpha.35" } p3-field = { version = "=0.4.3", default-features = false } -poseidon = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "poseidon", tag = "v1.0.0-alpha.34" } -sp1-curves = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sp1-curves", tag = "v1.0.0-alpha.34" } -sumcheck = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sumcheck", tag = "v1.0.0-alpha.34" } -transcript = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "transcript", tag = "v1.0.0-alpha.34" } -whir = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "whir", tag = "v1.0.0-alpha.34" } -witness = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "witness", tag = "v1.0.0-alpha.34" } +poseidon = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "poseidon", tag = "v1.0.0-alpha.35" } +sp1-curves = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sp1-curves", tag = "v1.0.0-alpha.35" } +sumcheck = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sumcheck", tag = "v1.0.0-alpha.35" } +transcript = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "transcript", tag = "v1.0.0-alpha.35" } +whir = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "whir", tag = "v1.0.0-alpha.35" } +witness = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "witness", tag = "v1.0.0-alpha.35" } anyhow = { version = "1.0", default-features = false } bincode = "1" cfg-if = "1.0" clap = { version = "4.5", features = ["derive"] } criterion = { version = "0.5", features = ["html_reports"] } -either = { version = "1.15.*", features = ["serde"] } +either = { version = "1.16.*", features = ["serde"] } generic-array = { version = "1.2", features = ["alloc", "serde"] } itertools = "0.13" ndarray = "*" @@ -87,7 +87,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } typenum = "*" uint = "0.8" -revm-precompile = { version = "27.0.0", default-features = false } +revm-precompile = { version = "=36.0.3", default-features = false } ceno_gpu = { git = "https://github.com/scroll-tech/ceno-gpu-mock.git", package = "cuda_hal", branch = "main", default-features = false, features = ["bb31"] } cudarc = { version = "0.17.3", features = ["driver", "cuda-version-from-build-system"] } diff --git a/ceno_emul/src/guest_profile.rs b/ceno_emul/src/guest_profile.rs new file mode 100644 index 000000000..a90b0b043 --- /dev/null +++ b/ceno_emul/src/guest_profile.rs @@ -0,0 +1,293 @@ +use crate::InsnKind; +use serde::Serialize; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; + +#[derive(Clone, Debug)] +pub struct GuestFunctionSymbol { + pub start: u32, + pub end: u32, + pub name: String, +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct GuestFunctionProfileEntry { + pub rank: usize, + pub function: String, + pub pc_start: u32, + pub pc_end: u32, + pub instructions: u64, + pub estimated_cells: u64, + pub percent_instructions: f64, + pub percent_cells: f64, + pub ecall_steps: u64, + pub top_opcodes: Vec<(String, u64)>, + pub suspected_crypto: Option, +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct GuestFunctionProfileReport { + pub total_non_ecall_instructions: u64, + pub total_estimated_cells: u64, + pub total_ecall_steps: u64, + pub top_functions: Vec, +} + +#[derive(Clone, Debug, Default)] +struct GuestFunctionCounters { + instructions: u64, + estimated_cells: u64, + ecall_steps: u64, + opcodes: BTreeMap, +} + +#[derive(Clone, Debug)] +pub struct GuestFunctionProfiler { + symbols: Vec, + counters: Vec, + unknown: GuestFunctionCounters, + last_symbol_index: Option, +} + +impl GuestFunctionProfiler { + pub fn new(symbols: Vec) -> Self { + let counters = vec![GuestFunctionCounters::default(); symbols.len()]; + Self { + symbols, + counters, + unknown: GuestFunctionCounters::default(), + last_symbol_index: None, + } + } + + #[inline(always)] + pub fn observe(&mut self, pc: u32, kind: InsnKind, estimated_cells: u64) { + let counters = match self.symbol_index(pc) { + Some(idx) => &mut self.counters[idx], + None => &mut self.unknown, + }; + + if matches!(kind, InsnKind::ECALL) { + counters.ecall_steps += 1; + return; + } + + counters.instructions += 1; + counters.estimated_cells += estimated_cells; + *counters.opcodes.entry(kind.to_string()).or_default() += 1; + } + + pub fn report(&self, top_n: usize) -> GuestFunctionProfileReport { + let total_non_ecall_instructions = self + .counters + .iter() + .map(|counters| counters.instructions) + .sum::() + + self.unknown.instructions; + let total_estimated_cells = self + .counters + .iter() + .map(|counters| counters.estimated_cells) + .sum::() + + self.unknown.estimated_cells; + let total_ecall_steps = self + .counters + .iter() + .map(|counters| counters.ecall_steps) + .sum::() + + self.unknown.ecall_steps; + + let mut entries = self + .symbols + .iter() + .zip(&self.counters) + .map(|(symbol, counters)| { + self.entry( + symbol.name.clone(), + symbol.start, + symbol.end, + counters, + total_non_ecall_instructions, + total_estimated_cells, + ) + }) + .collect::>(); + + if self.unknown.instructions > 0 || self.unknown.ecall_steps > 0 { + entries.push(self.entry( + "".to_string(), + 0, + 0, + &self.unknown, + total_non_ecall_instructions, + total_estimated_cells, + )); + } + + entries.sort_by(|a, b| { + b.estimated_cells + .cmp(&a.estimated_cells) + .then_with(|| b.instructions.cmp(&a.instructions)) + }); + entries.truncate(top_n); + for (idx, entry) in entries.iter_mut().enumerate() { + entry.rank = idx + 1; + } + + GuestFunctionProfileReport { + total_non_ecall_instructions, + total_estimated_cells, + total_ecall_steps, + top_functions: entries, + } + } + + pub fn write_reports(&self, path: &Path, top_n: usize) -> std::io::Result<()> { + let report = self.report(top_n); + let (json_path, md_path) = report_paths(path); + if let Some(parent) = json_path.parent() { + if !parent.as_os_str().is_empty() { + std::fs::create_dir_all(parent)?; + } + } + let json = serde_json::to_vec_pretty(&report).map_err(std::io::Error::other)?; + std::fs::write(&json_path, json)?; + std::fs::write(&md_path, render_markdown(&report))?; + tracing::info!( + "wrote guest function profile to {} and {}", + json_path.display(), + md_path.display() + ); + Ok(()) + } + + #[inline(always)] + fn symbol_index(&mut self, pc: u32) -> Option { + if let Some(idx) = self.last_symbol_index { + let symbol = &self.symbols[idx]; + if symbol.start <= pc && pc < symbol.end { + return Some(idx); + } + } + + let idx = self + .symbols + .partition_point(|symbol| symbol.start <= pc) + .checked_sub(1)?; + let symbol = &self.symbols[idx]; + if pc < symbol.end { + self.last_symbol_index = Some(idx); + Some(idx) + } else { + self.last_symbol_index = None; + None + } + } + + fn entry( + &self, + function: String, + pc_start: u32, + pc_end: u32, + counters: &GuestFunctionCounters, + total_instructions: u64, + total_cells: u64, + ) -> GuestFunctionProfileEntry { + let mut top_opcodes = counters + .opcodes + .iter() + .map(|(kind, count)| (kind.clone(), *count)) + .collect::>(); + top_opcodes.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0))); + top_opcodes.truncate(8); + + GuestFunctionProfileEntry { + rank: 0, + suspected_crypto: suspected_crypto(&function), + function, + pc_start, + pc_end, + instructions: counters.instructions, + estimated_cells: counters.estimated_cells, + percent_instructions: percent(counters.instructions, total_instructions), + percent_cells: percent(counters.estimated_cells, total_cells), + ecall_steps: counters.ecall_steps, + top_opcodes, + } + } +} + +fn percent(part: u64, total: u64) -> f64 { + if total == 0 { + 0.0 + } else { + part as f64 * 100.0 / total as f64 + } +} + +fn report_paths(path: &Path) -> (PathBuf, PathBuf) { + match path.extension().and_then(|ext| ext.to_str()) { + Some("md") => (path.with_extension("json"), path.to_path_buf()), + Some("json") => (path.to_path_buf(), path.with_extension("md")), + _ => (path.with_extension("json"), path.with_extension("md")), + } +} + +fn suspected_crypto(function: &str) -> Option { + let lower = function.to_ascii_lowercase(); + for (needle, label) in [ + ("keccak", "keccak"), + ("sha", "sha"), + ("secp256k1", "secp256k1"), + ("secp256r1", "secp256r1"), + ("bn254", "bn254"), + ("bls", "bls"), + ("modexp", "modexp"), + ("mulmod", "mulmod"), + ("uint256", "uint256"), + ("hash", "hash"), + ("trie", "trie"), + ("rlp", "rlp"), + ] { + if lower.contains(needle) { + return Some(label.to_string()); + } + } + None +} + +fn render_markdown(report: &GuestFunctionProfileReport) -> String { + let mut out = String::new(); + out.push_str("# Guest Function Profile\n\n"); + out.push_str(&format!( + "- total non-ecall instructions: {}\n- total estimated cells: {}\n- total ecall steps: {}\n\n", + report.total_non_ecall_instructions, + report.total_estimated_cells, + report.total_ecall_steps + )); + out.push_str("| rank | function | instructions | cells | inst % | cell % | ecall steps | top opcodes | tag |\n"); + out.push_str("| ---: | --- | ---: | ---: | ---: | ---: | ---: | --- | --- |\n"); + for entry in &report.top_functions { + let opcodes = entry + .top_opcodes + .iter() + .map(|(kind, count)| format!("{kind}:{count}")) + .collect::>() + .join(", "); + out.push_str(&format!( + "| {} | `{}` | {} | {} | {:.2} | {:.2} | {} | {} | {} |\n", + entry.rank, + entry.function.replace('|', "\\|"), + entry.instructions, + entry.estimated_cells, + entry.percent_instructions, + entry.percent_cells, + entry.ecall_steps, + opcodes, + entry.suspected_crypto.as_deref().unwrap_or("") + )); + } + out +} diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 2f4ca0a0d..db79489bb 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -37,8 +37,8 @@ pub mod disassemble; mod syscalls; pub use syscalls::{ BLS12381_ADD, BLS12381_DECOMPRESS, BLS12381_DOUBLE, BN254_ADD, BN254_DOUBLE, BN254_FP_ADD, - BN254_FP_MUL, BN254_FP2_ADD, BN254_FP2_MUL, KECCAK_PERMUTE, PubIoCommitSpec, SECP256K1_ADD, - SECP256K1_DECOMPRESS, SECP256K1_DOUBLE, SECP256K1_SCALAR_INVERT, SECP256R1_ADD, + BN254_FP_MUL, BN254_FP2_ADD, BN254_FP2_MUL, KECCAK_PERMUTE, KECCAK_XORIN, PubIoCommitSpec, + SECP256K1_ADD, SECP256K1_DECOMPRESS, SECP256K1_DOUBLE, SECP256K1_SCALAR_INVERT, SECP256R1_ADD, SECP256R1_DECOMPRESS, SECP256R1_DOUBLE, SECP256R1_SCALAR_INVERT, SHA_EXTEND, STATE_CONTINUATION, SyscallSpec, SyscallWitness, UINT256_MUL, bn254::{ @@ -46,6 +46,7 @@ pub use syscalls::{ Bn254Fp2AddSpec, Bn254Fp2MulSpec, Bn254FpAddSpec, Bn254FpMulSpec, }, keccak_permute::{KECCAK_WORDS, KeccakSpec}, + keccak_xorin::{KECCAK_RATE_WORDS, KeccakXorinSpec}, phantom::LogPcCycleSpec, secp256k1::{ COORDINATE_WORDS as SECP256K1_COORDINATE_WORDS, SECP256K1_ARG_WORDS, Secp256k1AddSpec, diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index e66798eba..19a9ea246 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -3,6 +3,7 @@ use anyhow::Result; pub mod bn254; pub mod keccak_permute; +pub mod keccak_xorin; pub mod phantom; pub mod pubio_commit; pub mod secp256k1; @@ -14,7 +15,7 @@ pub mod uint256; pub use ceno_syscall::{ BLS12381_ADD, BLS12381_DECOMPRESS, BLS12381_DOUBLE, BN254_ADD, BN254_DOUBLE, BN254_FP_ADD, - BN254_FP_MUL, BN254_FP2_ADD, BN254_FP2_MUL, KECCAK_PERMUTE, PHANTOM_LOG_PC_CYCLE, + BN254_FP_MUL, BN254_FP2_ADD, BN254_FP2_MUL, KECCAK_PERMUTE, KECCAK_XORIN, PHANTOM_LOG_PC_CYCLE, PUB_IO_COMMIT, SECP256K1_ADD, SECP256K1_DECOMPRESS, SECP256K1_DOUBLE, SECP256K1_SCALAR_INVERT, SECP256R1_ADD, SECP256R1_DECOMPRESS, SECP256R1_DOUBLE, SECP256R1_SCALAR_INVERT, SHA_EXTEND, STATE_CONTINUATION, UINT256_MUL, @@ -37,6 +38,7 @@ pub trait SyscallSpec { pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { match function_code { KECCAK_PERMUTE => Ok(keccak_permute::keccak_permute(vm)), + KECCAK_XORIN => Ok(keccak_xorin::keccak_xorin(vm)), SECP256K1_ADD => Ok(secp256k1::secp256k1_add(vm)), SECP256K1_DOUBLE => Ok(secp256k1::secp256k1_double(vm)), SECP256K1_DECOMPRESS => Ok(secp256k1::secp256k1_decompress(vm)), diff --git a/ceno_emul/src/syscalls/keccak_xorin.rs b/ceno_emul/src/syscalls/keccak_xorin.rs new file mode 100644 index 000000000..855ccbd9c --- /dev/null +++ b/ceno_emul/src/syscalls/keccak_xorin.rs @@ -0,0 +1,60 @@ +use itertools::Itertools; + +use crate::{Change, EmuContext, Platform, Tracer, VMState, WORD_SIZE, WriteOp, utils::MemoryView}; + +use super::{SyscallEffects, SyscallSpec, SyscallWitness}; + +pub const KECCAK_RATE_WORDS: usize = 34; + +pub struct KeccakXorinSpec; + +impl SyscallSpec for KeccakXorinSpec { + const NAME: &'static str = "KECCAK_XORIN"; + + const REG_OPS_COUNT: usize = 2; + const MEM_OPS_COUNT: usize = KECCAK_RATE_WORDS * 2; + const CODE: u32 = ceno_syscall::KECCAK_XORIN; + const HAS_LOOKUPS: bool = true; +} + +/// XOR a fixed 136-byte block into the Keccak rate portion of the state. +pub fn keccak_xorin(vm: &VMState) -> SyscallEffects { + let state_ptr = vm.peek_register(Platform::reg_arg0()); + let block_ptr = vm.peek_register(Platform::reg_arg1()); + let rate_bytes = (KECCAK_RATE_WORDS * WORD_SIZE) as u32; + let state_bytes = (ceno_syscall::KECCAK_STATE_WORDS * 2 * WORD_SIZE) as u32; + let state_end = state_ptr + .checked_add(state_bytes) + .expect("state range overflow"); + let block_end = block_ptr + .checked_add(rate_bytes) + .expect("block range overflow"); + assert!( + state_end <= block_ptr || block_end <= state_ptr, + "Keccak state and XOR-in block must not overlap" + ); + + let reg_ops = vec![ + WriteOp::new_register_op(Platform::reg_arg0(), Change::new(state_ptr, state_ptr), 0), + WriteOp::new_register_op(Platform::reg_arg1(), Change::new(block_ptr, block_ptr), 0), + ]; + + let block_view = MemoryView::<_, KECCAK_RATE_WORDS>::new(vm, block_ptr); + let block = block_view.words(); + let mut state_view = MemoryView::<_, KECCAK_RATE_WORDS>::new(vm, state_ptr); + let state = state_view.words(); + let output = std::array::from_fn(|i| state[i] ^ block[i]); + state_view.write(output); + + // The circuit relies on this exact ordering: all scratch reads, then all state writes. + let mem_ops = block_view + .mem_ops() + .into_iter() + .chain(state_view.mem_ops()) + .collect_vec(); + + SyscallEffects { + witness: SyscallWitness::new(mem_ops, reg_ops), + next_pc: None, + } +} diff --git a/ceno_recursion_v2/Cargo.toml b/ceno_recursion_v2/Cargo.toml index d6939fedf..61c43d750 100644 --- a/ceno_recursion_v2/Cargo.toml +++ b/ceno_recursion_v2/Cargo.toml @@ -20,11 +20,11 @@ clap = { version = "4.5", features = ["derive"] } continuations-v2 = { git = "https://github.com/openvm-org/openvm.git", package = "openvm-continuations", rev = "53784108cf93718b7acd062c0b5a910a82f40156", default-features = false } derive-new = "0.6.0" eyre = "0.6" -ff_ext = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "ff_ext", tag = "v1.0.0-alpha.34" } +ff_ext = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "ff_ext", tag = "v1.0.0-alpha.35" } gkr_iop = { path = "../gkr_iop" } itertools = "0.13" -mpcs = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "mpcs", tag = "v1.0.0-alpha.34" } -multilinear_extensions = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "multilinear_extensions", tag = "v1.0.0-alpha.34" } +mpcs = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "mpcs", tag = "v1.0.0-alpha.35" } +multilinear_extensions = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "multilinear_extensions", tag = "v1.0.0-alpha.35" } openvm = { git = "https://github.com/openvm-org/openvm.git", rev = "53784108cf93718b7acd062c0b5a910a82f40156", default-features = false } openvm-circuit = { git = "https://github.com/openvm-org/openvm.git", rev = "53784108cf93718b7acd062c0b5a910a82f40156", default-features = false } openvm-circuit-primitives = { git = "https://github.com/openvm-org/openvm.git", rev = "53784108cf93718b7acd062c0b5a910a82f40156", default-features = false } @@ -34,7 +34,7 @@ openvm-cuda-common = { git = "https://github.com/openvm-org/stark-backend.git", openvm-poseidon2-air = { git = "https://github.com/openvm-org/openvm.git", rev = "53784108cf93718b7acd062c0b5a910a82f40156", package = "openvm-poseidon2-air", default-features = false } openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", branch = "develop-v2", default-features = false } openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", branch = "develop-v2", default-features = false, features = ["baby-bear-bn254-poseidon2", "cpu-backend"] } -p3 = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "p3", tag = "v1.0.0-alpha.34" } +p3 = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "p3", tag = "v1.0.0-alpha.35" } p3-air = { version = "=0.4.3", default-features = false } p3-field = { version = "=0.4.3", default-features = false } p3-matrix = { version = "=0.4.3", default-features = false } @@ -48,14 +48,14 @@ serde_json = "1.0" stark-recursion-circuit-derive = { git = "https://github.com/openvm-org/openvm.git", package = "openvm-recursion-circuit-derive", rev = "53784108cf93718b7acd062c0b5a910a82f40156" } strum = "0.26" strum_macros = "0.26" -sumcheck = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sumcheck", tag = "v1.0.0-alpha.34" } +sumcheck = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "sumcheck", tag = "v1.0.0-alpha.35" } tracing = { version = "0.1", features = ["attributes"] } tracing-forest = { version = "0.1.6" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } -transcript = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "transcript", tag = "v1.0.0-alpha.34" } +transcript = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "transcript", tag = "v1.0.0-alpha.35" } verify-stark = { git = "https://github.com/openvm-org/openvm.git", package = "openvm-verify-stark-host", rev = "53784108cf93718b7acd062c0b5a910a82f40156", default-features = false } -whir = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "whir", tag = "v1.0.0-alpha.34" } -witness = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "witness", tag = "v1.0.0-alpha.34" } +whir = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "whir", tag = "v1.0.0-alpha.35" } +witness = { git = "https://github.com/scroll-tech/gkr-backend.git", package = "witness", tag = "v1.0.0-alpha.35" } [build-dependencies] openvm-cuda-builder = { git = "https://github.com/openvm-org/stark-backend.git", branch = "develop-v2", optional = true } diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index dd0d4871f..ccbe118ab 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -160,6 +160,11 @@ _start: ", ); +// Provide a strong, universally linked RV32IM memcpy implementation so guest programs do not +// fall back to compiler-builtins' byte-oriented wrapper. +#[cfg(target_arch = "riscv32")] +global_asm!(include_str!("memcpy.s")); + unsafe extern "C" { // The address of this variable is the start of the stack (growing downwards). static _stack_start: u8; diff --git a/ceno_rt/src/memcpy.s b/ceno_rt/src/memcpy.s new file mode 100644 index 000000000..e0043ec22 --- /dev/null +++ b/ceno_rt/src/memcpy.s @@ -0,0 +1,498 @@ +// This is musl-libc commit 37e18b7bf307fa4a8c745feebfcba54a0ba74f30: +// +// src/string/memcpy.c +// +// This was compiled into assembly with: +// +// clang-14 -target riscv32 -march=rv32im -O3 -S memcpy.c -nostdlib -fno-builtin -funroll-loops +// +// and labels manually updated to not conflict. +// +// musl as a whole is licensed under the following standard MIT license: +// +// ---------------------------------------------------------------------- +// Copyright © 2005-2020 Rich Felker, et al. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ---------------------------------------------------------------------- +// +// Authors/contributors include: +// +// A. Wilcox +// Ada Worcester +// Alex Dowad +// Alex Suykov +// Alexander Monakov +// Andre McCurdy +// Andrew Kelley +// Anthony G. Basile +// Aric Belsito +// Arvid Picciani +// Bartosz Brachaczek +// Benjamin Peterson +// Bobby Bingham +// Boris Brezillon +// Brent Cook +// Chris Spiegel +// Clément Vasseur +// Daniel Micay +// Daniel Sabogal +// Daurnimator +// David Carlier +// David Edelsohn +// Denys Vlasenko +// Dmitry Ivanov +// Dmitry V. Levin +// Drew DeVault +// Emil Renner Berthing +// Fangrui Song +// Felix Fietkau +// Felix Janda +// Gianluca Anzolin +// Hauke Mehrtens +// He X +// Hiltjo Posthuma +// Isaac Dunham +// Jaydeep Patil +// Jens Gustedt +// Jeremy Huntwork +// Jo-Philipp Wich +// Joakim Sindholt +// John Spencer +// Julien Ramseier +// Justin Cormack +// Kaarle Ritvanen +// Khem Raj +// Kylie McClain +// Leah Neukirchen +// Luca Barbato +// Luka Perkov +// M Farkas-Dyck (Strake) +// Mahesh Bodapati +// Markus Wichmann +// Masanori Ogino +// Michael Clark +// Michael Forney +// Mikhail Kremnyov +// Natanael Copa +// Nicholas J. Kain +// orc +// Pascal Cuoq +// Patrick Oppenlander +// Petr Hosek +// Petr Skocik +// Pierre Carrier +// Reini Urban +// Rich Felker +// Richard Pennington +// Ryan Fairfax +// Samuel Holland +// Segev Finer +// Shiz +// sin +// Solar Designer +// Stefan Kristiansson +// Stefan O'Rear +// Szabolcs Nagy +// Timo Teräs +// Trutz Behn +// Valentin Ochs +// Will Dietz +// William Haddon +// William Pitcock +// +// Portions of this software are derived from third-party works licensed +// under terms compatible with the above MIT license: +// +// The TRE regular expression implementation (src/regex/reg* and +// src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +// under a 2-clause BSD license (license text in the source files). The +// included version has been heavily modified by Rich Felker in 2012, in +// the interests of size, simplicity, and namespace cleanliness. +// +// Much of the math library code (src/math/* and src/complex/*) is +// Copyright © 1993,2004 Sun Microsystems or +// Copyright © 2003-2011 David Schultz or +// Copyright © 2003-2009 Steven G. Kargl or +// Copyright © 2003-2009 Bruce D. Evans or +// Copyright © 2008 Stephen L. Moshier or +// Copyright © 2017-2018 Arm Limited +// and labelled as such in comments in the individual source files. All +// have been licensed under extremely permissive terms. +// +// The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 +// The Android Open Source Project and is licensed under a two-clause BSD +// license. It was taken from Bionic libc, used on Android. +// +// The AArch64 memcpy and memset code (src/string/aarch64/*) are +// Copyright © 1999-2019, Arm Limited. +// +// The implementation of DES for crypt (src/crypt/crypt_des.c) is +// Copyright © 1994 David Burren. It is licensed under a BSD license. +// +// The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was +// originally written by Solar Designer and placed into the public +// domain. The code also comes with a fallback permissive license for use +// in jurisdictions that may not recognize the public domain. +// +// The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 +// Valentin Ochs and is licensed under an MIT-style license. +// +// The x86_64 port was written by Nicholas J. Kain and is licensed under +// the standard MIT terms. +// +// The mips and microblaze ports were originally written by Richard +// Pennington for use in the ellcc project. The original code was adapted +// by Rich Felker for build system and code conventions during upstream +// integration. It is licensed under the standard MIT terms. +// +// The mips64 port was contributed by Imagination Technologies and is +// licensed under the standard MIT terms. +// +// The powerpc port was also originally written by Richard Pennington, +// and later supplemented and integrated by John Spencer. It is licensed +// under the standard MIT terms. +// +// All other files which have no copyright comments are original works +// produced specifically for use as part of this library, written either +// by Rich Felker, the main author of the library, or by one or more +// contributors listed above. Details on authorship of individual files +// can be found in the git version control history of the project. The +// omission of copyright and license comments in each file is in the +// interest of source tree size. +// +// In addition, permission is hereby granted for all public header files +// (include/* and arch/* /bits/* ) and crt files intended to be linked into +// applications (crt/*, ldso/dlstart.c, and arch/* /crt_arch.h) to omit +// the copyright notice and permission notice otherwise required by the +// license, and to use these files without any requirement of +// attribution. These files include substantial contributions from: +// +// Bobby Bingham +// John Spencer +// Nicholas J. Kain +// Rich Felker +// Richard Pennington +// Stefan Kristiansson +// Szabolcs Nagy +// +// all of whom have explicitly granted such permission. +// +// This file previously contained text expressing a belief that most of +// the files covered by the above exception were sufficiently trivial not +// to be subject to copyright, resulting in confusion over whether it +// negated the permissions granted in the license. In the spirit of +// permissive licensing, and of not having licensing issues being an +// obstacle to adoption, that text has been removed. + .text + .attribute 4, 16 + .attribute 5, "rv32im" + .file "musl_memcpy.c" + .globl memcpy + .p2align 2 + .type memcpy,@function +memcpy: + andi a3, a1, 3 + seqz a3, a3 + seqz a4, a2 + or a3, a3, a4 + bnez a3, .LBBmemcpy0_11 + addi a5, a1, 1 + mv a6, a0 +.LBBmemcpy0_2: + lb a7, 0(a1) + addi a4, a1, 1 + addi a3, a6, 1 + sb a7, 0(a6) + addi a2, a2, -1 + andi a1, a5, 3 + snez a1, a1 + snez a6, a2 + and a7, a1, a6 + addi a5, a5, 1 + mv a1, a4 + mv a6, a3 + bnez a7, .LBBmemcpy0_2 + andi a1, a3, 3 + beqz a1, .LBBmemcpy0_12 +.LBBmemcpy0_4: + li a5, 32 + bltu a2, a5, .LBBmemcpy0_26 + li a5, 3 + beq a1, a5, .LBBmemcpy0_19 + li a5, 2 + beq a1, a5, .LBBmemcpy0_22 + li a5, 1 + bne a1, a5, .LBBmemcpy0_26 + lw a5, 0(a4) + sb a5, 0(a3) + srli a1, a5, 8 + sb a1, 1(a3) + srli a6, a5, 16 + addi a1, a3, 3 + sb a6, 2(a3) + addi a2, a2, -3 + addi a3, a4, 16 + li a4, 16 +.LBBmemcpy0_9: + lw a6, -12(a3) + srli a5, a5, 24 + slli a7, a6, 8 + lw t0, -8(a3) + or a5, a7, a5 + sw a5, 0(a1) + srli a5, a6, 24 + slli a6, t0, 8 + lw a7, -4(a3) + or a5, a6, a5 + sw a5, 4(a1) + srli a6, t0, 24 + slli t0, a7, 8 + lw a5, 0(a3) + or a6, t0, a6 + sw a6, 8(a1) + srli a6, a7, 24 + slli a7, a5, 8 + or a6, a7, a6 + sw a6, 12(a1) + addi a1, a1, 16 + addi a2, a2, -16 + addi a3, a3, 16 + bltu a4, a2, .LBBmemcpy0_9 + addi a4, a3, -13 + j .LBBmemcpy0_25 +.LBBmemcpy0_11: + mv a3, a0 + mv a4, a1 + andi a1, a3, 3 + bnez a1, .LBBmemcpy0_4 +.LBBmemcpy0_12: + li a1, 16 + bltu a2, a1, .LBBmemcpy0_15 + li a1, 15 +.LBBmemcpy0_14: + lw a5, 0(a4) + lw a6, 4(a4) + lw a7, 8(a4) + lw t0, 12(a4) + sw a5, 0(a3) + sw a6, 4(a3) + sw a7, 8(a3) + sw t0, 12(a3) + addi a4, a4, 16 + addi a2, a2, -16 + addi a3, a3, 16 + bltu a1, a2, .LBBmemcpy0_14 +.LBBmemcpy0_15: + andi a1, a2, 8 + beqz a1, .LBBmemcpy0_17 + lw a1, 0(a4) + lw a5, 4(a4) + sw a1, 0(a3) + sw a5, 4(a3) + addi a3, a3, 8 + addi a4, a4, 8 +.LBBmemcpy0_17: + andi a1, a2, 4 + beqz a1, .LBBmemcpy0_30 + lw a1, 0(a4) + sw a1, 0(a3) + addi a3, a3, 4 + addi a4, a4, 4 + j .LBBmemcpy0_30 +.LBBmemcpy0_19: + lw a5, 0(a4) + addi a1, a3, 1 + sb a5, 0(a3) + addi a2, a2, -1 + addi a3, a4, 16 + li a4, 18 +.LBBmemcpy0_20: + lw a6, -12(a3) + srli a5, a5, 8 + slli a7, a6, 24 + lw t0, -8(a3) + or a5, a7, a5 + sw a5, 0(a1) + srli a5, a6, 8 + slli a6, t0, 24 + lw a7, -4(a3) + or a5, a6, a5 + sw a5, 4(a1) + srli a6, t0, 8 + slli t0, a7, 24 + lw a5, 0(a3) + or a6, t0, a6 + sw a6, 8(a1) + srli a6, a7, 8 + slli a7, a5, 24 + or a6, a7, a6 + sw a6, 12(a1) + addi a1, a1, 16 + addi a2, a2, -16 + addi a3, a3, 16 + bltu a4, a2, .LBBmemcpy0_20 + addi a4, a3, -15 + j .LBBmemcpy0_25 +.LBBmemcpy0_22: + lw a5, 0(a4) + sb a5, 0(a3) + srli a6, a5, 8 + addi a1, a3, 2 + sb a6, 1(a3) + addi a2, a2, -2 + addi a3, a4, 16 + li a4, 17 +.LBBmemcpy0_23: + lw a6, -12(a3) + srli a5, a5, 16 + slli a7, a6, 16 + lw t0, -8(a3) + or a5, a7, a5 + sw a5, 0(a1) + srli a5, a6, 16 + slli a6, t0, 16 + lw a7, -4(a3) + or a5, a6, a5 + sw a5, 4(a1) + srli a6, t0, 16 + slli t0, a7, 16 + lw a5, 0(a3) + or a6, t0, a6 + sw a6, 8(a1) + srli a6, a7, 16 + slli a7, a5, 16 + or a6, a7, a6 + sw a6, 12(a1) + addi a1, a1, 16 + addi a2, a2, -16 + addi a3, a3, 16 + bltu a4, a2, .LBBmemcpy0_23 + addi a4, a3, -14 +.LBBmemcpy0_25: + mv a3, a1 +.LBBmemcpy0_26: + andi a1, a2, 16 + bnez a1, .LBBmemcpy0_35 + andi a1, a2, 8 + bnez a1, .LBBmemcpy0_36 +.LBBmemcpy0_28: + andi a1, a2, 4 + beqz a1, .LBBmemcpy0_30 +.LBBmemcpy0_29: + lb a1, 0(a4) + lb a5, 1(a4) + lb a6, 2(a4) + sb a1, 0(a3) + sb a5, 1(a3) + lb a1, 3(a4) + sb a6, 2(a3) + addi a4, a4, 4 + addi a5, a3, 4 + sb a1, 3(a3) + mv a3, a5 +.LBBmemcpy0_30: + andi a1, a2, 2 + bnez a1, .LBBmemcpy0_33 + andi a1, a2, 1 + bnez a1, .LBBmemcpy0_34 +.LBBmemcpy0_32: + ret +.LBBmemcpy0_33: + lb a1, 0(a4) + lb a5, 1(a4) + sb a1, 0(a3) + addi a4, a4, 2 + addi a1, a3, 2 + sb a5, 1(a3) + mv a3, a1 + andi a1, a2, 1 + beqz a1, .LBBmemcpy0_32 +.LBBmemcpy0_34: + lb a1, 0(a4) + sb a1, 0(a3) + ret +.LBBmemcpy0_35: + lb a1, 0(a4) + lb a5, 1(a4) + lb a6, 2(a4) + sb a1, 0(a3) + sb a5, 1(a3) + lb a1, 3(a4) + sb a6, 2(a3) + lb a5, 4(a4) + lb a6, 5(a4) + sb a1, 3(a3) + lb a1, 6(a4) + sb a5, 4(a3) + sb a6, 5(a3) + lb a5, 7(a4) + sb a1, 6(a3) + lb a1, 8(a4) + lb a6, 9(a4) + sb a5, 7(a3) + lb a5, 10(a4) + sb a1, 8(a3) + sb a6, 9(a3) + lb a1, 11(a4) + sb a5, 10(a3) + lb a5, 12(a4) + lb a6, 13(a4) + sb a1, 11(a3) + lb a1, 14(a4) + sb a5, 12(a3) + sb a6, 13(a3) + lb a5, 15(a4) + sb a1, 14(a3) + addi a4, a4, 16 + addi a1, a3, 16 + sb a5, 15(a3) + mv a3, a1 + andi a1, a2, 8 + beqz a1, .LBBmemcpy0_28 +.LBBmemcpy0_36: + lb a1, 0(a4) + lb a5, 1(a4) + lb a6, 2(a4) + sb a1, 0(a3) + sb a5, 1(a3) + lb a1, 3(a4) + sb a6, 2(a3) + lb a5, 4(a4) + lb a6, 5(a4) + sb a1, 3(a3) + lb a1, 6(a4) + sb a5, 4(a3) + sb a6, 5(a3) + lb a5, 7(a4) + sb a1, 6(a3) + addi a4, a4, 8 + addi a1, a3, 8 + sb a5, 7(a3) + mv a3, a1 + andi a1, a2, 4 + bnez a1, .LBBmemcpy0_29 + j .LBBmemcpy0_30 +.Lfuncmemcpy_end0: + .size memcpy, .Lfuncmemcpy_end0-memcpy + + .ident "Ubuntu clang version 14.0.6-++20220622053131+f28c006a5895-1~exp1~20220622173215.157" + .section ".note.GNU-stack","",@progbits + .addrsig diff --git a/ceno_zkvm/src/e2e.rs b/ceno_zkvm/src/e2e.rs index 4d69b5a02..97d5e0719 100644 --- a/ceno_zkvm/src/e2e.rs +++ b/ceno_zkvm/src/e2e.rs @@ -23,7 +23,7 @@ use crate::{ }; use ceno_emul::{ Addr, ByteAddr, CENO_PLATFORM, Cycle, EmuContext, EmulatorBackend, FullTracer, - FullTracerConfig, IterAddresses, NextCycleAccess, Platform, PreflightTracer, + FullTracerConfig, InsnKind, IterAddresses, NextCycleAccess, Platform, PreflightTracer, PreflightTracerConfig, Program, RegIdx, StepCellExtractor, StepIndex, StepRecord, SyscallWitness, Tracer, VM_REG_COUNT, VMState, WORD_SIZE, Word, WordAddr, host_utils::read_all_messages, @@ -42,9 +42,9 @@ use multilinear_extensions::util::max_usable_threads; use rustc_hash::FxHashSet; use serde::Serialize; #[cfg(debug_assertions)] -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, HashMap}, io::Write, marker::PhantomData, ops::Range, @@ -358,6 +358,14 @@ impl<'a> ShardContext<'a> { } } + pub fn read_record_count(&self) -> usize { + self.read_records().iter().map(BTreeMap::len).sum() + } + + pub fn write_record_count(&self) -> usize { + self.write_records().iter().map(BTreeMap::len).sum() + } + #[inline(always)] pub fn is_first_shard(&self) -> bool { self.shard_id == 0 @@ -448,32 +456,47 @@ impl<'a> ShardContext<'a> { #[inline(always)] pub fn insert_read_record(&mut self, addr: WordAddr, record: RAMRecord) { - let ram_record = self - .read_records_tbs - .as_mut() - .right() - .expect("illegal type"); - ram_record.insert(addr, record); + match &mut self.read_records_tbs { + Either::Left(records) => { + records + .first_mut() + .expect("read record storage must have at least one slot") + .insert(addr, record); + } + Either::Right(records) => { + records.insert(addr, record); + } + } } #[inline(always)] pub fn insert_write_record(&mut self, addr: WordAddr, record: RAMRecord) { - let ram_record = self - .write_records_tbs - .as_mut() - .right() - .expect("illegal type"); - ram_record.insert(addr, record); + match &mut self.write_records_tbs { + Either::Left(records) => { + records + .first_mut() + .expect("write record storage must have at least one slot") + .insert(addr, record); + } + Either::Right(records) => { + records.insert(addr, record); + } + } } #[inline(always)] pub fn push_addr_accessed(&mut self, addr: WordAddr) { - let addr_accessed = self - .addr_accessed_tbs - .as_mut() - .right() - .expect("illegal type"); - addr_accessed.push(addr); + match &mut self.addr_accessed_tbs { + Either::Left(addrs) => { + addrs + .first_mut() + .expect("address access storage must have at least one slot") + .push(addr); + } + Either::Right(addrs) => { + addrs.push(addr); + } + } } #[inline(always)] @@ -1791,6 +1814,522 @@ pub fn generate_witness<'a, E: ExtensionField>( }) } +#[derive(Clone, Debug, Default)] +pub struct ShardRamLightPcStat { + pub pc: Addr, + pub kind: String, + pub read_records: usize, + pub write_records: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct ShardRamLightReport { + pub shard_id: usize, + pub step_count: usize, + pub cycle_range: Range, + pub heap_addr_range: Range, + pub hint_addr_range: Range, + pub read_records: usize, + pub write_records: usize, + pub first_shard_access_later_records: usize, + pub current_shard_access_later_records: usize, + pub total_records: usize, + pub final_mem_by_source: BTreeMap<&'static str, usize>, + pub top_pcs: Vec, + pub top_memcpy_callers: Vec, +} + +#[derive(Clone, Debug, Default)] +struct PcShardRamDelta { + kind: String, + read_records: usize, + write_records: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct ShardRamLightMemcpyCallerStat { + pub caller_pc: Addr, + pub calls: usize, + pub copied_bytes: u64, + pub read_records: usize, + pub write_records: usize, + pub old_source_loads: usize, + pub current_destination_stores: usize, + pub destination_access_later_words: usize, + pub max_len: u32, + pub sample_dst: Addr, + pub sample_src: Addr, +} + +#[derive(Clone, Debug, Default)] +struct MemcpyCallerDelta { + calls: usize, + copied_bytes: u64, + read_records: usize, + write_records: usize, + old_source_loads: usize, + current_destination_stores: usize, + dst_store_words: FxHashSet, + destination_access_later_words: usize, + max_len: u32, + sample_dst: Addr, + sample_src: Addr, +} + +#[derive(Clone, Debug)] +struct ActiveMemcpyCall { + caller_pc: Addr, + dst: Addr, + src: Addr, + len: u32, + read_records: usize, + write_records: usize, + old_source_loads: usize, + current_destination_stores: usize, + dst_store_words: FxHashSet, +} + +impl ActiveMemcpyCall { + fn new(caller_pc: Addr, dst: Addr, src: Addr, len: u32) -> Self { + Self { + caller_pc, + dst, + src, + len, + read_records: 0, + write_records: 0, + old_source_loads: 0, + current_destination_stores: 0, + dst_store_words: FxHashSet::default(), + } + } +} + +impl ShardRamLightReport { + fn log(&self) { + tracing::info!( + "[ceno] shard_ram_light shard_id={} steps={} cycles={:?} heap={:x}-{:x} hints={:x}-{:x} read={} write={} first_access_later={} current_access_later={} total={}", + self.shard_id, + self.step_count, + self.cycle_range, + self.heap_addr_range.start, + self.heap_addr_range.end, + self.hint_addr_range.start, + self.hint_addr_range.end, + self.read_records, + self.write_records, + self.first_shard_access_later_records, + self.current_shard_access_later_records, + self.total_records, + ); + for (source, count) in &self.final_mem_by_source { + tracing::info!( + "[ceno] shard_ram_light_final_mem shard_id={} source={} count={}", + self.shard_id, + source, + count + ); + } + for (rank, stat) in self.top_pcs.iter().enumerate() { + tracing::info!( + "[ceno] shard_ram_light_top_pc shard_id={} rank={} pc=0x{:08x} kind={} read={} write={} total={}", + self.shard_id, + rank + 1, + stat.pc, + stat.kind, + stat.read_records, + stat.write_records, + stat.read_records + stat.write_records, + ); + } + for (rank, stat) in self.top_memcpy_callers.iter().enumerate() { + tracing::info!( + "[ceno] shard_ram_light_memcpy_caller shard_id={} rank={} caller_pc=0x{:08x} calls={} bytes={} read={} write={} total={} old_source_loads={} current_destination_stores={} destination_access_later_words={} max_len={} sample_dst=0x{:08x} sample_src=0x{:08x}", + self.shard_id, + rank + 1, + stat.caller_pc, + stat.calls, + stat.copied_bytes, + stat.read_records, + stat.write_records, + stat.read_records + stat.write_records, + stat.old_source_loads, + stat.current_destination_stores, + stat.destination_access_later_words, + stat.max_len, + stat.sample_dst, + stat.sample_src, + ); + } + } +} + +const MEMCPY_START_PC: Addr = 0x0800_0028; +const MEMCPY_END_PC: Addr = MEMCPY_START_PC + 1036; +const REG_RA: usize = 1; +const REG_A0: usize = 10; +const REG_A1: usize = 11; +const REG_A2: usize = 12; + +#[allow(clippy::too_many_arguments)] +pub fn analyze_shard_ram_light( + program_ctx: &E2EProgramCtx, + init_mem_state: &InitMemState, + public_io_digest: [u32; 8], + max_steps: usize, + target_shard_id: Option, + top_pc_limit: usize, + #[cfg(all(feature = "aot-x86_64", target_arch = "x86_64", target_os = "linux"))] + precompiled_aot: Option>, +) -> Vec { + let raw_step_cell_extractor = Arc::clone(&program_ctx.system_config.config); + let step_cell_extractor: Arc = raw_step_cell_extractor; + let mut emul_result = emulate_program( + program_ctx.program.clone(), + max_steps, + init_mem_state, + public_io_digest, + &program_ctx.platform, + &program_ctx.multi_prover, + step_cell_extractor, + #[cfg(all(feature = "aot-x86_64", target_arch = "x86_64", target_os = "linux"))] + precompiled_aot, + ); + + assert!( + emul_result.executed_steps > 0, + "execution trace must contain at least one step" + ); + + let mut shard_ctx_builder = std::mem::take(&mut emul_result.shard_ctx_builder); + let mut step_iter = StepReplay::new( + program_ctx.platform.clone(), + program_ctx.program.clone(), + init_mem_state, + emul_result.executed_steps, + emul_result.max_step_shard, + ); + let mut reports = Vec::new(); + + while let Some((mut shard_ctx, shard_summary)) = + shard_ctx_builder.position_next_shard(&mut step_iter, |_, _| {}) + { + let shard_id = shard_ctx.shard_id; + shard_ctx.syscall_witnesses = Arc::new(step_iter.take_syscall_witnesses()); + let shard_steps = step_iter.shard_steps(); + + if let Some(target) = target_shard_id { + if shard_id < target { + continue; + } + if shard_id > target { + break; + } + } + + let mut pc_deltas: HashMap = HashMap::new(); + let mut memcpy_callers: HashMap = HashMap::new(); + let mut active_memcpy: Option = None; + let mut regs = [0u32; VM_REG_COUNT]; + for step in shard_steps { + let pc = step.pc().before.0; + if active_memcpy.is_some() && !is_memcpy_pc(pc) { + flush_memcpy_call(&mut memcpy_callers, active_memcpy.take().unwrap()); + } + if pc == MEMCPY_START_PC { + active_memcpy = Some(ActiveMemcpyCall::new( + regs[REG_RA].saturating_sub(WORD_SIZE as u32), + regs[REG_A0], + regs[REG_A1], + regs[REG_A2], + )); + } + + let before_reads = shard_ctx.read_record_count(); + let before_writes = shard_ctx.write_record_count(); + program_ctx + .system_config + .config + .collect_step_shardram(&mut shard_ctx, step) + .unwrap_or_else(|err| { + panic!( + "lightweight shardram collection failed at shard {shard_id}, pc=0x{pc:08x}: {err:?}" + ) + }); + let read_delta = shard_ctx.read_record_count().saturating_sub(before_reads); + let write_delta = shard_ctx.write_record_count().saturating_sub(before_writes); + if read_delta != 0 || write_delta != 0 { + let entry = pc_deltas.entry(pc).or_insert_with(|| PcShardRamDelta { + kind: step.insn.kind.to_string(), + ..Default::default() + }); + entry.read_records += read_delta; + entry.write_records += write_delta; + } + if let Some(call) = active_memcpy.as_mut() { + call.read_records += read_delta; + call.write_records += write_delta; + collect_memcpy_memory_evidence( + call, + step, + shard_ctx.cur_shard_cycle_range.start as Cycle, + &shard_ctx.shard_heap_addr_range, + ); + } + if let Some(rd) = step.rd() { + let reg_idx = rd.register_index() as usize; + if reg_idx < regs.len() { + regs[reg_idx] = rd.value.after; + } + } + } + if let Some(call) = active_memcpy.take() { + flush_memcpy_call(&mut memcpy_callers, call); + } + + let read_records = shard_ctx.read_record_count(); + let write_records = shard_ctx.write_record_count(); + let ( + first_shard_access_later_records, + current_shard_access_later_records, + final_mem_by_source, + ) = count_final_mem_access_later(&shard_ctx, &emul_result.final_mem_state); + + mark_memcpy_destination_access_later( + &mut memcpy_callers, + shard_ctx.cur_shard_cycle_range.end as Cycle, + &emul_result.final_mem_state, + ); + + let mut top_pcs = pc_deltas + .into_iter() + .map(|(pc, delta)| ShardRamLightPcStat { + pc, + kind: delta.kind, + read_records: delta.read_records, + write_records: delta.write_records, + }) + .collect::>(); + top_pcs.sort_by(|a, b| { + (b.read_records + b.write_records) + .cmp(&(a.read_records + a.write_records)) + .then_with(|| b.read_records.cmp(&a.read_records)) + .then_with(|| a.pc.cmp(&b.pc)) + }); + top_pcs.truncate(top_pc_limit); + let mut top_memcpy_callers = memcpy_callers + .into_iter() + .map(|(caller_pc, delta)| ShardRamLightMemcpyCallerStat { + caller_pc, + calls: delta.calls, + copied_bytes: delta.copied_bytes, + read_records: delta.read_records, + write_records: delta.write_records, + old_source_loads: delta.old_source_loads, + current_destination_stores: delta.current_destination_stores, + destination_access_later_words: delta.destination_access_later_words, + max_len: delta.max_len, + sample_dst: delta.sample_dst, + sample_src: delta.sample_src, + }) + .collect::>(); + top_memcpy_callers.sort_by(|a, b| { + (b.read_records + b.write_records) + .cmp(&(a.read_records + a.write_records)) + .then_with(|| { + b.destination_access_later_words + .cmp(&a.destination_access_later_words) + }) + .then_with(|| b.copied_bytes.cmp(&a.copied_bytes)) + .then_with(|| a.caller_pc.cmp(&b.caller_pc)) + }); + top_memcpy_callers.truncate(top_pc_limit); + + let report = ShardRamLightReport { + shard_id, + step_count: shard_summary.step_count, + cycle_range: shard_ctx.cur_shard_cycle_range.clone(), + heap_addr_range: shard_ctx.shard_heap_addr_range.clone(), + hint_addr_range: shard_ctx.shard_hint_addr_range.clone(), + read_records, + write_records, + first_shard_access_later_records, + current_shard_access_later_records, + total_records: read_records + + write_records + + first_shard_access_later_records + + current_shard_access_later_records, + final_mem_by_source, + top_pcs, + top_memcpy_callers, + }; + report.log(); + reports.push(report); + + if target_shard_id == Some(shard_id) { + break; + } + } + + reports +} + +fn is_memcpy_pc(pc: Addr) -> bool { + (MEMCPY_START_PC..MEMCPY_END_PC).contains(&pc) +} + +fn is_load_kind(kind: InsnKind) -> bool { + matches!( + kind, + InsnKind::LB | InsnKind::LH | InsnKind::LW | InsnKind::LBU | InsnKind::LHU + ) +} + +fn is_store_kind(kind: InsnKind) -> bool { + matches!(kind, InsnKind::SB | InsnKind::SH | InsnKind::SW) +} + +fn word_addr_in_byte_range(addr: WordAddr, start: Addr, len: u32) -> bool { + let byte_addr = u32::from(addr); + byte_addr >= start && byte_addr < start.saturating_add(len) +} + +fn collect_memcpy_memory_evidence( + call: &mut ActiveMemcpyCall, + step: &StepRecord, + shard_start_cycle: Cycle, + shard_heap_addr_range: &Range, +) { + let Some(memory_op) = step.memory_op() else { + return; + }; + + if is_load_kind(step.insn.kind) + && memory_op.previous_cycle < shard_start_cycle + && word_addr_in_byte_range(memory_op.addr, call.src, call.len) + { + call.old_source_loads += 1; + } + + if is_store_kind(step.insn.kind) && word_addr_in_byte_range(memory_op.addr, call.dst, call.len) + { + let byte_addr = u32::from(memory_op.addr); + if shard_heap_addr_range.contains(&byte_addr) { + call.current_destination_stores += 1; + } + call.dst_store_words.insert(memory_op.addr); + } +} + +fn flush_memcpy_call( + memcpy_callers: &mut HashMap, + call: ActiveMemcpyCall, +) { + let entry = memcpy_callers.entry(call.caller_pc).or_default(); + entry.calls += 1; + entry.copied_bytes += call.len as u64; + entry.read_records += call.read_records; + entry.write_records += call.write_records; + entry.old_source_loads += call.old_source_loads; + entry.current_destination_stores += call.current_destination_stores; + entry.max_len = entry.max_len.max(call.len); + if entry.sample_dst == 0 { + entry.sample_dst = call.dst; + entry.sample_src = call.src; + } + entry.dst_store_words.extend(call.dst_store_words); +} + +fn mark_memcpy_destination_access_later( + memcpy_callers: &mut HashMap, + shard_end_cycle: Cycle, + final_mem_state: &FinalMemState, +) { + let access_later_words = final_mem_state + .heap + .iter() + .filter(|record| record.cycle >= shard_end_cycle) + .map(|record| WordAddr::from(record.addr)) + .collect::>(); + + for delta in memcpy_callers.values_mut() { + delta.destination_access_later_words = delta + .dst_store_words + .iter() + .filter(|addr| access_later_words.contains(addr)) + .count(); + } +} + +fn count_final_mem_access_later( + shard_ctx: &ShardContext, + final_mem_state: &FinalMemState, +) -> (usize, usize, BTreeMap<&'static str, usize>) { + let addr_accessed = shard_ctx.get_addr_accessed(); + let mut by_source = BTreeMap::new(); + let first = if shard_ctx.is_first_shard() { + [ + ("reg", None, final_mem_state.reg.as_slice()), + ("mem", None, final_mem_state.mem.as_slice()), + ("stack", None, final_mem_state.stack.as_slice()), + ] + .into_iter() + .map(|(name, range, records)| { + let count = count_final_mem_source(shard_ctx, &addr_accessed, range, records); + if count != 0 { + by_source.insert(name, count); + } + count + }) + .sum() + } else { + 0 + }; + + let heap_range = shard_ctx.shard_heap_addr_range.clone(); + let hint_range = shard_ctx.shard_hint_addr_range.clone(); + let current = [ + ("heap", Some(heap_range), final_mem_state.heap.as_slice()), + ("hints", Some(hint_range), final_mem_state.hints.as_slice()), + ] + .into_iter() + .map(|(name, range, records)| { + let count = count_final_mem_source(shard_ctx, &addr_accessed, range, records); + if count != 0 { + *by_source.entry(name).or_default() += count; + } + count + }) + .sum(); + + (first, current, by_source) +} + +fn count_final_mem_source( + shard_ctx: &ShardContext, + addr_accessed: &FxHashSet, + range: Option>, + records: &[MemFinalRecord], +) -> usize { + records + .iter() + .filter(|record| { + let (waddr, addr) = match record.ram_type { + RAMType::Register => ( + Platform::register_vma(record.addr as RegIdx).into(), + record.addr, + ), + RAMType::Memory => (record.addr.into(), record.addr), + _ => unimplemented!(), + }; + if range.as_ref().is_some_and(|range| !range.contains(&addr)) { + return false; + } + !addr_accessed.contains(&waddr) && shard_ctx.after_current_shard_cycle(record.cycle) + }) + .count() +} + #[cfg(feature = "gpu")] fn assert_witgen_mem_released(shard_id: usize, baseline: u64) { use gkr_iop::gpu::gpu_prover::*; diff --git a/ceno_zkvm/src/instructions/riscv/ecall.rs b/ceno_zkvm/src/instructions/riscv/ecall.rs index ae356be10..e38afbe2a 100644 --- a/ceno_zkvm/src/instructions/riscv/ecall.rs +++ b/ceno_zkvm/src/instructions/riscv/ecall.rs @@ -3,6 +3,7 @@ mod fptower_fp2_add; mod fptower_fp2_mul; mod halt; pub(crate) mod keccak; +mod keccak_xorin; mod pubio_commit; mod sha_extend; mod uint256; @@ -14,6 +15,7 @@ pub use fptower_fp::{FpAddInstruction, FpMulInstruction}; pub use fptower_fp2_add::Fp2AddInstruction; pub use fptower_fp2_mul::Fp2MulInstruction; pub use keccak::{KeccakCoreInstruction, KeccakEcallInstruction, KeccakInstruction}; +pub use keccak_xorin::KeccakXorinInstruction; pub use pubio_commit::PubIoCommitInstruction; pub use sha_extend::ShaExtendInstruction; pub use uint256::{Secp256k1InvInstruction, Secp256r1InvInstruction, Uint256MulInstruction}; diff --git a/ceno_zkvm/src/instructions/riscv/ecall/keccak.rs b/ceno_zkvm/src/instructions/riscv/ecall/keccak.rs index 22091f948..1694d0c21 100644 --- a/ceno_zkvm/src/instructions/riscv/ecall/keccak.rs +++ b/ceno_zkvm/src/instructions/riscv/ecall/keccak.rs @@ -301,6 +301,50 @@ impl Instruction for KeccakEcallInstruction { lk_multiplicity.into_finalize_result(), )) } + + fn collect_lk_and_shardram( + _config: &Self::InstructionConfig, + shard_ctx: &mut ShardContext, + _lk_multiplicity: &mut LkMultiplicity, + step: &StepRecord, + ) -> Result<(), ZKVMError> { + let syscall_witnesses = shard_ctx.syscall_witnesses.clone(); + let ops = step + .syscall(&syscall_witnesses) + .expect("keccak syscall step"); + + shard_ctx.send( + RAMType::Register, + Platform::register_vma(Platform::reg_ecall()).into(), + Platform::reg_ecall() as u64, + step.cycle() + ceno_emul::FullTracer::SUBCYCLE_RS1, + step.rs1().unwrap().previous_cycle, + KECCAK_PERMUTE, + None, + ); + shard_ctx.send( + RAMType::Register, + ops.reg_ops[0].addr, + Platform::reg_arg0() as u64, + step.cycle() + ceno_emul::FullTracer::SUBCYCLE_RD, + ops.reg_ops[0].previous_cycle, + ops.reg_ops[0].value.after, + None, + ); + for op in &ops.mem_ops { + shard_ctx.send( + RAMType::Memory, + op.addr, + op.addr.baddr().0 as u64, + step.cycle() + ceno_emul::FullTracer::SUBCYCLE_MEM, + op.previous_cycle, + op.value.after, + Some(op.value.before), + ); + } + + Ok(()) + } } impl Instruction for KeccakCoreInstruction { diff --git a/ceno_zkvm/src/instructions/riscv/ecall/keccak_xorin.rs b/ceno_zkvm/src/instructions/riscv/ecall/keccak_xorin.rs new file mode 100644 index 000000000..879352402 --- /dev/null +++ b/ceno_zkvm/src/instructions/riscv/ecall/keccak_xorin.rs @@ -0,0 +1,352 @@ +use std::{array, marker::PhantomData}; + +use ceno_emul::{ + ByteAddr, Change, FullTracer as Tracer, InsnKind, KECCAK_RATE_WORDS, KECCAK_XORIN, Platform, + StepRecord, WORD_SIZE, WriteOp, +}; +use ff_ext::{ExtensionField, FieldInto}; +use itertools::Itertools; +use multilinear_extensions::{Expression, ToExpr, WitIn}; +use p3::field::PrimeCharacteristicRing; +use witness::set_val; + +use crate::{ + chip_handler::{MemoryExpr, general::InstFetch}, + circuit_builder::CircuitBuilder, + e2e::ShardContext, + error::ZKVMError, + instructions::{ + Instruction, + riscv::{ + constants::{LIMB_BITS, LIMB_MASK, MEM_BITS, UInt}, + ecall_base::OpFixedRS, + insn_base::{MemAddr, StateInOut, WriteMEM}, + }, + }, + structs::{ProgramParams, RAMType}, + tables::InsnRecord, + uint::Value, + witness::LkMultiplicity, +}; + +#[derive(Debug)] +pub struct KeccakXorinConfig { + vm_state: StateInOut, + ecall_id: OpFixedRS, + state_ptr: (OpFixedRS, MemAddr), + block_ptr: (OpFixedRS, MemAddr), + block_words: [MemoryExpr; KECCAK_RATE_WORDS], + state_words: [MemoryExpr; KECCAK_RATE_WORDS], + block_bytes: [[WitIn; WORD_SIZE]; KECCAK_RATE_WORDS], + state_bytes: [[WitIn; WORD_SIZE]; KECCAK_RATE_WORDS], + output_bytes: [[WitIn; WORD_SIZE]; KECCAK_RATE_WORDS], + mem_rw: Vec, +} + +pub struct KeccakXorinInstruction(PhantomData); + +fn new_memory_expr(cb: &mut CircuitBuilder, name: &str) -> MemoryExpr { + array::from_fn(|i| cb.create_witin(|| format!("{name}_{i}")).expr()) +} + +fn create_bytes(cb: &mut CircuitBuilder, name: &str) -> [WitIn; WORD_SIZE] { + array::from_fn(|i| cb.create_witin(|| format!("{name}_{i}"))) +} + +fn constrain_word_bytes( + cb: &mut CircuitBuilder, + name: &str, + word: &MemoryExpr, + bytes: &[WitIn; WORD_SIZE], +) -> Result<(), ZKVMError> { + let byte_base = E::BaseField::from_u32(1 << 8).expr(); + cb.require_zero( + || format!("{name}_low_limb"), + word[0].clone() - bytes[0].expr() - byte_base.clone() * bytes[1].expr(), + )?; + cb.require_zero( + || format!("{name}_high_limb"), + word[1].clone() - bytes[2].expr() - byte_base * bytes[3].expr(), + )?; + Ok(()) +} + +fn bytes_as_memory_expr(bytes: &[WitIn; WORD_SIZE]) -> MemoryExpr { + let byte_base = E::BaseField::from_u32(1 << 8).expr(); + [ + bytes[0].expr() + byte_base.clone() * bytes[1].expr(), + bytes[2].expr() + byte_base * bytes[3].expr(), + ] +} + +fn assign_memory_expr( + instance: &mut [E::BaseField], + expr: &MemoryExpr, + value: u32, +) { + let value = Value::new_unchecked(value); + let limbs = value.as_u16_limbs(); + for (limb_expr, limb) in expr.iter().zip_eq(limbs.iter()) { + let Expression::WitIn(wit) = limb_expr else { + panic!("Keccak XOR-in memory limbs must be witness columns"); + }; + instance[*wit as usize] = E::BaseField::from_u64(*limb as u64); + } +} + +impl Instruction for KeccakXorinInstruction { + type InstructionConfig = KeccakXorinConfig; + type InsnType = InsnKind; + + fn inst_kinds() -> &'static [Self::InsnType] { + &[InsnKind::ECALL] + } + + fn name() -> String { + "KeccakXorin".to_string() + } + + fn construct_circuit( + cb: &mut CircuitBuilder, + _param: &ProgramParams, + ) -> Result { + let vm_state = StateInOut::construct_circuit(cb, false)?; + let ecall_id = OpFixedRS::<_, { Platform::reg_ecall() }, false>::construct_circuit( + cb, + UInt::from_const_unchecked(vec![ + KECCAK_XORIN & LIMB_MASK, + (KECCAK_XORIN >> LIMB_BITS) & LIMB_MASK, + ]) + .register_expr(), + vm_state.ts, + )?; + + let state_ptr_value = MemAddr::construct_with_max_bits(cb, 2, MEM_BITS)?; + let state_ptr = OpFixedRS::<_, { Platform::reg_arg0() }, true>::construct_circuit( + cb, + state_ptr_value.uint_unaligned().register_expr(), + vm_state.ts, + )?; + let block_ptr_value = MemAddr::construct_with_max_bits(cb, 2, MEM_BITS)?; + let block_ptr = OpFixedRS::<_, { Platform::reg_arg1() }, true>::construct_circuit( + cb, + block_ptr_value.uint_unaligned().register_expr(), + vm_state.ts, + )?; + + cb.lk_fetch(&InsnRecord::new( + vm_state.pc.expr(), + InsnKind::ECALL.into(), + None, + 0.into(), + 0.into(), + 0.into(), + #[cfg(feature = "u16limb_circuit")] + 0.into(), + ))?; + + let block_words = array::from_fn(|i| new_memory_expr(cb, &format!("block_word_{i}"))); + let state_words = array::from_fn(|i| new_memory_expr(cb, &format!("state_word_{i}"))); + let block_bytes = array::from_fn(|i| create_bytes(cb, &format!("block_byte_{i}"))); + let state_bytes = array::from_fn(|i| create_bytes(cb, &format!("state_byte_{i}"))); + let output_bytes = array::from_fn(|i| create_bytes(cb, &format!("output_byte_{i}"))); + + for word in 0..KECCAK_RATE_WORDS { + constrain_word_bytes( + cb, + &format!("block_{word}"), + &block_words[word], + &block_bytes[word], + )?; + constrain_word_bytes( + cb, + &format!("state_{word}"), + &state_words[word], + &state_bytes[word], + )?; + for byte in 0..WORD_SIZE { + cb.lookup_xor_byte( + state_bytes[word][byte].expr(), + block_bytes[word][byte].expr(), + output_bytes[word][byte].expr(), + )?; + } + } + + let mut mem_rw = block_words + .iter() + .enumerate() + .map(|(i, word)| { + WriteMEM::construct_circuit( + cb, + block_ptr.prev_value.as_ref().unwrap().value() + + E::BaseField::from_u32(ByteAddr::from((i * WORD_SIZE) as u32).0).expr(), + word.clone(), + word.clone(), + vm_state.ts, + ) + }) + .collect::, _>>()?; + mem_rw.extend( + state_words + .iter() + .zip(output_bytes.iter()) + .enumerate() + .map(|(i, (before, after))| { + WriteMEM::construct_circuit( + cb, + state_ptr.prev_value.as_ref().unwrap().value() + + E::BaseField::from_u32(ByteAddr::from((i * WORD_SIZE) as u32).0) + .expr(), + before.clone(), + bytes_as_memory_expr(after), + vm_state.ts, + ) + }) + .collect::, _>>()?, + ); + + Ok(KeccakXorinConfig { + vm_state, + ecall_id, + state_ptr: (state_ptr, state_ptr_value), + block_ptr: (block_ptr, block_ptr_value), + block_words, + state_words, + block_bytes, + state_bytes, + output_bytes, + mem_rw, + }) + } + + fn assign_instance( + config: &Self::InstructionConfig, + shard_ctx: &mut ShardContext, + instance: &mut [E::BaseField], + lk_multiplicity: &mut LkMultiplicity, + step: &StepRecord, + ) -> Result<(), ZKVMError> { + let syscall_witnesses = shard_ctx.syscall_witnesses.clone(); + let ops = step + .syscall(&syscall_witnesses) + .expect("Keccak XOR-in syscall step"); + assert_eq!(ops.reg_ops.len(), 2); + assert_eq!(ops.mem_ops.len(), KECCAK_RATE_WORDS * 2); + + config.vm_state.assign_instance(instance, shard_ctx, step)?; + config.ecall_id.assign_op( + instance, + shard_ctx, + lk_multiplicity, + step.cycle(), + &WriteOp::new_register_op( + Platform::reg_ecall(), + Change::new(KECCAK_XORIN, KECCAK_XORIN), + step.rs1().unwrap().previous_cycle, + ), + )?; + + config.state_ptr.1.assign_instance( + instance, + lk_multiplicity, + ops.reg_ops[0].value.after, + )?; + config.state_ptr.0.assign_op( + instance, + shard_ctx, + lk_multiplicity, + step.cycle(), + &ops.reg_ops[0], + )?; + config.block_ptr.1.assign_instance( + instance, + lk_multiplicity, + ops.reg_ops[1].value.after, + )?; + config.block_ptr.0.assign_op( + instance, + shard_ctx, + lk_multiplicity, + step.cycle(), + &ops.reg_ops[1], + )?; + + for word in 0..KECCAK_RATE_WORDS { + let block = ops.mem_ops[word].value.before; + let state = ops.mem_ops[KECCAK_RATE_WORDS + word].value.before; + assign_memory_expr::(instance, &config.block_words[word], block); + assign_memory_expr::(instance, &config.state_words[word], state); + + for byte in 0..WORD_SIZE { + let block_byte = block.to_le_bytes()[byte]; + let state_byte = state.to_le_bytes()[byte]; + let output_byte = block_byte ^ state_byte; + set_val!(instance, config.block_bytes[word][byte], block_byte as u64); + set_val!(instance, config.state_bytes[word][byte], state_byte as u64); + set_val!( + instance, + config.output_bytes[word][byte], + output_byte as u64 + ); + lk_multiplicity.lookup_xor_byte(state_byte as u64, block_byte as u64); + } + } + + for (writer, op) in config.mem_rw.iter().zip_eq(&ops.mem_ops) { + writer.assign_op(instance, shard_ctx, lk_multiplicity, step.cycle(), op)?; + } + lk_multiplicity.fetch(step.pc().before.0); + Ok(()) + } + + fn collect_lk_and_shardram( + _config: &Self::InstructionConfig, + shard_ctx: &mut ShardContext, + _lk_multiplicity: &mut LkMultiplicity, + step: &StepRecord, + ) -> Result<(), ZKVMError> { + let syscall_witnesses = shard_ctx.syscall_witnesses.clone(); + let ops = step + .syscall(&syscall_witnesses) + .expect("Keccak XOR-in syscall step"); + assert_eq!(ops.reg_ops.len(), 2); + assert_eq!(ops.mem_ops.len(), KECCAK_RATE_WORDS * 2); + + shard_ctx.send( + RAMType::Register, + Platform::register_vma(Platform::reg_ecall()).into(), + Platform::reg_ecall() as u64, + step.cycle() + Tracer::SUBCYCLE_RS1, + step.rs1().unwrap().previous_cycle, + KECCAK_XORIN, + None, + ); + for (reg_id, op) in [Platform::reg_arg0(), Platform::reg_arg1()] + .into_iter() + .zip_eq(&ops.reg_ops) + { + shard_ctx.send( + RAMType::Register, + op.addr, + reg_id as u64, + step.cycle() + Tracer::SUBCYCLE_RD, + op.previous_cycle, + op.value.after, + None, + ); + } + for op in &ops.mem_ops { + shard_ctx.send( + RAMType::Memory, + op.addr, + op.addr.baddr().0 as u64, + step.cycle() + Tracer::SUBCYCLE_MEM, + op.previous_cycle, + op.value.after, + Some(op.value.before), + ); + } + Ok(()) + } +} diff --git a/ceno_zkvm/src/instructions/riscv/rv32im.rs b/ceno_zkvm/src/instructions/riscv/rv32im.rs index 8c46241d7..4935151d3 100644 --- a/ceno_zkvm/src/instructions/riscv/rv32im.rs +++ b/ceno_zkvm/src/instructions/riscv/rv32im.rs @@ -21,9 +21,9 @@ use crate::{ div::{DivInstruction, DivuInstruction, RemInstruction, RemuInstruction}, ecall::{ Fp2AddInstruction, Fp2MulInstruction, FpAddInstruction, FpMulInstruction, - KeccakCoreInstruction, KeccakEcallInstruction, PubIoCommitInstruction, - Secp256k1InvInstruction, Secp256r1InvInstruction, ShaExtendInstruction, - Uint256MulInstruction, WeierstrassAddAssignInstruction, + KeccakCoreInstruction, KeccakEcallInstruction, KeccakXorinInstruction, + PubIoCommitInstruction, Secp256k1InvInstruction, Secp256r1InvInstruction, + ShaExtendInstruction, Uint256MulInstruction, WeierstrassAddAssignInstruction, WeierstrassDecompressInstruction, WeierstrassDoubleAssignInstruction, }, logic::{AndInstruction, OrInstruction, XorInstruction}, @@ -37,7 +37,9 @@ use crate::{ }, scheme::constants::DYNAMIC_RANGE_MAX_BITS, state::GlobalState, - structs::{ComposedConstrainSystem, ZKVMConstraintSystem, ZKVMFixedTraces, ZKVMWitnesses}, + structs::{ + ComposedConstrainSystem, RAMType, ZKVMConstraintSystem, ZKVMFixedTraces, ZKVMWitnesses, + }, tables::{ AndTableCircuit, DoubleU8TableCircuit, DynamicRangeTableCircuit, LtuTableCircuit, OrTableCircuit, TableCircuit, XorTableCircuit, @@ -45,12 +47,12 @@ use crate::{ }; use ceno_emul::{ Bn254AddSpec, Bn254DoubleSpec, Bn254Fp2AddSpec, Bn254Fp2MulSpec, Bn254FpAddSpec, - Bn254FpMulSpec, ChipCostSpec, + Bn254FpMulSpec, ChipCostSpec, FullTracer as Tracer, InsnKind::{self, *}, - KeccakSpec, LogPcCycleSpec, Platform, PubIoCommitSpec, STATE_CONTINUATION, Secp256k1AddSpec, - Secp256k1DecompressSpec, Secp256k1DoubleSpec, Secp256k1ScalarInvertSpec, Secp256r1AddSpec, - Secp256r1DoubleSpec, Secp256r1ScalarInvertSpec, Sha256ExtendSpec, ShardCostModel, - StepCellExtractor, StepIndex, StepRecord, SyscallSpec, Uint256MulSpec, Word, + KeccakSpec, KeccakXorinSpec, LogPcCycleSpec, Platform, PubIoCommitSpec, STATE_CONTINUATION, + Secp256k1AddSpec, Secp256k1DecompressSpec, Secp256k1DoubleSpec, Secp256k1ScalarInvertSpec, + Secp256r1AddSpec, Secp256r1DoubleSpec, Secp256r1ScalarInvertSpec, Sha256ExtendSpec, + ShardCostModel, StepCellExtractor, StepIndex, StepRecord, SyscallSpec, Uint256MulSpec, Word, }; use dummy::LargeEcallDummy; use ff_ext::ExtensionField; @@ -185,6 +187,8 @@ pub struct Rv32imConfig { as Instruction>::InstructionConfig, pub keccak_core_config: as Instruction>::InstructionConfig, + pub keccak_xorin_config: + as Instruction>::InstructionConfig, pub sha_extend_config: as Instruction>::InstructionConfig, pub bn254_add_config: > as Instruction>::InstructionConfig, @@ -474,6 +478,8 @@ impl Rv32imConfig { chip_specs.push(chip_cost_spec(circuit_cs)); } ecall_name_to_chips.insert(>::name(), keccak_chips); + let keccak_xorin_config = + register_ecall_circuit!(KeccakXorinInstruction, ecall_cells_map); let bn254_add_config = register_ecall_circuit!(WeierstrassAddAssignInstruction>, ecall_cells_map); let sha_extend_config = register_ecall_circuit!(ShaExtendInstruction, ecall_cells_map); let bn254_double_config = register_ecall_circuit!(WeierstrassDoubleAssignInstruction>, ecall_cells_map); @@ -507,6 +513,7 @@ impl Rv32imConfig { map_ecall(ECALL_PUB_IO_COMMIT, PubIoCommitInstruction::::name()); map_ecall(STATE_CONTINUATION, GlobalState::::name()); map_ecall(KeccakSpec::CODE, KeccakCoreInstruction::::name()); + map_ecall(KeccakXorinSpec::CODE, KeccakXorinInstruction::::name()); map_ecall( Bn254AddSpec::CODE, WeierstrassAddAssignInstruction::>::name(), @@ -638,6 +645,7 @@ impl Rv32imConfig { state_continuation_config, keccak_ecall_config, keccak_core_config, + keccak_xorin_config, sha_extend_config, bn254_add_config, bn254_double_config, @@ -736,6 +744,7 @@ impl Rv32imConfig { fixed.register_opcode_circuit::>(cs, &self.state_continuation_config); fixed.register_opcode_circuit::>(cs, &self.keccak_ecall_config); fixed.register_opcode_circuit::>(cs, &self.keccak_core_config); + fixed.register_opcode_circuit::>(cs, &self.keccak_xorin_config); fixed.register_opcode_circuit::>(cs, &self.sha_extend_config); fixed.register_opcode_circuit::>>( cs, @@ -826,6 +835,7 @@ impl Rv32imConfig { log_ecall!("PUB_IO_COMMIT", ECALL_PUB_IO_COMMIT); log_ecall!("STATE_CONTINUATION", STATE_CONTINUATION); log_ecall!("KECCAK", KeccakSpec::CODE); + log_ecall!("KECCAK_XORIN", KeccakXorinSpec::CODE); log_ecall!("bn254_add_records", Bn254AddSpec::CODE); log_ecall!("bn254_double_records", Bn254DoubleSpec::CODE); log_ecall!("bn254_fp_add_records", Bn254FpAddSpec::CODE); @@ -964,6 +974,11 @@ impl Rv32imConfig { keccak_core_config, KeccakSpec::CODE ); + assign_ecall!( + KeccakXorinInstruction, + keccak_xorin_config, + KeccakXorinSpec::CODE + ); assign_ecall!( WeierstrassAddAssignInstruction>, bn254_add_config, @@ -1065,6 +1080,213 @@ impl Rv32imConfig { Ok(()) } + + pub fn collect_step_shardram( + &self, + shard_ctx: &mut ShardContext, + step: &StepRecord, + ) -> Result<(), ZKVMError> { + let mut lk_multiplicity = crate::witness::LkMultiplicity::default(); + + macro_rules! collect { + ($instruction:ty, $config:ident) => {{ + <$instruction>::collect_lk_and_shardram( + &self.$config, + shard_ctx, + &mut lk_multiplicity, + step, + )?; + }}; + } + + macro_rules! collect_ecall { + ($instruction:ty, $config:ident) => {{ + if let Err(err) = <$instruction>::collect_lk_and_shardram( + &self.$config, + shard_ctx, + &mut lk_multiplicity, + step, + ) { + if is_missing_lightweight_collector(&err) { + collect_generic_ecall_shardram(shard_ctx, step); + } else { + return Err(err); + } + } + }}; + } + + match step.insn.kind { + ADD => collect!(AddInstruction, add_config), + SUB => collect!(SubInstruction, sub_config), + AND => collect!(AndInstruction, and_config), + OR => collect!(OrInstruction, or_config), + XOR => collect!(XorInstruction, xor_config), + SLL => collect!(SllInstruction, sll_config), + SRL => collect!(SrlInstruction, srl_config), + SRA => collect!(SraInstruction, sra_config), + SLT => collect!(SltInstruction, slt_config), + SLTU => collect!(SltuInstruction, sltu_config), + MUL => collect!(MulInstruction, mul_config), + MULH => collect!(MulhInstruction, mulh_config), + MULHSU => collect!(MulhsuInstruction, mulhsu_config), + MULHU => collect!(MulhuInstruction, mulhu_config), + DIVU => collect!(DivuInstruction, divu_config), + REMU => collect!(RemuInstruction, remu_config), + DIV => collect!(DivInstruction, div_config), + REM => collect!(RemInstruction, rem_config), + ADDI => collect!(AddiInstruction, addi_config), + ANDI => collect!(AndiInstruction, andi_config), + ORI => collect!(OriInstruction, ori_config), + XORI => collect!(XoriInstruction, xori_config), + SLLI => collect!(SlliInstruction, slli_config), + SRLI => collect!(SrliInstruction, srli_config), + SRAI => collect!(SraiInstruction, srai_config), + SLTI => collect!(SltiInstruction, slti_config), + SLTIU => collect!(SltiuInstruction, sltiu_config), + #[cfg(feature = "u16limb_circuit")] + LUI => collect!(LuiInstruction, lui_config), + #[cfg(feature = "u16limb_circuit")] + AUIPC => collect!(AuipcInstruction, auipc_config), + BEQ => collect!(BeqInstruction, beq_config), + BNE => collect!(BneInstruction, bne_config), + BLT => collect!(BltInstruction, blt_config), + BLTU => collect!(BltuInstruction, bltu_config), + BGE => collect!(BgeInstruction, bge_config), + BGEU => collect!(BgeuInstruction, bgeu_config), + JAL => collect!(JalInstruction, jal_config), + JALR => collect!(JalrInstruction, jalr_config), + LW => collect!(LwInstruction, lw_config), + LB => collect!(LbInstruction, lb_config), + LBU => collect!(LbuInstruction, lbu_config), + LH => collect!(LhInstruction, lh_config), + LHU => collect!(LhuInstruction, lhu_config), + SW => collect!(SwInstruction, sw_config), + SH => collect!(ShInstruction, sh_config), + SB => collect!(SbInstruction, sb_config), + ECALL => { + let code = step + .rs1() + .expect("ecall requires rs1 to determine syscall code") + .value; + match code { + ECALL_HALT => collect_ecall!(HaltInstruction, halt_config), + ECALL_PUB_IO_COMMIT => { + collect_ecall!(PubIoCommitInstruction, pubio_commit_config) + } + STATE_CONTINUATION => collect_ecall!(GlobalState, state_continuation_config), + KeccakSpec::CODE => { + collect_ecall!(KeccakEcallInstruction, keccak_ecall_config); + } + KeccakXorinSpec::CODE => { + collect_ecall!(KeccakXorinInstruction, keccak_xorin_config) + } + Bn254AddSpec::CODE => collect_ecall!( + WeierstrassAddAssignInstruction>, + bn254_add_config + ), + Bn254DoubleSpec::CODE => collect_ecall!( + WeierstrassDoubleAssignInstruction>, + bn254_double_config + ), + Bn254FpAddSpec::CODE => { + collect_ecall!(FpAddInstruction, bn254_fp_add_config) + } + Bn254FpMulSpec::CODE => { + collect_ecall!(FpMulInstruction, bn254_fp_mul_config) + } + Bn254Fp2AddSpec::CODE => { + collect_ecall!(Fp2AddInstruction, bn254_fp2_add_config) + } + Bn254Fp2MulSpec::CODE => { + collect_ecall!(Fp2MulInstruction, bn254_fp2_mul_config) + } + Secp256k1AddSpec::CODE => collect_ecall!( + WeierstrassAddAssignInstruction>, + secp256k1_add_config + ), + Secp256k1DoubleSpec::CODE => collect_ecall!( + WeierstrassDoubleAssignInstruction>, + secp256k1_double_config + ), + Secp256k1ScalarInvertSpec::CODE => { + collect_ecall!(Secp256k1InvInstruction, secp256k1_scalar_invert) + } + Secp256k1DecompressSpec::CODE => collect_ecall!( + WeierstrassDecompressInstruction>, + secp256k1_decompress_config + ), + Secp256r1AddSpec::CODE => collect_ecall!( + WeierstrassAddAssignInstruction>, + secp256r1_add_config + ), + Secp256r1DoubleSpec::CODE => collect_ecall!( + WeierstrassDoubleAssignInstruction>, + secp256r1_double_config + ), + Secp256r1ScalarInvertSpec::CODE => { + collect_ecall!(Secp256r1InvInstruction, secp256r1_scalar_invert) + } + Uint256MulSpec::CODE => { + collect_ecall!(Uint256MulInstruction, uint256_mul_config) + } + Sha256ExtendSpec::CODE => { + collect_ecall!(ShaExtendInstruction, sha_extend_config) + } + _ => collect_generic_ecall_shardram(shard_ctx, step), + } + } + _ => {} + } + + Ok(()) + } +} + +fn is_missing_lightweight_collector(err: &ZKVMError) -> bool { + matches!(err, ZKVMError::InvalidWitness(message) if message.contains("does not implement")) +} + +fn collect_generic_ecall_shardram(shard_ctx: &mut ShardContext, step: &StepRecord) { + let Some(rs1) = step.rs1() else { + return; + }; + shard_ctx.send( + RAMType::Register, + Platform::register_vma(Platform::reg_ecall()).into(), + Platform::reg_ecall() as u64, + step.cycle() + Tracer::SUBCYCLE_RS1, + rs1.previous_cycle, + rs1.value, + None, + ); + + let syscall_witnesses = shard_ctx.syscall_witnesses.clone(); + let Some(syscall) = step.syscall(&syscall_witnesses) else { + return; + }; + for op in &syscall.reg_ops { + shard_ctx.send( + RAMType::Register, + op.addr, + op.register_index() as u64, + step.cycle() + Tracer::SUBCYCLE_RD, + op.previous_cycle, + op.value.after, + None, + ); + } + for op in &syscall.mem_ops { + shard_ctx.send( + RAMType::Memory, + op.addr, + op.addr.baddr().0 as u64, + step.cycle() + Tracer::SUBCYCLE_MEM, + op.previous_cycle, + op.value.after, + Some(op.value.before), + ); + } } pub struct InstructionDispatchCtx { @@ -1258,6 +1480,10 @@ impl Rv32imConfig { .ecall_cells_map .get(&KeccakCoreInstruction::::name()) .expect("unable to find name"), + KeccakXorinSpec::CODE => *self + .ecall_cells_map + .get(&KeccakXorinInstruction::::name()) + .expect("unable to find name"), Bn254AddSpec::CODE => *self .ecall_cells_map .get(&WeierstrassAddAssignInstruction::>::name()) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 577c01a75..d369b6af2 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/scroll-tech/ceno" version = "0.1.0" [dependencies] -alloy-consensus = { version = "1.0", features = ["crypto-backend"] } -alloy-primitives = { version = "1.3", features = ["native-keccak"] } +alloy-consensus = { version = "=2.0.5", features = ["crypto-backend"] } +alloy-primitives = { version = "=1.6.0", features = ["native-keccak"] } ceno_crypto = { path = "../guest_libs/crypto" } ceno_crypto_primitives.workspace = true ceno_keccak = { path = "../guest_libs/keccak" } diff --git a/examples/examples/bn254_precompile.rs b/examples/examples/bn254_precompile.rs index 350411d09..e386598dd 100644 --- a/examples/examples/bn254_precompile.rs +++ b/examples/examples/bn254_precompile.rs @@ -6,7 +6,7 @@ extern crate ceno_rt; use alloy_primitives::hex; use ceno_crypto::ceno_crypto; use revm_precompile::{ - PrecompileError, + PrecompileHalt, bn254::{add::*, mul::*, pair::*, *}, }; @@ -70,7 +70,7 @@ fn test_bn254_add() { let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499); - assert!(matches!(res, Err(PrecompileError::OutOfGas))); + assert!(matches!(res, Err(PrecompileHalt::OutOfGas))); // No input test let input = [0u8; 0]; @@ -97,7 +97,7 @@ fn test_bn254_add() { let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500); assert!(matches!( res, - Err(PrecompileError::Bn254AffineGFailedToCreate) + Err(PrecompileHalt::Bn254AffineGFailedToCreate) )); } @@ -129,7 +129,7 @@ fn test_bn254_mul() { .unwrap(); let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999); - assert!(matches!(res, Err(PrecompileError::OutOfGas))); + assert!(matches!(res, Err(PrecompileHalt::OutOfGas))); // Zero multiplication test let input = hex::decode( @@ -173,7 +173,7 @@ fn test_bn254_mul() { let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000); assert!(matches!( res, - Err(PrecompileError::Bn254AffineGFailedToCreate) + Err(PrecompileHalt::Bn254AffineGFailedToCreate) )); } @@ -230,7 +230,7 @@ fn test_bn254_pair() { BYZANTIUM_PAIR_BASE, 259_999, ); - assert!(matches!(res, Err(PrecompileError::OutOfGas))); + assert!(matches!(res, Err(PrecompileHalt::OutOfGas))); // No input test let input = [0u8; 0]; @@ -266,7 +266,7 @@ fn test_bn254_pair() { ); assert!(matches!( res, - Err(PrecompileError::Bn254AffineGFailedToCreate) + Err(PrecompileHalt::Bn254AffineGFailedToCreate) )); // Invalid input length @@ -285,7 +285,7 @@ fn test_bn254_pair() { BYZANTIUM_PAIR_BASE, 260_000, ); - assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); + assert!(matches!(res, Err(PrecompileHalt::Bn254PairLength))); // Test with point at infinity - should return true (identity element) // G1 point at infinity (0,0) followed by a valid G2 point diff --git a/examples/examples/ceno_rt_memcpy.rs b/examples/examples/ceno_rt_memcpy.rs new file mode 100644 index 000000000..2ea4d8ee4 --- /dev/null +++ b/examples/examples/ceno_rt_memcpy.rs @@ -0,0 +1,52 @@ +extern crate ceno_rt; + +const BUFFER_LEN: usize = 640; +const GUARD: u8 = 0xa5; + +unsafe extern "C" { + fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8; +} + +fn main() { + const LENGTHS: &[usize] = &[ + 0, 1, 2, 3, 4, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 511, + ]; + + for src_align in 0..4 { + for dst_align in 0..4 { + for &len in LENGTHS { + check_copy(src_align, dst_align, len); + } + } + } +} + +#[inline(never)] +fn check_copy(src_align: usize, dst_align: usize, len: usize) { + let src_start = 16 + src_align; + let dst_start = 16 + dst_align; + let mut src = [0u8; BUFFER_LEN]; + let mut dst = [GUARD; BUFFER_LEN]; + + for (i, byte) in src.iter_mut().enumerate() { + *byte = (i as u8).wrapping_mul(73).wrapping_add(19); + } + let original_src = src; + + let returned = unsafe { + memcpy( + dst.as_mut_ptr().add(dst_start), + src.as_ptr().add(src_start), + len, + ) + }; + + assert_eq!(returned, unsafe { dst.as_mut_ptr().add(dst_start) }); + assert_eq!(src, original_src); + assert!(dst[..dst_start].iter().all(|&byte| byte == GUARD)); + assert_eq!( + &dst[dst_start..dst_start + len], + &src[src_start..src_start + len] + ); + assert!(dst[dst_start + len..].iter().all(|&byte| byte == GUARD)); +} diff --git a/examples/examples/keccak_xorin.rs b/examples/examples/keccak_xorin.rs new file mode 100644 index 000000000..53b958a8f --- /dev/null +++ b/examples/examples/keccak_xorin.rs @@ -0,0 +1,90 @@ +extern crate ceno_rt; + +use ceno_keccak::{Hasher, Keccak}; + +const LENGTHS: [usize; 12] = [0, 1, 4, 31, 32, 64, 135, 136, 137, 271, 272, 409]; +const DIGESTS: [[u8; 32]; 12] = [ + [ + 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, + 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112, + ], + [ + 96, 129, 24, 87, 221, 86, 104, 137, 255, 98, 85, 39, 125, 130, 82, 111, 45, 155, 59, 188, + 185, 96, 118, 190, 34, 165, 134, 7, 101, 172, 61, 6, + ], + [ + 59, 44, 196, 88, 156, 229, 243, 74, 58, 217, 253, 106, 106, 76, 156, 24, 43, 147, 31, 46, + 23, 52, 194, 226, 149, 207, 176, 24, 201, 76, 84, 1, + ], + [ + 208, 97, 171, 167, 217, 216, 124, 220, 11, 138, 142, 133, 154, 199, 76, 221, 166, 176, 234, + 102, 126, 247, 234, 212, 48, 195, 211, 12, 227, 138, 69, 152, + ], + [ + 208, 100, 201, 114, 234, 124, 189, 159, 18, 55, 187, 217, 34, 253, 95, 8, 202, 87, 137, 92, + 19, 188, 158, 162, 185, 25, 19, 247, 9, 152, 9, 161, + ], + [ + 82, 193, 244, 97, 104, 98, 249, 213, 1, 30, 214, 162, 167, 125, 137, 162, 16, 46, 81, 238, + 125, 178, 219, 4, 91, 181, 251, 38, 127, 186, 152, 209, + ], + [ + 155, 109, 235, 35, 135, 200, 103, 131, 134, 34, 22, 208, 32, 80, 81, 186, 125, 65, 250, 79, + 167, 10, 48, 226, 171, 254, 24, 202, 148, 210, 43, 34, + ], + [ + 184, 113, 124, 110, 118, 5, 202, 59, 90, 10, 148, 161, 71, 18, 118, 121, 119, 138, 35, 164, + 50, 78, 83, 185, 16, 38, 54, 115, 208, 191, 181, 92, + ], + [ + 226, 217, 244, 9, 166, 213, 117, 225, 69, 127, 157, 63, 116, 54, 8, 20, 133, 213, 121, 75, + 248, 77, 177, 121, 86, 110, 234, 7, 168, 38, 110, 141, + ], + [ + 8, 44, 24, 144, 171, 4, 210, 37, 113, 44, 24, 113, 69, 74, 238, 84, 18, 58, 173, 230, 44, + 165, 147, 126, 91, 96, 153, 33, 236, 33, 147, 11, + ], + [ + 121, 202, 207, 213, 45, 180, 39, 206, 123, 154, 119, 25, 132, 161, 51, 135, 166, 227, 16, + 117, 188, 196, 113, 106, 93, 237, 223, 246, 135, 92, 78, 105, + ], + [ + 136, 184, 12, 42, 149, 60, 1, 134, 6, 19, 73, 119, 155, 121, 127, 245, 144, 18, 64, 193, + 119, 233, 111, 167, 105, 181, 116, 228, 113, 55, 101, 6, + ], +]; + +fn main() { + for (case, &len) in LENGTHS.iter().enumerate() { + let input = (0..len) + .map(|i| (i as u8).wrapping_mul(37).wrapping_add(11)) + .collect::>(); + + assert_eq!(digest_one_shot(&input), DIGESTS[case]); + assert_eq!(digest_fragmented(&input), DIGESTS[case]); + } +} + +fn digest_one_shot(input: &[u8]) -> [u8; 32] { + let mut hasher = Keccak::v256(); + hasher.update(input); + let mut digest = [0u8; 32]; + hasher.finalize(&mut digest); + digest +} + +fn digest_fragmented(input: &[u8]) -> [u8; 32] { + const CHUNKS: [usize; 6] = [1, 3, 17, 64, 5, 89]; + let mut hasher = Keccak::v256(); + let mut offset = 0; + let mut chunk_index = 0; + while offset < input.len() { + let end = (offset + CHUNKS[chunk_index % CHUNKS.len()]).min(input.len()); + hasher.update(&input[offset..end]); + offset = end; + chunk_index += 1; + } + let mut digest = [0u8; 32]; + hasher.finalize(&mut digest); + digest +} diff --git a/guest_libs/crypto/Cargo.toml b/guest_libs/crypto/Cargo.toml index a6f9fe08b..540cdbc7a 100644 --- a/guest_libs/crypto/Cargo.toml +++ b/guest_libs/crypto/Cargo.toml @@ -19,8 +19,8 @@ p256 = { git = "https://github.com/scroll-tech/elliptic-curves", branch = "ceno/ thiserror.workspace = true [dev-dependencies] -alloy-consensus = { version = "1.0", features = ["crypto-backend"] } -alloy-primitives = "1.4" +alloy-consensus = { version = "=2.0.5", features = ["crypto-backend"] } +alloy-primitives = "=1.6.0" revm-precompile.workspace = true [features] diff --git a/guest_libs/crypto/src/macros.rs b/guest_libs/crypto/src/macros.rs index 47d4b4e70..794054c81 100644 --- a/guest_libs/crypto/src/macros.rs +++ b/guest_libs/crypto/src/macros.rs @@ -78,16 +78,16 @@ macro_rules! ceno_crypto { } #[allow(dead_code)] - fn __map_err(e: $crate::CenoCryptoError) -> __rp::PrecompileError { + fn __map_err(e: $crate::CenoCryptoError) -> __rp::PrecompileHalt { match e { $crate::CenoCryptoError::Bn254FieldPointNotAMember => { - __rp::PrecompileError::Bn254FieldPointNotAMember + __rp::PrecompileHalt::Bn254FieldPointNotAMember } $crate::CenoCryptoError::Bn254AffineGFailedToCreate => { - __rp::PrecompileError::Bn254AffineGFailedToCreate + __rp::PrecompileHalt::Bn254AffineGFailedToCreate } - $crate::CenoCryptoError::Bn254PairLength => __rp::PrecompileError::Bn254PairLength, - _ => __rp::PrecompileError::Other(e.to_string()), + $crate::CenoCryptoError::Bn254PairLength => __rp::PrecompileHalt::Bn254PairLength, + _ => __rp::PrecompileHalt::other(e.to_string()), } } @@ -103,7 +103,7 @@ macro_rules! ceno_crypto { &self, p1: &[u8], p2: &[u8], - ) -> Result<[u8; 64], __rp::PrecompileError> { + ) -> Result<[u8; 64], __rp::PrecompileHalt> { $crate::bn254::g1_point_add(p1, p2).map_err(__map_err) } #[inline] @@ -111,14 +111,14 @@ macro_rules! ceno_crypto { &self, point: &[u8], scalar: &[u8], - ) -> Result<[u8; 64], __rp::PrecompileError> { + ) -> Result<[u8; 64], __rp::PrecompileHalt> { $crate::bn254::g1_point_mul(point, scalar).map_err(__map_err) } #[inline] fn bn254_pairing_check( &self, pairs: &[(&[u8], &[u8])], - ) -> Result { + ) -> Result { $crate::bn254::pairing_check(pairs).map_err(__map_err) } #[inline] @@ -127,7 +127,7 @@ macro_rules! ceno_crypto { sig: &[u8; 64], recid: u8, msg: &[u8; 32], - ) -> Result<[u8; 32], __rp::PrecompileError> { + ) -> Result<[u8; 32], __rp::PrecompileHalt> { $crate::secp256k1::secp256k1_ecrecover(sig, recid, msg).map_err(__map_err) } #[inline] @@ -157,6 +157,19 @@ macro_rules! ceno_crypto { .map(|res| Address::from_slice(&res[12..])) .map_err(__ac::crypto::RecoveryError::from_source) } + + #[inline] + fn verify_and_compute_signer_unchecked( + &self, + pubkey: &[u8; 65], + sig: &[u8; 64], + msg: &[u8; 32], + ) -> Result<$addr, __ac::crypto::RecoveryError> { + use $addr as Address; + $crate::secp256k1::verify_and_compute_signer_unchecked(pubkey, sig, msg) + .map(|res| Address::from_slice(&res[12..])) + .map_err(__ac::crypto::RecoveryError::from_source) + } } }; } diff --git a/guest_libs/crypto/src/secp256k1.rs b/guest_libs/crypto/src/secp256k1.rs index 053d564eb..fb0e2d5ed 100644 --- a/guest_libs/crypto/src/secp256k1.rs +++ b/guest_libs/crypto/src/secp256k1.rs @@ -2,7 +2,7 @@ use crate::CenoCryptoError; use ceno_keccak::{Hasher, Keccak}; #[cfg(feature = "profiling")] use ceno_syscall::syscall_phantom_log_pc_cycle; -use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; +use k256::ecdsa::{RecoveryId, Signature, VerifyingKey, signature::hazmat::PrehashVerifier}; /// secp256k1 ECDSA signature recovery. #[inline] @@ -46,3 +46,25 @@ pub fn secp256k1_ecrecover( syscall_phantom_log_pc_cycle("secp256k1_ecrecover end"); Ok(hash) } + +/// Verify a signature against an uncompressed public key and return its Ethereum address. +#[inline] +pub fn verify_and_compute_signer_unchecked( + pubkey: &[u8; 65], + sig: &[u8; 64], + msg: &[u8; 32], +) -> Result<[u8; 32], CenoCryptoError> { + let verifying_key = VerifyingKey::from_sec1_bytes(pubkey)?; + let mut signature = Signature::from_slice(sig)?; + if let Some(normalized) = signature.normalize_s() { + signature = normalized; + } + verifying_key.verify_prehash(msg, &signature)?; + + let mut hasher = Keccak::v256(); + let mut hash = [0u8; 32]; + hasher.update(&pubkey[1..]); + hasher.finalize(&mut hash); + hash[..12].fill(0); + Ok(hash) +} diff --git a/guest_libs/keccak/src/vendor.rs b/guest_libs/keccak/src/vendor.rs index b3c26bc0b..31fd154a4 100644 --- a/guest_libs/keccak/src/vendor.rs +++ b/guest_libs/keccak/src/vendor.rs @@ -1,6 +1,6 @@ //! Private types and traits copied from the `tiny-keccak`. -use ceno_syscall::KECCAK_STATE_WORDS; +use ceno_syscall::{KECCAK_RATE_WORDS, KECCAK_STATE_WORDS, syscall_keccak_xorin}; pub mod keccak; @@ -25,6 +25,27 @@ impl Buffer { } pub fn xorin(&mut self, src: &[u8], offset: usize, len: usize) { + if len == 0 { + return; + } + + // The accelerated syscall covers Keccak's 136-byte rate. Keep the + // generic path for the 144-byte Keccak-224 rate. + if offset + len <= KECCAK_RATE_WORDS * 4 { + let mut block = [0u32; KECCAK_RATE_WORDS]; + // SAFETY: `block` is contiguous, aligned storage with exactly the + // byte length represented by this slice. + let block_bytes = unsafe { + core::slice::from_raw_parts_mut( + block.as_mut_ptr().cast::(), + KECCAK_RATE_WORDS * 4, + ) + }; + block_bytes[offset..offset + len].copy_from_slice(&src[..len]); + syscall_keccak_xorin(&mut self.0, &block); + return; + } + self.execute(offset, len, |dst| { assert!(dst.len() <= src.len()); let len = dst.len();