diff --git a/.github/workflows/primitives.yml b/.github/workflows/primitives.yml index d94e1c25e1..9d833fb91f 100644 --- a/.github/workflows/primitives.yml +++ b/.github/workflows/primitives.yml @@ -8,7 +8,7 @@ on: paths: - "crates/circuits/primitives/**" - "crates/circuits/poseidon2-air/**" - - "crates/circuits/sha256-air/**" + - "crates/circuits/sha-air/**" - "Cargo.toml" concurrency: @@ -45,7 +45,7 @@ jobs: run: | cargo nextest run --cargo-profile fast --features parallel - - name: Run tests for sha256-air - working-directory: crates/circuits/sha256-air + - name: Run tests for sha-air + working-directory: crates/circuits/sha-air run: | cargo nextest run --cargo-profile fast --features parallel diff --git a/Cargo.lock b/Cargo.lock index 836ac14fec..33438cbff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -81,18 +81,18 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.21" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478bedf4d24e71ea48428d1bc278553bd7c6ae07c30ca063beb0b09fe58a9e74" +checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", - "derive_more 1.0.0", + "derive_more 2.0.1", "foldhash", "hashbrown 0.15.2", - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "k256", "keccak-asm", @@ -125,7 +125,7 @@ checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "ark-ff" @@ -352,15 +352,21 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "aurora-engine-modexp" version = "1.2.0" @@ -379,7 +385,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -390,9 +396,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.5.18" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90aff65e86db5fe300752551c1b015ef72b708ac54bded8ef43d0d53cb7cb0b1" +checksum = "8c39646d1a6b51240a1a23bb57ea4eebede7e16fbc237fdc876980233dcecb4f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -400,7 +406,7 @@ dependencies = [ "aws-sdk-ssooidc", "aws-sdk-sts", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -409,7 +415,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 0.2.12", + "http 1.3.1", "ring", "time", "tokio", @@ -420,9 +426,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +checksum = "4471bef4c22a06d2c7a1b6492493d3fdf24a805323109d6874f9c94d5906ac14" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -430,17 +436,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-lc-rs" +version = "1.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "aws-runtime" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dd04d39cc12844c0994f2c9c5a6f5184c22e9188ec1ff723de41910a21dcad" +checksum = "0aff45ffe35196e593ea3b9dd65b320e51e2dda95aff4390bc459e461d09c6ad" dependencies = [ "aws-credential-types", "aws-sigv4", "aws-smithy-async", "aws-smithy-eventstream", - "aws-smithy-http 0.60.12", + "aws-smithy-http", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -458,9 +487,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.78.0" +version = "1.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038614b6cf7dd68d9a7b5b39563d04337eb3678d1d4173e356e927b0356158a" +checksum = "3a36b09e8273d89c4f35ea122b83b30e48f906f3b644460d72a7d3656d1be93d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -468,7 +497,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-checksums", "aws-smithy-eventstream", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -480,6 +509,7 @@ dependencies = [ "hex", "hmac", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", "lru", "once_cell", @@ -492,20 +522,21 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.61.0" +version = "1.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65ff295979977039a25f5a0bf067a64bc5e6aa38f3cef4037cf42516265553c" +checksum = "b1cb45b83b53b5cd55ee33fd9fd8a70750255a3f286e4dca20e882052f2b256f" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -514,20 +545,21 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.62.0" +version = "1.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91430a60f754f235688387b75ee798ef00cfd09709a582be2b7525ebb5306d4f" +checksum = "c8d4d9bc075ea6238778ed3951b65d3cde8c3864282d64fdcd19f2a90c0609f1" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -536,14 +568,14 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.62.0" +version = "1.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9276e139d39fff5a0b0c984fc2d30f970f9a202da67234f948fda02e5bea1dbe" +checksum = "819ccba087f403890fee4825eeab460e64c59345667d2b83a12cf544b581e3a7" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-query", "aws-smithy-runtime", @@ -551,6 +583,7 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -559,13 +592,13 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe75fad52793ce6dec0dc3d4b1f388f038b5eb866c8d4d7f3a8e21b5ea5051" +checksum = "69d03c3c05ff80d54ff860fe38c726f6f494c639ae975203a101335f223386db" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", - "aws-smithy-http 0.60.12", + "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", @@ -574,7 +607,7 @@ dependencies = [ "hex", "hmac", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "once_cell", "p256 0.11.1", "percent-encoding", @@ -588,9 +621,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" dependencies = [ "futures-util", "pin-project-lite", @@ -599,11 +632,11 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.63.0" +version = "0.63.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c" +checksum = "b65d21e1ba6f2cdec92044f904356a19f5ad86961acf015741106cdfafd747c0" dependencies = [ - "aws-smithy-http 0.60.12", + "aws-smithy-http", "aws-smithy-types", "bytes", "crc32c", @@ -621,9 +654,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.7" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461e5e02f9864cba17cff30f007c2e37ade94d01e87cdb5204e44a84e6d38c17" +checksum = "7c45d3dddac16c5c59d553ece225a88870cf81b7b813c9cc17b78cf4685eac7a" dependencies = [ "aws-smithy-types", "bytes", @@ -632,16 +665,18 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.12" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7809c27ad8da6a6a68c454e651d4962479e81472aa19ae99e59f9aba1f9713cc" +checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" dependencies = [ + "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "bytes-utils", "futures-core", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", "once_cell", "percent-encoding", @@ -651,35 +686,52 @@ dependencies = [ ] [[package]] -name = "aws-smithy-http" -version = "0.61.1" +name = "aws-smithy-http-client" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f276f21c7921fe902826618d1423ae5bf74cf8c1b8472aee8434f3dfd31824" +checksum = "8aff1159006441d02e57204bf57a1b890ba68bedb6904ffd2873c1c4c11c546b" dependencies = [ - "aws-smithy-eventstream", + "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", + "h2 0.4.8", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", - "once_cell", - "percent-encoding", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.5", + "hyper-util", "pin-project-lite", - "pin-utils", + "rustls 0.21.12", + "rustls 0.23.25", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.2" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-observability" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445d065e76bc1ef54963db400319f1dd3ebb3e0a74af20f7f7630625b0cc7cc0" +dependencies = [ + "aws-smithy-runtime-api", + "once_cell", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -692,42 +744,40 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.8" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" +checksum = "0152749e17ce4d1b47c7747bdfec09dac1ccafdcbc741ebf9daa2a373356730f" dependencies = [ "aws-smithy-async", - "aws-smithy-http 0.60.12", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", "fastrand", - "h2", "http 0.2.12", + "http 1.3.1", "http-body 0.4.6", "http-body 1.0.1", - "httparse", - "hyper", - "hyper-rustls", "once_cell", "pin-project-lite", "pin-utils", - "rustls", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" dependencies = [ "aws-smithy-async", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "pin-project-lite", "tokio", "tracing", @@ -736,16 +786,16 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.13" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" +checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" dependencies = [ "base64-simd", "bytes", "bytes-utils", "futures-core", "http 0.2.12", - "http 1.2.0", + "http 1.3.1", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -771,9 +821,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.5" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbd0a668309ec1f66c0f6bda4840dd6d4796ae26d699ebc266d7cc95c6d040f" +checksum = "3873f8deed8927ce8d04487630dc9ff73193bab64742a61d050e57a68dec4125" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -841,9 +891,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bincode" @@ -854,6 +904,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.100", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -871,9 +944,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcode" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c1406a27371b2f76232a2259df6ab607b91b5a0a7476a7729ff590df5a969a" +checksum = "cf300f4aa6e66f3bdff11f1236a88c622fe47ea814524792240b4d554d9858ee" dependencies = [ "arrayvec", "bitcode_derive", @@ -890,14 +963,14 @@ checksum = "42b6b4cb608b8282dc3b53d0f4c9ab404655d562674c682db7e6c0458cc83c23" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" @@ -933,16 +1006,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937" +checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "memmap2", ] [[package]] @@ -981,9 +1053,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.3.2" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7acc34ff59877422326db7d6f2d845a582b16396b6b08194942bf34c6528ab" +checksum = "65268237be94042665b92034f979c42d431d2fd998b49809543afe3e66abad1c" dependencies = [ "bon-macros", "rustversion", @@ -991,9 +1063,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.3.2" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4159dd617a7fbc9be6a692fe69dc2954f8e6bb6bb5e4d7578467441390d77fd0" +checksum = "803c95b2ecf650eb10b5f87dda6b9f6a1b758cee53245e2b7b825c9b3803a443" dependencies = [ "darling", "ident_case", @@ -1001,7 +1073,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1012,15 +1084,15 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder" @@ -1030,9 +1102,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytes-utils" @@ -1110,7 +1182,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.25", + "semver 1.0.26", "serde", "serde_json", "thiserror 1.0.69", @@ -1124,15 +1196,24 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.14" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "jobserver", "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1141,15 +1222,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets", + "windows-link", ] [[package]] @@ -1179,11 +1260,22 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" -version = "4.5.30" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -1191,9 +1283,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -1203,14 +1295,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1219,6 +1311,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -1292,6 +1393,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1508,7 +1619,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1519,7 +1630,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1544,9 +1655,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", "serde", @@ -1571,7 +1682,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1582,7 +1693,7 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1595,7 +1706,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1604,7 +1715,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1615,7 +1735,19 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", + "unicode-xid", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", "unicode-xid", ] @@ -1648,7 +1780,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1657,11 +1789,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "ecdsa" @@ -1691,9 +1829,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elf" @@ -1730,7 +1868,7 @@ dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.5", "digest 0.10.7", - "ff 0.13.0", + "ff 0.13.1", "generic-array", "group 0.13.0", "pkcs8 0.10.2", @@ -1767,7 +1905,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1778,7 +1916,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -1792,9 +1930,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", @@ -1892,9 +2030,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "bitvec", "byteorder", @@ -1905,12 +2043,11 @@ dependencies = [ [[package]] name = "ff_derive" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f54704be45ed286151c5e11531316eaef5b8f5af7d597b806fdb8af108d84a" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" dependencies = [ "addchain", - "cfg-if", "num-bigint 0.3.3", "num-integer", "num-traits", @@ -1939,9 +2076,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1952,6 +2089,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -2027,26 +2170,26 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "getset" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded738faa0e88d3abc9d1a13cb11adc2073c400969eeb8793cf7132589959fc" +checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2070,9 +2213,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17fcdf9683c406c2fc4d124afd29c0d595e22210d633cbdb8695ba9935ab1dc6" +checksum = "bf3aa70d918d2b234126ff4f850f628f172542bf0603ded26b8ee36e5e22d5f9" [[package]] name = "glob" @@ -2098,7 +2241,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff 0.13.1", "rand_core", "subtle", ] @@ -2115,7 +2258,26 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.8.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.8.0", "slab", "tokio", "tokio-util", @@ -2124,9 +2286,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" dependencies = [ "cfg-if", "crunchy", @@ -2149,7 +2311,7 @@ checksum = "62f0ca78d12ac5c893f286d7cdfe3869290305ab8cac376e2592cdc8396da102" dependencies = [ "blake2b_simd", "crossbeam", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "halo2curves-axiom", "itertools 0.11.0", @@ -2226,7 +2388,7 @@ checksum = "b756596082144af6e57105a20403b7b80fe9dccd085700b74fae3af523b74dba" dependencies = [ "blake2", "digest 0.10.7", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "halo2derive", "hex", @@ -2255,7 +2417,7 @@ checksum = "dd8309e4638b4f1bcf6613d72265a84074d26034c35edc5d605b5688e580b8b8" dependencies = [ "blake2b_simd", "digest 0.10.7", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "hex", "lazy_static", @@ -2329,9 +2491,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "hex" @@ -2357,6 +2519,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "0.2.12" @@ -2370,9 +2541,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -2397,27 +2568,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -2435,7 +2606,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -2449,6 +2620,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -2457,24 +2648,62 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.32", "log", - "rustls", - "rustls-native-certs", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.25", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", "tokio", - "tokio-rustls", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -2529,9 +2758,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -2553,9 +2782,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -2574,9 +2803,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -2603,7 +2832,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2650,7 +2879,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -2672,9 +2901,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -2683,11 +2912,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.0", "libc", "windows-sys 0.59.0", ] @@ -2716,6 +2945,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -2727,9 +2965,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" @@ -2805,11 +3043,17 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libgit2-sys" @@ -2823,6 +3067,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "libm" version = "0.2.11" @@ -2831,9 +3085,9 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" -version = "0.1.39" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" +checksum = "07d0e07885d6a754b9c7993f2625187ad694ee985d60f23355ff0e7077261502" dependencies = [ "cc", "libc", @@ -2841,9 +3095,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -2863,11 +3117,17 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lockfree-object-pool" @@ -2877,9 +3137,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -2899,6 +3159,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2925,15 +3195,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" -dependencies = [ - "libc", -] - [[package]] name = "memuse" version = "0.2.2" @@ -2956,7 +3217,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62a6a1f7141f1d9bc7a886b87536bbfc97752e08b369e1e0453a9acfab5f5da4" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "lockfree-object-pool", "metrics", @@ -2977,7 +3238,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.14.5", - "indexmap 2.7.1", + "indexmap 2.8.0", "metrics", "num_cpus", "ordered-float", @@ -2988,18 +3249,24 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" +checksum = "99585191385958383e13f6b822e6b6d8d9cf928e7d286ceb092da92b43c87bc1" dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -3015,6 +3282,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -3024,6 +3306,16 @@ dependencies = [ "smallvec", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3179,15 +3471,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" @@ -3243,7 +3535,7 @@ version = "0.1.0" dependencies = [ "openvm-macros-common", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3264,7 +3556,7 @@ version = "1.0.0-rc.2" dependencies = [ "openvm-macros-common", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3451,7 +3743,7 @@ version = "1.0.0-rc.2" dependencies = [ "itertools 0.14.0", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3476,7 +3768,7 @@ version = "1.0.0-rc.2" dependencies = [ "itertools 0.14.0", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3499,7 +3791,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3585,7 +3877,7 @@ version = "1.0.0-rc.2" dependencies = [ "openvm-macros-common", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3625,7 +3917,7 @@ dependencies = [ "quote", "strum", "strum_macros", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3694,7 +3986,7 @@ dependencies = [ name = "openvm-macros-common" version = "1.0.0-rc.2" dependencies = [ - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -3774,7 +4066,7 @@ name = "openvm-native-compiler-derive" version = "1.0.0-rc.2" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4080,17 +4372,31 @@ dependencies = [ ] [[package]] -name = "openvm-sha256-air" +name = "openvm-sha-air" version = "1.0.0-rc.2" dependencies = [ + "ndarray", "openvm-circuit", "openvm-circuit-primitives", + "openvm-sha-macros", "openvm-stark-backend", "openvm-stark-sdk", "rand", "sha2", ] +[[package]] +name = "openvm-sha-macros" +version = "1.0.0-rc.2" +dependencies = [ + "itertools 0.14.0", + "ndarray", + "openvm-sha-air", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "openvm-sha256-circuit" version = "1.0.0-rc.2" @@ -4103,7 +4409,7 @@ dependencies = [ "openvm-circuit-primitives-derive", "openvm-instructions", "openvm-rv32im-circuit", - "openvm-sha256-air", + "openvm-sha-air", "openvm-sha256-transpiler", "openvm-stark-backend", "openvm-stark-sdk", @@ -4184,7 +4490,7 @@ source = "git+https://github.com/openvm-org/stark-backend.git?rev=b051e8978da9c8 dependencies = [ "derivative", "derive_more 0.99.19", - "ff 0.13.0", + "ff 0.13.1", "itertools 0.14.0", "metrics", "metrics-tracing-context", @@ -4330,7 +4636,7 @@ name = "p3-bn254-fr" version = "0.1.0" source = "git+https://github.com/Plonky3/Plonky3.git?rev=88d7f05#88d7f059500fd956a7c1eb121e08653e5974728d" dependencies = [ - "ff 0.13.0", + "ff 0.13.1", "halo2curves", "num-bigint 0.4.6", "p3-field", @@ -4662,7 +4968,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4687,7 +4993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" dependencies = [ "blake2b_simd", - "ff 0.13.0", + "ff 0.13.1", "group 0.13.0", "lazy_static", "rand", @@ -4709,12 +5015,12 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] @@ -4752,9 +5058,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plotters" @@ -4786,9 +5092,18 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "poseidon-primitives" @@ -4797,7 +5112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4aaeda7a092e21165cc5f0cbc738e72a46f31c03c3cbd87b71ceae9d2d93bc" dependencies = [ "bitvec", - "ff 0.13.0", + "ff 0.13.1", "lazy_static", "log", "rand", @@ -4813,21 +5128,21 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -4843,9 +5158,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] @@ -4869,14 +5184,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -4924,13 +5239,19 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -4988,13 +5309,19 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.4.0" +version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529468c1335c1c03919960dfefdb1b3648858c20d7ec2d0663e728e4a717efbc" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ "bitflags", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -5152,9 +5479,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", @@ -5276,7 +5603,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.25", + "semver 1.0.26", ] [[package]] @@ -5288,7 +5615,20 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -5300,10 +5640,24 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.1", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -5313,7 +5667,19 @@ dependencies = [ "openssl-probe", "rustls-pemfile", "schannel", - "security-framework", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", ] [[package]] @@ -5325,6 +5691,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -5335,11 +5707,23 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rusty-fork" @@ -5355,9 +5739,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -5441,7 +5825,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -5468,9 +5865,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -5486,9 +5883,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -5513,22 +5910,22 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "itoa", "memchr", "ryu", @@ -5554,7 +5951,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_derive", "serde_json", @@ -5571,7 +5968,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5805,7 +6202,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5853,9 +6250,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -5870,7 +6267,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5887,15 +6284,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.17.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -5917,7 +6313,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5928,7 +6324,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "test-case-core", ] @@ -5951,7 +6347,7 @@ checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -5965,11 +6361,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -5980,18 +6376,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6035,9 +6431,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -6052,15 +6448,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -6097,9 +6493,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -6120,7 +6516,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6129,15 +6525,25 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.25", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -6173,13 +6579,29 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.8.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -6205,7 +6627,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6308,9 +6730,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" @@ -6377,9 +6799,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.13.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" [[package]] name = "valuable" @@ -6454,9 +6876,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -6483,7 +6905,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -6505,7 +6927,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6529,6 +6951,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "winapi" version = "0.3.9" @@ -6569,6 +7003,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + [[package]] name = "windows-sys" version = "0.52.0" @@ -6653,18 +7093,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] @@ -6716,7 +7156,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -6726,8 +7166,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -6738,27 +7186,38 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", "synstructure", ] @@ -6779,7 +7238,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] @@ -6801,7 +7260,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.100", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 03c1c2eb06..7bee31cc5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,7 +112,8 @@ openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", re openvm-sdk = { path = "crates/sdk", default-features = false } openvm-mod-circuit-builder = { path = "crates/circuits/mod-builder", default-features = false } openvm-poseidon2-air = { path = "crates/circuits/poseidon2-air", default-features = false } -openvm-sha256-air = { path = "crates/circuits/sha256-air", default-features = false } +openvm-sha-air = { path = "crates/circuits/sha-air", default-features = false } +openvm-sha-macros = { path = "crates/circuits/sha-macros", default-features = false } openvm-circuit-primitives = { path = "crates/circuits/primitives", default-features = false } openvm-circuit-primitives-derive = { path = "crates/circuits/primitives/derive", default-features = false } openvm = { path = "crates/toolchain/openvm", default-features = false } diff --git a/crates/circuits/sha256-air/Cargo.toml b/crates/circuits/sha-air/Cargo.toml similarity index 85% rename from crates/circuits/sha256-air/Cargo.toml rename to crates/circuits/sha-air/Cargo.toml index c376a1ffdd..21524930cd 100644 --- a/crates/circuits/sha256-air/Cargo.toml +++ b/crates/circuits/sha-air/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "openvm-sha256-air" +name = "openvm-sha-air" version.workspace = true authors.workspace = true edition.workspace = true @@ -9,6 +9,8 @@ openvm-circuit-primitives = { workspace = true } openvm-stark-backend = { workspace = true } sha2 = { version = "0.10", features = ["compress"] } rand.workspace = true +openvm-sha-macros = { workspace = true } +ndarray = "0.16" [dev-dependencies] openvm-stark-sdk = { workspace = true } diff --git a/crates/circuits/sha-air/src/air.rs b/crates/circuits/sha-air/src/air.rs new file mode 100644 index 0000000000..fe44425df7 --- /dev/null +++ b/crates/circuits/sha-air/src/air.rs @@ -0,0 +1,674 @@ +use std::{cmp::max, iter::once, marker::PhantomData}; + +use ndarray::s; +use openvm_circuit_primitives::{ + bitwise_op_lookup::BitwiseOperationLookupBus, + encoder::Encoder, + utils::{not, select}, + SubAir, +}; +use openvm_stark_backend::{ + interaction::{BusIndex, InteractionBuilder, PermutationCheckBus}, + p3_air::{AirBuilder, BaseAir}, + p3_field::{Field, FieldAlgebra}, + p3_matrix::Matrix, +}; + +use super::{ + big_sig0_field, big_sig1_field, ch_field, compose, maj_field, small_sig0_field, + small_sig1_field, +}; +use crate::{ + constraint_word_addition, word_into_u16_limbs, ShaConfig, ShaDigestColsRef, + ShaPrecomputedValues, ShaRoundColsRef, +}; + +/// Expects the message to be padded to a multiple of C::BLOCK_WORDS * C::WORD_BITS bits +#[derive(Clone, Debug)] +pub struct ShaAir> { + pub bitwise_lookup_bus: BitwiseOperationLookupBus, + pub row_idx_encoder: Encoder, + /// Internal bus for self-interactions in this AIR. + bus: PermutationCheckBus, + _phantom: PhantomData, +} + +impl> ShaAir { + pub fn new(bitwise_lookup_bus: BitwiseOperationLookupBus, self_bus_idx: BusIndex) -> Self { + Self { + bitwise_lookup_bus, + row_idx_encoder: Encoder::new(C::ROWS_PER_BLOCK + 1, 2, false), // + 1 for dummy (padding) rows + bus: PermutationCheckBus::new(self_bus_idx), + _phantom: PhantomData, + } + } +} + +impl> BaseAir for ShaAir { + fn width(&self) -> usize { + max(C::ROUND_WIDTH, C::DIGEST_WIDTH) + } +} + +impl> SubAir + for ShaAir +{ + /// The start column for the sub-air to use + type AirContext<'a> + = usize + where + Self: 'a, + AB: 'a, + ::Var: 'a, + ::Expr: 'a; + + fn eval<'a>(&'a self, builder: &'a mut AB, start_col: Self::AirContext<'a>) + where + ::Var: 'a, + ::Expr: 'a, + { + self.eval_row(builder, start_col); + self.eval_transitions(builder, start_col); + } +} + +impl> ShaAir { + /// Implements the single row constraints (i.e. imposes constraints only on local) + /// Implements some sanity constraints on the row index, flags, and work variables + fn eval_row(&self, builder: &mut AB, start_col: usize) { + let main = builder.main(); + let local = main.row_slice(0); + + // Doesn't matter which column struct we use here as we are only interested in the common columns + let local_cols: ShaDigestColsRef = + ShaDigestColsRef::from::(&local[start_col..start_col + C::DIGEST_WIDTH]); + let flags = &local_cols.flags; + builder.assert_bool(*flags.is_round_row); + builder.assert_bool(*flags.is_first_4_rows); + builder.assert_bool(*flags.is_digest_row); + builder.assert_bool(*flags.is_round_row + *flags.is_digest_row); + builder.assert_bool(*flags.is_last_block); + + self.row_idx_encoder + .eval(builder, local_cols.flags.row_idx.to_slice().unwrap()); + builder.assert_one(self.row_idx_encoder.contains_flag_range::( + local_cols.flags.row_idx.to_slice().unwrap(), + 0..=C::ROWS_PER_BLOCK, + )); + builder.assert_eq( + self.row_idx_encoder + .contains_flag_range::(local_cols.flags.row_idx.to_slice().unwrap(), 0..=3), + *flags.is_first_4_rows, + ); + builder.assert_eq( + self.row_idx_encoder.contains_flag_range::( + local_cols.flags.row_idx.to_slice().unwrap(), + 0..=C::ROUND_ROWS - 1, + ), + *flags.is_round_row, + ); + builder.assert_eq( + self.row_idx_encoder.contains_flag::( + local_cols.flags.row_idx.to_slice().unwrap(), + &[C::ROUND_ROWS], + ), + *flags.is_digest_row, + ); + // If padding row we want the row_idx to be C::ROWS_PER_BLOCK + builder.assert_eq( + self.row_idx_encoder.contains_flag::( + local_cols.flags.row_idx.to_slice().unwrap(), + &[C::ROWS_PER_BLOCK], + ), + flags.is_padding_row(), + ); + + // Constrain a, e, being composed of bits: we make sure a and e are always in the same place in the trace matrix + // Note: this has to be true for every row, even padding rows + for i in 0..C::ROUNDS_PER_ROW { + for j in 0..C::WORD_BITS { + builder.assert_bool(local_cols.hash.a[[i, j]]); + builder.assert_bool(local_cols.hash.e[[i, j]]); + } + } + } + + /// Implements constraints for a digest row that ensure proper state transitions between blocks + /// This validates that: + /// The work variables are correctly initialized for the next message block + /// For the last message block, the initial state matches SHA_H constants + fn eval_digest_row( + &self, + builder: &mut AB, + local: ShaRoundColsRef, + next: ShaDigestColsRef, + ) { + // Check that if this is the last row of a message or an inpadding row, the hash should be the [SHA_H] + for i in 0..C::ROUNDS_PER_ROW { + let a = next.hash.a.row(i).mapv(|x| x.into()).to_vec(); + let e = next.hash.e.row(i).mapv(|x| x.into()).to_vec(); + + for j in 0..C::WORD_U16S { + let a_limb = compose::(&a[j * 16..(j + 1) * 16], 1); + let e_limb = compose::(&e[j * 16..(j + 1) * 16], 1); + + // If it is a padding row or the last row of a message, the `hash` should be the [SHA_H] + builder + .when( + next.flags.is_padding_row() + + *next.flags.is_last_block * *next.flags.is_digest_row, + ) + .assert_eq( + a_limb, + AB::Expr::from_canonical_u32( + word_into_u16_limbs::(C::get_h()[C::ROUNDS_PER_ROW - i - 1])[j], + ), + ); + + builder + .when( + next.flags.is_padding_row() + + *next.flags.is_last_block * *next.flags.is_digest_row, + ) + .assert_eq( + e_limb, + AB::Expr::from_canonical_u32( + word_into_u16_limbs::(C::get_h()[C::ROUNDS_PER_ROW - i + 3])[j], + ), + ); + } + } + + // Check if last row of a non-last block, the `hash` should be equal to the final hash of the current block + for i in 0..C::ROUNDS_PER_ROW { + let prev_a = next.hash.a.row(i).mapv(|x| x.into()).to_vec(); + let prev_e = next.hash.e.row(i).mapv(|x| x.into()).to_vec(); + let cur_a = next + .final_hash + .row(C::ROUNDS_PER_ROW - i - 1) + .mapv(|x| x.into()); + + let cur_e = next + .final_hash + .row(C::ROUNDS_PER_ROW - i + 3) + .mapv(|x| x.into()); + for j in 0..C::WORD_U8S { + let prev_a_limb = compose::(&prev_a[j * 8..(j + 1) * 8], 1); + let prev_e_limb = compose::(&prev_e[j * 8..(j + 1) * 8], 1); + + builder + .when(not(*next.flags.is_last_block) * *next.flags.is_digest_row) + .assert_eq(prev_a_limb, cur_a[j].clone()); + + builder + .when(not(*next.flags.is_last_block) * *next.flags.is_digest_row) + .assert_eq(prev_e_limb, cur_e[j].clone()); + } + } + + // Assert that the previous hash + work vars == final hash. + // That is, `next.prev_hash[i] + local.work_vars[i] == next.final_hash[i]` + // where addition is done modulo 2^32 + for i in 0..C::HASH_WORDS { + let mut carry = AB::Expr::ZERO; + for j in 0..C::WORD_U16S { + let work_var_limb = if i < C::ROUNDS_PER_ROW { + compose::( + &local + .work_vars + .a + .slice(s![C::ROUNDS_PER_ROW - 1 - i, j * 16..(j + 1) * 16]) + .as_slice().unwrap(), + 1, + ) + } else { + compose::( + &local + .work_vars + .e + .slice(s![C::ROUNDS_PER_ROW + 3 - i, j * 16..(j + 1) * 16]) + .as_slice().unwrap(), + 1, + ) + }; + let final_hash_limb = compose::( + &next.final_hash.slice(s![i, j * 2..(j + 1) * 2]).as_slice().unwrap(), + 8, + ); + + carry = AB::Expr::from(AB::F::from_canonical_u32(1 << 16).inverse()) + * (next.prev_hash[[i,j]] + work_var_limb + carry - final_hash_limb); + builder + .when(*next.flags.is_digest_row) + .assert_bool(carry.clone()); + } + // constrain the final hash limbs two at a time since we can do two checks per interaction + for chunk in next.final_hash.row(i).as_slice().unwrap().chunks(2) { + self.bitwise_lookup_bus + .send_range(chunk[0], chunk[1]) + .eval(builder, *next.flags.is_digest_row); + } + } + } + + fn eval_transitions(&self, builder: &mut AB, start_col: usize) { + let main = builder.main(); + let local = main.row_slice(0); + let next = main.row_slice(1); + + // Doesn't matter what column structs we use here + let local_cols: ShaRoundColsRef = + ShaRoundColsRef::from::(&local[start_col..start_col + C::ROUND_WIDTH]); + let next_cols: ShaRoundColsRef = + ShaRoundColsRef::from::(&next[start_col..start_col + C::ROUND_WIDTH]); + + let local_is_padding_row = local_cols.flags.is_padding_row(); + // Note that there will always be a padding row in the trace since the unpadded height is a multiple of 17. + // So the next row is padding iff the current block is the last block in the trace. + let next_is_padding_row = next_cols.flags.is_padding_row(); + + // We check that the very last block has `is_last_block` set to true, which guarantees that + // there is at least one complete message. If other digest rows have `is_last_block` set to true, + // then the trace will be interpreted as containing multiple messages. + builder + .when(next_is_padding_row.clone()) + .when(*next_cols.flags.is_digest_row) + .assert_one(*next_cols.flags.is_last_block); + // If we are in a round row, the next row cannot be a padding row + builder + .when(*local_cols.flags.is_round_row) + .assert_zero(next_is_padding_row.clone()); + // The first row must be a round row + builder + .when_first_row() + .assert_one(*local_cols.flags.is_round_row); + // If we are in a padding row, the next row must also be a padding row + builder + .when_transition() + .when(local_is_padding_row.clone()) + .assert_one(next_is_padding_row.clone()); + // If we are in a digest row, the next row cannot be a digest row + builder + .when(*local_cols.flags.is_digest_row) + .assert_zero(*next_cols.flags.is_digest_row); + // Constrain how much the row index changes by + // round->round: 1 + // round->digest: 1 + // digest->round: -C::ROUND_ROWS + // digest->padding: 1 + // padding->padding: 0 + // Other transitions are not allowed by the above constraints + let delta = *local_cols.flags.is_round_row * AB::Expr::ONE + + *local_cols.flags.is_digest_row + * *next_cols.flags.is_round_row + * AB::Expr::from_canonical_usize(C::ROUND_ROWS) + * AB::Expr::NEG_ONE + + *local_cols.flags.is_digest_row * next_is_padding_row.clone() * AB::Expr::ONE; + + let local_row_idx = self.row_idx_encoder.flag_with_val::( + local_cols.flags.row_idx.to_slice().unwrap(), + &(0..=C::ROWS_PER_BLOCK).map(|i| (i, i)).collect::>(), + ); + let next_row_idx = self.row_idx_encoder.flag_with_val::( + next_cols.flags.row_idx.to_slice().unwrap(), + &(0..=C::ROWS_PER_BLOCK).map(|i| (i, i)).collect::>(), + ); + + builder + .when_transition() + .assert_eq(local_row_idx.clone() + delta, next_row_idx.clone()); + builder.when_first_row().assert_zero(local_row_idx); + + // Constrain the global block index + // We set the global block index to 0 for padding rows + // Starting with 1 so it is not the same as the padding rows + + // Global block index is 1 on first row + builder + .when_first_row() + .assert_one(*local_cols.flags.global_block_idx); + + // Global block index is constant on all rows in a block + builder.when(*local_cols.flags.is_round_row).assert_eq( + *local_cols.flags.global_block_idx, + *next_cols.flags.global_block_idx, + ); + // Global block index increases by 1 between blocks + builder + .when_transition() + .when(*local_cols.flags.is_digest_row) + .when(*next_cols.flags.is_round_row) + .assert_eq( + *local_cols.flags.global_block_idx + AB::Expr::ONE, + *next_cols.flags.global_block_idx, + ); + // Global block index is 0 on padding rows + builder + .when(local_is_padding_row.clone()) + .assert_zero(*local_cols.flags.global_block_idx); + + // Constrain the local block index + // We set the local block index to 0 for padding rows + + // Local block index is constant on all rows in a block + // and its value on padding rows is equal to its value on the first block + builder + .when(not(*local_cols.flags.is_digest_row)) + .assert_eq( + *local_cols.flags.local_block_idx, + *next_cols.flags.local_block_idx, + ); + // Local block index increases by 1 between blocks in the same message + builder + .when(*local_cols.flags.is_digest_row) + .when(not(*local_cols.flags.is_last_block)) + .assert_eq( + *local_cols.flags.local_block_idx + AB::Expr::ONE, + *next_cols.flags.local_block_idx, + ); + // Local block index is 0 on padding rows + // Combined with the above, this means that the local block index is 0 in the first block + builder + .when(*local_cols.flags.is_digest_row) + .when(*local_cols.flags.is_last_block) + .assert_zero(*next_cols.flags.local_block_idx); + + self.eval_message_schedule(builder, local_cols.clone(), next_cols.clone()); + self.eval_work_vars(builder, local_cols.clone(), next_cols); + let next_cols: ShaDigestColsRef = + ShaDigestColsRef::from::(&next[start_col..start_col + C::DIGEST_WIDTH]); + self.eval_digest_row(builder, local_cols, next_cols); + let local_cols: ShaDigestColsRef = + ShaDigestColsRef::from::(&local[start_col..start_col + C::DIGEST_WIDTH]); + self.eval_prev_hash(builder, local_cols, next_is_padding_row); + } + + /// Constrains that the next block's `prev_hash` is equal to the current block's `hash` + /// Note: the constraining is done by interactions with the chip itself on every digest row + fn eval_prev_hash( + &self, + builder: &mut AB, + local: ShaDigestColsRef, + is_last_block_of_trace: AB::Expr, // note this indicates the last block of the trace, not the last block of the message + ) { + // Constrain that next block's `prev_hash` is equal to the current block's `hash` + let composed_hash = (0..C::HASH_WORDS) + .map(|i| { + let hash_bits = if i < C::ROUNDS_PER_ROW { + local + .hash + .a + .row(C::ROUNDS_PER_ROW - 1 - i) + .mapv(|x| x.into()) + .to_vec() + } else { + local + .hash + .e + .row(C::ROUNDS_PER_ROW + 3 - i) + .mapv(|x| x.into()) + .to_vec() + }; + (0..C::WORD_U16S) + .map(|j| compose::(&hash_bits[j * 16..(j + 1) * 16], 1)) + .collect::>() + }) + .collect::>(); + // Need to handle the case if this is the very last block of the trace matrix + let next_global_block_idx = select( + is_last_block_of_trace, + AB::Expr::ONE, + *local.flags.global_block_idx + AB::Expr::ONE, + ); + // The following interactions constrain certain values from block to block + self.bus.send( + builder, + composed_hash + .into_iter() + .flatten() + .chain(once(next_global_block_idx)), + *local.flags.is_digest_row, + ); + + self.bus.receive( + builder, + local + .prev_hash + .flatten() + .mapv(|x| x.into()) + .into_iter() + .chain(once((*local.flags.global_block_idx).into())), + *local.flags.is_digest_row, + ); + } + + /// Constrain the message schedule additions for `next` row + /// Note: For every addition we need to constrain the following for each of [WORD_U16S] limbs + /// sig_1(w_{t-2})[i] + w_{t-7}[i] + sig_0(w_{t-15})[i] + w_{t-16}[i] + carry_w[t][i-1] - carry_w[t][i] * 2^16 - w_t[i] == 0 + /// Refer to [https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf] + fn eval_message_schedule<'a, AB: InteractionBuilder>( + &self, + builder: &mut AB, + local: ShaRoundColsRef<'a, AB::Var>, + next: ShaRoundColsRef<'a, AB::Var>, + ) { + // This `w` array contains 8 message schedule words - w_{idx}, ..., w_{idx+7} for some idx + let w = ndarray::concatenate( + ndarray::Axis(0), + &[local.message_schedule.w, next.message_schedule.w], + ) + .unwrap(); + + // Constrain `w_3` for `next` row + for i in 0..C::ROUNDS_PER_ROW - 1 { + // here we constrain the w_3 of the i_th word of the next row + // w_3 of next is w[i+4-3] = w[i+1] + let w_3 = w.row(i + 1).mapv(|x| x.into()).to_vec(); + let expected_w_3 = next.schedule_helper.w_3.row(i); + for j in 0..C::WORD_U16S { + let w_3_limb = compose::(&w_3[j * 16..(j + 1) * 16], 1); + builder + .when(*local.flags.is_round_row) + .assert_eq(w_3_limb, expected_w_3[j].into()); + } + } + + // Constrain intermed for `next` row + // We will only constrain intermed_12 for rows [3, 14], and let it unconstrained for other rows + // Other rows should put the needed value in intermed_12 to make the below summation constraint hold + let is_row_intermed_12 = self.row_idx_encoder.contains_flag_range::( + next.flags.row_idx.to_slice().unwrap(), + 3..=C::ROUND_ROWS - 2, + ); + // We will only constrain intermed_8 for rows [2, C::ROUND_ROWS - 2], and let it unconstrained for other rows + let is_row_intermed_8 = self.row_idx_encoder.contains_flag_range::( + next.flags.row_idx.to_slice().unwrap(), + 2..=C::ROUND_ROWS - 3, + ); + for i in 0..C::ROUNDS_PER_ROW { + // w_idx + let w_idx = w.row(i).mapv(|x| x.into()).to_vec(); + // sig_0(w_{idx+1}) + let sig_w = small_sig0_field::(w.row(i + 1).as_slice().unwrap()); + for j in 0..C::WORD_U16S { + let w_idx_limb = compose::(&w_idx[j * 16..(j + 1) * 16], 1); + let sig_w_limb = compose::(&sig_w[j * 16..(j + 1) * 16], 1); + + // We would like to constrain this only on rows 0..16, but we can't do a conditional check because the degree is already 3. + // So we must fill in `intermed_4` with dummy values on rows 0 and 16 to ensure the constraint holds on these rows. + builder.when_transition().assert_eq( + next.schedule_helper.intermed_4[[i, j]], + w_idx_limb + sig_w_limb, + ); + + builder.when(is_row_intermed_8.clone()).assert_eq( + next.schedule_helper.intermed_8[[i, j]], + local.schedule_helper.intermed_4[[i, j]], + ); + + builder.when(is_row_intermed_12.clone()).assert_eq( + next.schedule_helper.intermed_12[[i, j]], + local.schedule_helper.intermed_8[[i, j]], + ); + } + } + + // Constrain the message schedule additions for `next` row + for i in 0..C::ROUNDS_PER_ROW { + // Note, here by w_{t} we mean the i_th word of the `next` row + // w_{t-7} + let w_7 = if i < 3 { + local.schedule_helper.w_3.row(i).mapv(|x| x.into()).to_vec() + } else { + let w_3 = w.row(i - 3).mapv(|x| x.into()).to_vec(); + (0..C::WORD_U16S) + .map(|j| compose::(&w_3[j * 16..(j + 1) * 16], 1)) + .collect::>() + }; + // sig_0(w_{t-15}) + w_{t-16} + let intermed_16 = local.schedule_helper.intermed_12.row(i).mapv(|x| x.into()); + + let carries = (0..C::WORD_U16S) + .map(|j| { + next.message_schedule.carry_or_buffer[[i, j * 2]] + + AB::Expr::TWO * next.message_schedule.carry_or_buffer[[i, j * 2 + 1]] + }) + .collect::>(); + + // Constrain `W_{idx} = sig_1(W_{idx-2}) + W_{idx-7} + sig_0(W_{idx-15}) + W_{idx-16}` + // We would like to constrain this only on rows 4..C::ROUND_ROWS, but we can't do a conditional check because the degree of sum is already 3 + // So we must fill in `intermed_12` with dummy values on rows 0..3 and C::ROUND_ROWS-1 and C::ROUND_ROWS to ensure the constraint holds on rows + // 0..4 and C::ROUND_ROWS. Note that the dummy value goes in the previous row to make the current row's constraint hold. + constraint_word_addition::<_, C>( + // Note: here we can't do a conditional check because the degree of sum is already 3 + &mut builder.when_transition(), + &[&small_sig1_field::( + w.row(i + 2).as_slice().unwrap(), + )], + &[&w_7, intermed_16.as_slice().unwrap()], + w.row(i + 4).as_slice().unwrap(), + &carries, + ); + + for j in 0..C::WORD_U16S { + // When on rows 4..C::ROUND_ROWS message schedule carries should be 0 or 1 + let is_row_4_or_more = *next.flags.is_round_row - *next.flags.is_first_4_rows; + builder + .when(is_row_4_or_more.clone()) + .assert_bool(next.message_schedule.carry_or_buffer[[i, j * 2]]); + builder + .when(is_row_4_or_more) + .assert_bool(next.message_schedule.carry_or_buffer[[i, j * 2 + 1]]); + } + // Constrain w being composed of bits + for j in 0..C::WORD_BITS { + builder + .when(*next.flags.is_round_row) + .assert_bool(next.message_schedule.w[[i, j]]); + } + } + } + + /// Constrain the work vars on `next` row according to the sha documentation + /// Refer to [https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf] + fn eval_work_vars<'a, AB: InteractionBuilder>( + &self, + builder: &mut AB, + local: ShaRoundColsRef<'a, AB::Var>, + next: ShaRoundColsRef<'a, AB::Var>, + ) { + let a = + ndarray::concatenate(ndarray::Axis(0), &[local.work_vars.a, next.work_vars.a]).unwrap(); + let e = + ndarray::concatenate(ndarray::Axis(0), &[local.work_vars.e, next.work_vars.e]).unwrap(); + + for i in 0..C::ROUNDS_PER_ROW { + for j in 0..C::WORD_U16S { + // Although we need carry_a <= 6 and carry_e <= 5, constraining carry_a, carry_e in [0, 2^8) is enough + // to prevent overflow and ensure the soundness of the addition we want to check + self.bitwise_lookup_bus + .send_range( + local.work_vars.carry_a[[i, j]], + local.work_vars.carry_e[[i, j]], + ) + .eval(builder, *local.flags.is_round_row); + } + + let w_limbs = (0..C::WORD_U16S) + .map(|j| { + compose::( + next.message_schedule + .w + .slice(s![i, j * 16..(j + 1) * 16]) + .as_slice() + .unwrap(), + 1, + ) * *next.flags.is_round_row + }) + .collect::>(); + + let k_limbs = (0..C::WORD_U16S) + .map(|j| { + self.row_idx_encoder.flag_with_val::( + next.flags.row_idx.to_slice().unwrap(), + &(0..C::ROUND_ROWS) + .map(|rw_idx| { + ( + rw_idx, + word_into_u16_limbs::( + C::get_k()[rw_idx * C::ROUNDS_PER_ROW + i], + )[j] as usize, + ) + }) + .collect::>(), + ) + }) + .collect::>(); + + // Constrain `a = h + sig_1(e) + ch(e, f, g) + K + W + sig_0(a) + Maj(a, b, c)` + // We have to enforce this constraint on all rows since the degree of the constraint is already 3. + // So, we must fill in `carry_a` with dummy values on digest rows to ensure the constraint holds. + constraint_word_addition::<_, C>( + builder, + &[ + e.row(i).mapv(|x| x.into()).as_slice().unwrap(), // previous `h` + &big_sig1_field::(e.row(i + 3).as_slice().unwrap()), // sig_1 of previous `e` + &ch_field::( + e.row(i + 3).as_slice().unwrap(), + e.row(i + 2).as_slice().unwrap(), + e.row(i + 1).as_slice().unwrap(), + ), // Ch of previous `e`, `f`, `g` + &big_sig0_field::(a.row(i + 3).as_slice().unwrap()), // sig_0 of previous `a` + &maj_field::( + a.row(i + 3).as_slice().unwrap(), + a.row(i + 2).as_slice().unwrap(), + a.row(i + 1).as_slice().unwrap(), + ), // Maj of previous a, b, c + ], + &[&w_limbs, &k_limbs], // K and W + a.row(i + 4).as_slice().unwrap(), // new `a` + next.work_vars.carry_a.row(i).as_slice().unwrap(), // carries of addition + ); + + // Constrain `e = d + h + sig_1(e) + ch(e, f, g) + K + W` + // We have to enforce this constraint on all rows since the degree of the constraint is already 3. + // So, we must fill in `carry_e` with dummy values on digest rows to ensure the constraint holds. + constraint_word_addition::<_, C>( + builder, + &[ + a.row(i).mapv(|x| x.into()).as_slice().unwrap(), // previous `d` + e.row(i).mapv(|x| x.into()).as_slice().unwrap(), // previous `h` + &big_sig1_field::(e.row(i + 3).as_slice().unwrap()), // sig_1 of previous `e` + &ch_field::( + e.row(i + 3).as_slice().unwrap(), + e.row(i + 2).as_slice().unwrap(), + e.row(i + 1).as_slice().unwrap(), + ), // Ch of previous `e`, `f`, `g` + ], + &[&w_limbs, &k_limbs], // K and W + e.row(i + 4).as_slice().unwrap(), // new `e` + next.work_vars.carry_e.row(i).as_slice().unwrap(), // carries of addition + ); + } + } +} diff --git a/crates/circuits/sha-air/src/columns.rs b/crates/circuits/sha-air/src/columns.rs new file mode 100644 index 0000000000..221c77e0f9 --- /dev/null +++ b/crates/circuits/sha-air/src/columns.rs @@ -0,0 +1,158 @@ +//! WARNING: the order of fields in the structs is important, do not change it + +use openvm_circuit_primitives::utils::not; +use openvm_sha_macros::ColsRef; +use openvm_stark_backend::p3_field::FieldAlgebra; + +use crate::ShaConfig; + +/// In each SHA block: +/// - First C::ROUND_ROWS rows use ShaRoundCols +/// - Final row uses ShaDigestCols +/// +/// ShaRoundCols and ShaDigestCols share the same first 3 fields: +/// - flags +/// - work_vars/hash (same type, different name) +/// - schedule_helper +/// +/// This design allows for: +/// 1. Common constraints to work on either struct type by accessing these shared fields +/// 2. Specific constraints to use the appropriate struct, with flags helping to do conditional constraints +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaRoundCols< + T, + const WORD_BITS: usize, + const WORD_U8S: usize, + const WORD_U16S: usize, + const ROUNDS_PER_ROW: usize, + const ROUNDS_PER_ROW_MINUS_ONE: usize, + const ROW_VAR_CNT: usize, +> { + pub flags: ShaFlagsCols, + pub work_vars: ShaWorkVarsCols, + pub schedule_helper: + ShaMessageHelperCols, + pub message_schedule: ShaMessageScheduleCols, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaDigestCols< + T, + const WORD_BITS: usize, + const WORD_U8S: usize, + const WORD_U16S: usize, + const HASH_WORDS: usize, + const ROUNDS_PER_ROW: usize, + const ROUNDS_PER_ROW_MINUS_ONE: usize, + const ROW_VAR_CNT: usize, +> { + pub flags: ShaFlagsCols, + /// Will serve as previous hash values for the next block + pub hash: ShaWorkVarsCols, + pub schedule_helper: + ShaMessageHelperCols, + /// The actual final hash values of the given block + /// Note: the above `hash` will be equal to `final_hash` unless we are on the last block + pub final_hash: [[T; WORD_U8S]; HASH_WORDS], + /// The final hash of the previous block + /// Note: will be constrained using interactions with the chip itself + pub prev_hash: [[T; WORD_U16S]; HASH_WORDS], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaMessageScheduleCols< + T, + const WORD_BITS: usize, + const ROUNDS_PER_ROW: usize, + const WORD_U8S: usize, +> { + /// The message schedule words as 32-bit integers + pub w: [[T; WORD_BITS]; ROUNDS_PER_ROW], + /// Will be message schedule carries for rows 4..C::ROUND_ROWS and a buffer for rows 0..4 to be used freely by wrapper chips + /// Note: carries are represented as 2 bit numbers + pub carry_or_buffer: [[T; WORD_U8S]; ROUNDS_PER_ROW], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaWorkVarsCols< + T, + const WORD_BITS: usize, + const ROUNDS_PER_ROW: usize, + const WORD_U16S: usize, +> { + /// `a` and `e` after each iteration as 32-bits + pub a: [[T; WORD_BITS]; ROUNDS_PER_ROW], + pub e: [[T; WORD_BITS]; ROUNDS_PER_ROW], + /// The carry's used for addition during each iteration when computing `a` and `e` + pub carry_a: [[T; WORD_U16S]; ROUNDS_PER_ROW], + pub carry_e: [[T; WORD_U16S]; ROUNDS_PER_ROW], +} + +/// These are the columns that are used to help with the message schedule additions +/// Note: these need to be correctly assigned for every row even on padding rows +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaMessageHelperCols< + T, + const WORD_U16S: usize, + const ROUNDS_PER_ROW: usize, + const ROUNDS_PER_ROW_MINUS_ONE: usize, +> { + /// The following are used to move data forward to constrain the message schedule additions + /// The value of `w` from 3 rounds ago + pub w_3: [[T; WORD_U16S]; ROUNDS_PER_ROW_MINUS_ONE], + /// Here intermediate(i) = w_i + sig_0(w_{i+1}) + /// Intermed_t represents the intermediate t rounds ago + pub intermed_4: [[T; WORD_U16S]; ROUNDS_PER_ROW], + pub intermed_8: [[T; WORD_U16S]; ROUNDS_PER_ROW], + pub intermed_12: [[T; WORD_U16S]; ROUNDS_PER_ROW], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaFlagsCols { + pub is_round_row: T, + /// A flag that indicates if the current row is among the first 4 rows of a block + pub is_first_4_rows: T, + pub is_digest_row: T, + pub is_last_block: T, + /// We will encode the row index [0..17) using 5 cells + //#[length(ROW_VAR_CNT)] + pub row_idx: [T; ROW_VAR_CNT], + /// The global index of the current block + pub global_block_idx: T, + /// Will store the index of the current block in the current message starting from 0 + pub local_block_idx: T, +} + +impl, const ROW_VAR_CNT: usize> + ShaFlagsCols +{ + pub fn is_not_padding_row(&self) -> O { + self.is_round_row + self.is_digest_row + } + + pub fn is_padding_row(&self) -> O + where + O: FieldAlgebra, + { + not(self.is_not_padding_row()) + } +} + +impl<'a, O, T: Copy + core::ops::Add> ShaFlagsColsRef<'a, T> { + pub fn is_not_padding_row(&self) -> O { + *self.is_round_row + *self.is_digest_row + } + + pub fn is_padding_row(&self) -> O + where + O: FieldAlgebra, + { + not(self.is_not_padding_row()) + } +} diff --git a/crates/circuits/sha-air/src/config.rs b/crates/circuits/sha-air/src/config.rs new file mode 100644 index 0000000000..66dc32f96f --- /dev/null +++ b/crates/circuits/sha-air/src/config.rs @@ -0,0 +1,334 @@ +use std::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr}; + +pub trait ShaConfig: Send + Sync + Clone { + type Word: 'static + + Shr + + Shl + + BitAnd + + Not + + BitXor + + BitOr + + RotateRight + + WrappingAdd + + PartialEq + + From + + TryInto + + Copy + + Send + + Sync; + /// Number of bits in a SHA word + const WORD_BITS: usize; + /// Number of 16-bit limbs in a SHA word + const WORD_U16S: usize = Self::WORD_BITS / 16; + /// Number of 8-bit limbs in a SHA word + const WORD_U8S: usize = Self::WORD_BITS / 8; + /// Number of words in a SHA block + const BLOCK_WORDS: usize; + /// Number of cells in a SHA block + const BLOCK_U8S: usize = Self::BLOCK_WORDS * Self::WORD_U8S; + /// Number of bits in a SHA block + const BLOCK_BITS: usize = Self::BLOCK_WORDS * Self::WORD_BITS; + /// Number of rows per block + const ROWS_PER_BLOCK: usize; + /// Number of rounds per row. Must divide Self::ROUNDS_PER_BLOCK + const ROUNDS_PER_ROW: usize; + /// Number of rows used for the sha rounds + const ROUND_ROWS: usize = Self::ROUNDS_PER_BLOCK / Self::ROUNDS_PER_ROW; + /// Number of rows used for the message + const MESSAGE_ROWS: usize = Self::BLOCK_WORDS / Self::ROUNDS_PER_ROW; + /// Number of rounds per row minus one (needed for one of the column structs) + const ROUNDS_PER_ROW_MINUS_ONE: usize = Self::ROUNDS_PER_ROW - 1; + /// Number of rounds per block. Must be a multiple of Self::ROUNDS_PER_ROW + const ROUNDS_PER_BLOCK: usize; + /// Number of words in a SHA hash + const HASH_WORDS: usize; + /// Number of vars needed to encode the row index with [Encoder] + const ROW_VAR_CNT: usize; + + /// Width of the ShaRoundCols + const ROUND_WIDTH: usize = Self::FLAGS_WIDTH + + Self::WORK_VARS_WIDTH + + Self::MESSAGE_HELPER_WIDTH + + Self::MESSAGE_SCHEDULE_WIDTH; + /// Width of the ShaDigestCols + const DIGEST_WIDTH: usize = Self::FLAGS_WIDTH + + Self::WORK_VARS_WIDTH + + Self::MESSAGE_HELPER_WIDTH + + Self::MESSAGE_SCHEDULE_WIDTH + + Self::WORD_U8S * Self::HASH_WORDS + + Self::WORD_U16S * Self::HASH_WORDS; + /// Width of the ShaFlagsCols + const FLAGS_WIDTH: usize = Self::ROW_VAR_CNT + 6; + /// Width of the ShaWorkVarsCols + const WORK_VARS_WIDTH: usize = + 2 * Self::WORD_BITS * Self::ROUNDS_PER_ROW + 2 * Self::WORD_U16S * Self::ROUNDS_PER_ROW; + // Width of the ShaMessageHelperCols + const MESSAGE_HELPER_WIDTH: usize = + Self::WORD_U8S * (Self::ROUNDS_PER_ROW - 1) + 3 * Self::WORD_U16S * Self::ROUNDS_PER_ROW; + /// Width of the ShaMessageScheduleCols + const MESSAGE_SCHEDULE_WIDTH: usize = + Self::WORD_BITS * Self::ROUNDS_PER_ROW + Self::WORD_U8S * Self::ROUNDS_PER_ROW; + + /// Size of the buffer of the first 4 rows of a block (each row's size) + const BUFFER_SIZE: usize = Self::ROUNDS_PER_ROW * Self::WORD_U8S; + /// Width of the ShaCols + const WIDTH: usize = if Self::ROUND_WIDTH > Self::DIGEST_WIDTH { + Self::ROUND_WIDTH + } else { + Self::DIGEST_WIDTH + }; +} + +/// We can notice that `carry_a`'s and `carry_e`'s are always the same on invalid rows +/// To optimize the trace generation of invalid rows, we precompute those values. +/// This trait also stores the constants K and H for the given SHA config. +pub trait ShaPrecomputedValues { + // these should be appropriately sized for the config + fn get_invalid_carry_a(round_num: usize) -> &'static [u32]; + fn get_invalid_carry_e(round_num: usize) -> &'static [u32]; + fn get_k() -> &'static [T]; + fn get_h() -> &'static [T]; +} + +#[derive(Clone)] +pub struct Sha256Config; + +impl ShaConfig for Sha256Config { + // ==== Do not change these constants! ==== + type Word = u32; + /// Number of bits in a SHA256 word + const WORD_BITS: usize = 32; + /// Number of words in a SHA256 block + const BLOCK_WORDS: usize = 16; + /// Number of rows per block + const ROWS_PER_BLOCK: usize = 17; + /// Number of rounds per row + const ROUNDS_PER_ROW: usize = 4; + /// Number of rounds per block + const ROUNDS_PER_BLOCK: usize = 64; + /// Number of words in a SHA256 hash + const HASH_WORDS: usize = 8; + /// Number of vars needed to encode the row index with [Encoder] + const ROW_VAR_CNT: usize = 5; +} + +pub const SHA256_INVALID_CARRY_A: [[u32; Sha256Config::WORD_U16S]; Sha256Config::ROUNDS_PER_ROW] = [ + [1230919683, 1162494304], + [266373122, 1282901987], + [1519718403, 1008990871], + [923381762, 330807052], +]; +pub const SHA256_INVALID_CARRY_E: [[u32; Sha256Config::WORD_U16S]; Sha256Config::ROUNDS_PER_ROW] = [ + [204933122, 1994683449], + [443873282, 1544639095], + [719953922, 1888246508], + [194580482, 1075725211], +]; + +/// SHA256 constant K's +pub const SHA256_K: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; +/// SHA256 initial hash values +pub const SHA256_H: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; + +impl ShaPrecomputedValues for Sha256Config { + fn get_invalid_carry_a(round_num: usize) -> &'static [u32] { + &SHA256_INVALID_CARRY_A[round_num] + } + fn get_invalid_carry_e(round_num: usize) -> &'static [u32] { + &SHA256_INVALID_CARRY_E[round_num] + } + fn get_k() -> &'static [u32] { + &SHA256_K + } + fn get_h() -> &'static [u32] { + &SHA256_H + } +} + +#[derive(Clone)] +pub struct Sha512Config; + +impl ShaConfig for Sha512Config { + // ==== Do not change these constants! ==== + type Word = u64; + /// Number of bits in a SHA512 word + const WORD_BITS: usize = 64; + /// Number of words in a SHA512 block + const BLOCK_WORDS: usize = 16; + /// Number of rows per block + const ROWS_PER_BLOCK: usize = 21; + /// Number of rounds per row + const ROUNDS_PER_ROW: usize = 4; + /// Number of rounds per block + const ROUNDS_PER_BLOCK: usize = 80; + /// Number of words in a SHA512 hash + const HASH_WORDS: usize = 8; + /// Number of vars needed to encode the row index with [Encoder] + const ROW_VAR_CNT: usize = 6; +} + +pub(crate) const SHA512_INVALID_CARRY_A: [[u32; Sha512Config::WORD_U16S]; + Sha512Config::ROUNDS_PER_ROW] = [ + [55971842, 827997017, 993005918, 512731953], + [227512322, 1697529235, 1936430385, 940122990], + [1939875843, 1173318562, 826201586, 1513494849], + [891955202, 1732283693, 1736658755, 223514501], +]; + +pub(crate) const SHA512_INVALID_CARRY_E: [[u32; Sha512Config::WORD_U16S]; + Sha512Config::ROUNDS_PER_ROW] = [ + [1384427522, 1509509767, 153131516, 102514978], + [1527552003, 1041677071, 837289497, 843522538], + [775188482, 1620184630, 744892564, 892058728], + [1801267202, 1393118048, 1846108940, 830635531], +]; + +/// SHA512 constant K's +pub const SHA512_K: [u64; 80] = [ + 0x428a2f98d728ae22, + 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, + 0x59f111f1b605d019, + 0x923f82a4af194f9b, + 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, + 0x12835b0145706fbe, + 0x243185be4ee4b28c, + 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, + 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, + 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, + 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, + 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, + 0x76f988da831153b5, + 0x983e5152ee66dfab, + 0xa831c66d2db43210, + 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, + 0xd5a79147930aa725, + 0x06ca6351e003826f, + 0x142929670a0e6e70, + 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, + 0x650a73548baf63de, + 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, + 0x92722c851482353b, + 0xa2bfe8a14cf10364, + 0xa81a664bbc423001, + 0xc24b8b70d0f89791, + 0xc76c51a30654be30, + 0xd192e819d6ef5218, + 0xd69906245565a910, + 0xf40e35855771202a, + 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, + 0x1e376c085141ab53, + 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, + 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, + 0x78a5636f43172f60, + 0x84c87814a1f0ab72, + 0x8cc702081a6439ec, + 0x90befffa23631e28, + 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, + 0xca273eceea26619c, + 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, + 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, + 0x1b710b35131c471b, + 0x28db77f523047d84, + 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, + 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, + 0x6c44198c4a475817, +]; +/// SHA512 initial hash values +pub const SHA512_H: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +impl ShaPrecomputedValues for Sha512Config { + fn get_invalid_carry_a(round_num: usize) -> &'static [u32] { + &SHA512_INVALID_CARRY_A[round_num] + } + fn get_invalid_carry_e(round_num: usize) -> &'static [u32] { + &SHA512_INVALID_CARRY_E[round_num] + } + fn get_k() -> &'static [u64] { + &SHA512_K + } + fn get_h() -> &'static [u64] { + &SHA512_H + } +} + +// Needed to avoid compile errors in utils.rs +// not sure why this doesn't inf loop +pub trait RotateRight { + fn rotate_right(self, n: u32) -> Self; +} +impl RotateRight for u32 { + fn rotate_right(self, n: u32) -> Self { + self.rotate_right(n) + } +} +impl RotateRight for u64 { + fn rotate_right(self, n: u32) -> Self { + self.rotate_right(n) + } +} +pub trait WrappingAdd { + fn wrapping_add(self, n: Self) -> Self; +} +impl WrappingAdd for u32 { + fn wrapping_add(self, n: u32) -> Self { + self.wrapping_add(n) + } +} +impl WrappingAdd for u64 { + fn wrapping_add(self, n: u64) -> Self { + self.wrapping_add(n) + } +} diff --git a/crates/circuits/sha256-air/src/lib.rs b/crates/circuits/sha-air/src/lib.rs similarity index 65% rename from crates/circuits/sha256-air/src/lib.rs rename to crates/circuits/sha-air/src/lib.rs index 48bdaee5f9..7c7d095938 100644 --- a/crates/circuits/sha256-air/src/lib.rs +++ b/crates/circuits/sha-air/src/lib.rs @@ -1,13 +1,15 @@ -//! Implementation of the SHA256 compression function without padding +//! Implementation of the SHA256/SHA512 compression function without padding //! This this AIR doesn't constrain any of the message padding mod air; mod columns; +mod config; mod trace; mod utils; pub use air::*; pub use columns::*; +pub use config::*; pub use trace::*; pub use utils::*; diff --git a/crates/circuits/sha-air/src/tests.rs b/crates/circuits/sha-air/src/tests.rs new file mode 100644 index 0000000000..8dbf4d07a4 --- /dev/null +++ b/crates/circuits/sha-air/src/tests.rs @@ -0,0 +1,287 @@ +use std::{borrow::BorrowMut, cmp::max, sync::Arc}; +use crate::{ShaDigestColsRefMut, ShaRoundColsRefMut, ShaRoundColsRef}; + +use openvm_circuit::arch::{ + instructions::riscv::RV32_CELL_BITS, + testing::{VmChipTestBuilder, BITWISE_OP_LOOKUP_BUS}, +}; +use openvm_circuit_primitives::{ + bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip}, + SubAir, +}; +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + interaction::{BusIndex, InteractionBuilder}, + p3_air::{Air, BaseAir}, + p3_field::{Field, FieldAlgebra, PrimeField32}, + p3_maybe_rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}, + prover::types::AirProofInput, + rap::{get_air_name, BaseAirWithPublicValues, PartitionedBaseAir}, + AirRef, Chip, ChipUsageGetter, +}; +use openvm_stark_sdk::utils::create_seeded_rng; +use rand::Rng; + +use crate::{ + compose, small_sig0_field, Sha256Config, Sha512Config, ShaAir, ShaConfig, ShaPrecomputedValues, +}; + +// A wrapper AIR purely for testing purposes +#[derive(Clone, Debug)] +pub struct ShaTestAir> { + pub sub_air: ShaAir, +} + +impl> BaseAirWithPublicValues + for ShaTestAir +{ +} +impl> PartitionedBaseAir + for ShaTestAir +{ +} +impl> BaseAir for ShaTestAir { + fn width(&self) -> usize { + as BaseAir>::width(&self.sub_air) + } +} + +impl> Air + for ShaTestAir +{ + fn eval(&self, builder: &mut AB) { + self.sub_air.eval(builder, 0); + } +} + +// A wrapper Chip purely for testing purposes +pub struct ShaTestChip> { + pub air: ShaTestAir, + pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, + pub records: Vec<(Vec, bool)>, // length of inner vec is BLOCK_U8S +} + +impl + 'static> Chip + for ShaTestChip +where + Val: PrimeField32, +{ + fn air(&self) -> AirRef { + Arc::new(self.air.clone()) + } + + fn generate_air_proof_input(self) -> AirProofInput { + let trace = crate::generate_trace::, C>( + &self.air.sub_air, + self.bitwise_lookup_chip.clone(), + self.records, + ); + AirProofInput::simple_no_pis(trace) + } +} + +impl> ChipUsageGetter for ShaTestChip { + fn air_name(&self) -> String { + get_air_name(&self.air) + } + fn current_trace_height(&self) -> usize { + self.records.len() * C::ROWS_PER_BLOCK + } + + fn trace_width(&self) -> usize { + max(C::ROUND_WIDTH, C::DIGEST_WIDTH) + } +} + +const SELF_BUS_IDX: BusIndex = 28; +fn rand_sha_test + 'static>() { + let mut rng = create_seeded_rng(); + let tester = VmChipTestBuilder::default(); + let bitwise_bus = BitwiseOperationLookupBus::new(BITWISE_OP_LOOKUP_BUS); + let bitwise_chip = SharedBitwiseOperationLookupChip::::new(bitwise_bus); + let len = rng.gen_range(1..100); + let random_records: Vec<_> = (0..len) + .map(|i| { + ( + (0..C::BLOCK_U8S) + .map(|_| rng.gen::()) + .collect::>(), + rng.gen::() || i == len - 1, + ) + }) + .collect(); + let chip = ShaTestChip { + air: ShaTestAir { + sub_air: ShaAir::::new(bitwise_bus, SELF_BUS_IDX), + }, + bitwise_lookup_chip: bitwise_chip.clone(), + records: random_records, + }; + + let tester = tester.build().load(chip).load(bitwise_chip).finalize(); + tester.simple_test().expect("Verification failed"); +} + +#[test] +fn rand_sha256_test() { + rand_sha_test::(); +} + +#[test] +fn rand_sha512_test() { + rand_sha_test::(); +} + +// A wrapper Chip to test that the final_hash is properly constrained. +// This chip implements a malicious trace gen that violates the final_hash constraints. +pub struct ShaTestBadFinalHashChip> { + pub air: ShaTestAir, + pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, + pub records: Vec<(Vec, bool)>, // length of inner vec should be C::BLOCK_U8S +} + +impl + 'static> Chip + for ShaTestBadFinalHashChip +where + Val: PrimeField32, +{ + fn air(&self) -> AirRef { + Arc::new(self.air.clone()) + } + + fn generate_air_proof_input(self) -> AirProofInput { + let mut trace = crate::generate_trace::, C>( + &self.air.sub_air, + self.bitwise_lookup_chip.clone(), + self.records.clone(), + ); + + // Set the final_hash in the digest row of the last block of each hash to zero. + // That is, every hash that this chip does will result in a final_hash of zero. + for (i, row) in self.records.iter().enumerate() { + if row.1 { + let last_digest_row_idx = (i + 1) * C::ROWS_PER_BLOCK - 1; + let mut last_digest_row: crate::ShaDigestColsRefMut> = + ShaDigestColsRefMut::from::( + trace.row_mut(last_digest_row_idx)[..C::DIGEST_WIDTH].borrow_mut(), + ); + // Set the final_hash to all zeros + for i in 0..C::HASH_WORDS { + for j in 0..C::WORD_U8S { + last_digest_row.final_hash[[i, j]] = Val::::ZERO; + } + } + + let (last_round_row, last_digest_row) = + trace.row_pair_mut(last_digest_row_idx - 1, last_digest_row_idx); + let last_round_row: crate::ShaRoundColsRefMut> = + ShaRoundColsRefMut::from::(last_round_row.borrow_mut()); + let mut last_digest_row: crate::ShaRoundColsRefMut> = + ShaRoundColsRefMut::from::(last_digest_row.borrow_mut()); + // fix the intermed_4 for the digest row + generate_intermed_4::, C>(&ShaRoundColsRef::from_mut::(&last_round_row), &mut last_digest_row); + } + } + + let non_padded_height = self.records.len() * C::ROWS_PER_BLOCK; + let width = as BaseAir>>::width(&self.air.sub_air); + // recalculate the missing cells (second pass of generate_trace) + trace.values[width..] + .par_chunks_mut(width * C::ROWS_PER_BLOCK) + .take(non_padded_height / C::ROWS_PER_BLOCK) + .for_each(|chunk| { + self.air.sub_air.generate_missing_cells(chunk, width, 0); + }); + + AirProofInput::simple_no_pis(trace) + } +} + +// Copy of private method in Sha256Air used for testing +/// Puts the correct intermed_4 in the `next_row` +fn generate_intermed_4>( + local_cols: &ShaRoundColsRef, + next_cols: &mut ShaRoundColsRefMut, +) { + let w = [ + local_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + next_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + ] + .concat(); + + + // length of inner vec is C::WORD_U16S + let w_limbs: Vec> = w + .iter() + .map(|x| { + (0..C::WORD_U16S) + .map(|i| compose::(&x.as_slice().unwrap()[i * 16..(i + 1) * 16], 1)) + .collect::>() + }) + .collect(); + for i in 0..C::ROUNDS_PER_ROW { + let sig_w = small_sig0_field::(w[i + 1].as_slice().unwrap()); + let sig_w_limbs: Vec = (0..C::WORD_U16S) + .map(|j| compose::(&sig_w[j * 16..(j + 1) * 16], 1)) + .collect(); + for (j, sig_w_limb) in sig_w_limbs.iter().enumerate() { + next_cols.schedule_helper.intermed_4[[i, j]] = w_limbs[i][j] + *sig_w_limb; + } + } +} + +impl> ChipUsageGetter for ShaTestBadFinalHashChip { + fn air_name(&self) -> String { + get_air_name(&self.air) + } + fn current_trace_height(&self) -> usize { + self.records.len() * C::ROWS_PER_BLOCK + } + + fn trace_width(&self) -> usize { + max(C::ROUND_WIDTH, C::DIGEST_WIDTH) + } +} + +fn test_sha_final_hash_constraints + 'static>() { + let mut rng = create_seeded_rng(); + let tester = VmChipTestBuilder::default(); + let bitwise_bus = BitwiseOperationLookupBus::new(BITWISE_OP_LOOKUP_BUS); + let bitwise_chip = SharedBitwiseOperationLookupChip::::new(bitwise_bus); + let len = rng.gen_range(1..100); + let random_records: Vec<_> = (0..len) + .map(|_| ((0..C::BLOCK_U8S).map(|_| rng.gen::()).collect::>(), true)) + .collect(); + let chip = ShaTestBadFinalHashChip { + air: ShaTestAir { + sub_air: ShaAir::::new(bitwise_bus, SELF_BUS_IDX), + }, + bitwise_lookup_chip: bitwise_chip.clone(), + records: random_records, + }; + + let tester = tester.build().load(chip).load(bitwise_chip).finalize(); + tester.simple_test().expect("Verification failed"); +} + +#[test] +#[should_panic] +fn test_sha256_final_hash_constraints() { + test_sha_final_hash_constraints::(); +} + +#[test] +#[should_panic] +fn test_sha512_final_hash_constraints() { + test_sha_final_hash_constraints::(); +} diff --git a/crates/circuits/sha-air/src/trace.rs b/crates/circuits/sha-air/src/trace.rs new file mode 100644 index 0000000000..fe294a430f --- /dev/null +++ b/crates/circuits/sha-air/src/trace.rs @@ -0,0 +1,846 @@ +use std::ops::Range; + +use openvm_circuit_primitives::{ + bitwise_op_lookup::SharedBitwiseOperationLookupChip, utils::next_power_of_two_or_zero, +}; +use openvm_stark_backend::{ + p3_air::BaseAir, p3_field::PrimeField32, p3_matrix::dense::RowMajorMatrix, + p3_maybe_rayon::prelude::*, +}; +use sha2::{compress256, compress512, digest::generic_array::GenericArray}; + +use super::{ + air::ShaAir, big_sig0_field, big_sig1_field, ch_field, compose, get_flag_pt_array, maj_field, + small_sig0_field, small_sig1_field, ShaRoundColsRefMut, +}; +use crate::{ + big_sig0, big_sig1, ch, limbs_into_word, maj, small_sig0, small_sig1, word_into_bits, + word_into_u16_limbs, word_into_u8_limbs, ShaConfig, ShaDigestColsRefMut, ShaPrecomputedValues, + ShaRoundColsRef, WrappingAdd, +}; + +/// The trace generation of SHA should be done in two passes. +/// The first pass should do `get_block_trace` for every block and generate the invalid rows through `get_default_row` +/// The second pass should go through all the blocks and call `generate_missing_cells` +impl> ShaAir { + /// This function takes the input_message (padding not handled), the previous hash, + /// and returns the new hash after processing the block input + pub fn get_block_hash(prev_hash: &[C::Word], input: Vec) -> Vec { + debug_assert!(prev_hash.len() == C::HASH_WORDS); + debug_assert!(input.len() == C::BLOCK_U8S); + let mut new_hash: [C::Word; 8] = prev_hash.try_into().unwrap(); + if C::WORD_BITS == 32 { + let hash_ptr: &mut [u32; 8] = unsafe { std::mem::transmute(&mut new_hash) }; + let input_array = [*GenericArray::::from_slice( + &input, + )]; + compress256(hash_ptr, &input_array); + } else if C::WORD_BITS == 64 { + let hash_ptr: &mut [u64; 8] = unsafe { std::mem::transmute(&mut new_hash) }; + let input_array = [*GenericArray::::from_slice( + &input, + )]; + compress512(hash_ptr, &input_array); + } + new_hash.to_vec() + } + + /// This function takes a (C::WORD_BITS * C::BLOCK_WORDS)-bit chunk of the input message (padding not handled), the previous hash, + /// a flag indicating if it's the last block, the global block index, the local block index, + /// and the buffer values that will be put in rows 0..4. + /// Will populate the given `trace` with the trace of the block, where the width of the trace is `trace_width` + /// and the starting column for the `ShaAir` is `trace_start_col`. + /// **Note**: this function only generates some of the required trace. Another pass is required, refer to [`Self::generate_missing_cells`] for details. + #[allow(clippy::too_many_arguments)] + pub fn generate_block_trace( + &self, + trace: &mut [F], + trace_width: usize, + trace_start_col: usize, + input: &[C::Word], + bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, + prev_hash: &[C::Word], + is_last_block: bool, + global_block_idx: u32, + local_block_idx: u32, + buffer_vals: Vec<&[F]>, + ) { + #[cfg(debug_assertions)] + { + assert!(input.len() == C::BLOCK_WORDS); + assert!(prev_hash.len() == C::HASH_WORDS); + assert!(buffer_vals.len() == C::MESSAGE_ROWS); + assert!(buffer_vals.iter().all(|x| x.len() == C::BUFFER_SIZE)); + assert!(trace.len() == trace_width * C::ROWS_PER_BLOCK); + assert!(trace_start_col + C::WIDTH <= trace_width); + assert!(self.bitwise_lookup_bus == bitwise_lookup_chip.bus()); + if local_block_idx == 0 { + assert!(*prev_hash == *C::get_h()); + } + } + let get_range = |start: usize, len: usize| -> Range { start..start + len }; + let mut message_schedule = vec![C::Word::from(0); C::ROUNDS_PER_BLOCK]; + message_schedule[..input.len()].copy_from_slice(input); + let mut work_vars = prev_hash.to_vec(); + for (i, row) in trace.chunks_exact_mut(trace_width).enumerate() { + // do the rounds + if i < C::ROUND_ROWS { + let mut cols: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut row[get_range(trace_start_col, C::ROUND_WIDTH)], + ); + *cols.flags.is_round_row = F::ONE; + *cols.flags.is_first_4_rows = if i < 4 { F::ONE } else { F::ZERO }; + *cols.flags.is_digest_row = F::ZERO; + *cols.flags.is_last_block = F::from_bool(is_last_block); + cols.flags + .row_idx + .iter_mut() + .zip( + get_flag_pt_array(&self.row_idx_encoder, i) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + + *cols.flags.global_block_idx = F::from_canonical_u32(global_block_idx); + *cols.flags.local_block_idx = F::from_canonical_u32(local_block_idx); + + // W_idx = M_idx + if i < C::MESSAGE_ROWS { + for j in 0..C::ROUNDS_PER_ROW { + cols.message_schedule + .w + .row_mut(j) + .iter_mut() + .zip( + word_into_bits::(input[i * C::ROUNDS_PER_ROW + j]) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + cols.message_schedule + .carry_or_buffer + .row_mut(j) + .iter_mut() + .zip( + (0..C::WORD_U16S).map(|k| buffer_vals[i][j * C::WORD_U16S * 2 + k]), + ) + .for_each(|(x, y)| *x = y); + } + } + // W_idx = SIG1(W_{idx-2}) + W_{idx-7} + SIG0(W_{idx-15}) + W_{idx-16} + else { + for j in 0..C::ROUNDS_PER_ROW { + let idx = i * C::ROUNDS_PER_ROW + j; + let nums: [C::Word; 4] = [ + small_sig1::(message_schedule[idx - 2]), + message_schedule[idx - 7], + small_sig0::(message_schedule[idx - 15]), + message_schedule[idx - 16], + ]; + let w: C::Word = nums + .iter() + .fold(C::Word::from(0), |acc, &num| acc.wrapping_add(num)); + cols.message_schedule + .w + .row_mut(j) + .iter_mut() + .zip( + word_into_bits::(w) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + + let nums_limbs = nums + .iter() + .map(|x| word_into_u16_limbs::(*x)) + .collect::>(); + let w_limbs = word_into_u16_limbs::(w); + + // fill in the carrys + for k in 0..C::WORD_U16S { + let mut sum = nums_limbs.iter().fold(0, |acc, num| acc + num[k]); + if k > 0 { + sum += (cols.message_schedule.carry_or_buffer[[j, k * 2 - 2]] + + F::TWO + * cols.message_schedule.carry_or_buffer[[j, k * 2 - 1]]) + .as_canonical_u32(); + } + let carry = (sum - w_limbs[k]) >> 16; + cols.message_schedule.carry_or_buffer[[j, k * 2]] = + F::from_canonical_u32(carry & 1); + cols.message_schedule.carry_or_buffer[[j, k * 2 + 1]] = + F::from_canonical_u32(carry >> 1); + } + // update the message schedule + message_schedule[idx] = w; + } + } + // fill in the work variables + for j in 0..C::ROUNDS_PER_ROW { + // t1 = h + SIG1(e) + ch(e, f, g) + K_idx + W_idx + let t1 = [ + work_vars[7], + big_sig1::(work_vars[4]), + ch::(work_vars[4], work_vars[5], work_vars[6]), + C::get_k()[i * C::ROUNDS_PER_ROW + j], + limbs_into_word::( + cols.message_schedule + .w + .row(j) + .map(|f| f.as_canonical_u32()) + .as_slice() + .unwrap(), + ), + ]; + let t1_sum: C::Word = t1 + .iter() + .fold(C::Word::from(0), |acc, &num| acc.wrapping_add(num)); + + // t2 = SIG0(a) + maj(a, b, c) + let t2 = [ + big_sig0::(work_vars[0]), + maj::(work_vars[0], work_vars[1], work_vars[2]), + ]; + + let t2_sum: C::Word = t2 + .iter() + .fold(C::Word::from(0), |acc, &num| acc.wrapping_add(num)); + + // e = d + t1 + let e = work_vars[3].wrapping_add(t1_sum); + cols.work_vars + .e + .row_mut(j) + .iter_mut() + .zip( + word_into_bits::(e) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + let e_limbs = word_into_u16_limbs::(e); + // a = t1 + t2 + let a = t1_sum.wrapping_add(t2_sum); + cols.work_vars + .a + .row_mut(j) + .iter_mut() + .zip( + word_into_bits::(a) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + let a_limbs = word_into_u16_limbs::(a); + // fill in the carrys + for k in 0..C::WORD_U16S { + let t1_limb = t1 + .iter() + .fold(0, |acc, &num| acc + word_into_u16_limbs::(num)[k]); + let t2_limb = t2 + .iter() + .fold(0, |acc, &num| acc + word_into_u16_limbs::(num)[k]); + + let mut e_limb = t1_limb + word_into_u16_limbs::(work_vars[3])[k]; + let mut a_limb = t1_limb + t2_limb; + if k > 0 { + a_limb += cols.work_vars.carry_a[[j, k - 1]].as_canonical_u32(); + e_limb += cols.work_vars.carry_e[[j, k - 1]].as_canonical_u32(); + } + let carry_a = (a_limb - a_limbs[k]) >> 16; + let carry_e = (e_limb - e_limbs[k]) >> 16; + cols.work_vars.carry_a[[j, k]] = F::from_canonical_u32(carry_a); + cols.work_vars.carry_e[[j, k]] = F::from_canonical_u32(carry_e); + bitwise_lookup_chip.request_range(carry_a, carry_e); + } + + // update working variables + work_vars[7] = work_vars[6]; + work_vars[6] = work_vars[5]; + work_vars[5] = work_vars[4]; + work_vars[4] = e; + work_vars[3] = work_vars[2]; + work_vars[2] = work_vars[1]; + work_vars[1] = work_vars[0]; + work_vars[0] = a; + } + + // filling w_3 and intermed_4 here and the rest later + if i > 0 { + for j in 0..C::ROUNDS_PER_ROW { + let idx = i * C::ROUNDS_PER_ROW + j; + let w_4 = word_into_u16_limbs::(message_schedule[idx - 4]); + let sig_0_w_3 = + word_into_u16_limbs::(small_sig0::(message_schedule[idx - 3])); + cols.schedule_helper + .intermed_4 + .row_mut(j) + .iter_mut() + .zip( + (0..C::WORD_U16S) + .map(|k| F::from_canonical_u32(w_4[k] + sig_0_w_3[k])) + .collect::>(), + ) + .for_each(|(x, y)| *x = y); + if j < C::ROUNDS_PER_ROW - 1 { + let w_3 = message_schedule[idx - 3]; + cols.schedule_helper + .w_3 + .row_mut(j) + .iter_mut() + .zip( + word_into_u16_limbs::(w_3) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + } + } + } + } + // generate the digest row + else { + let mut cols: ShaDigestColsRefMut = ShaDigestColsRefMut::from::( + &mut row[get_range(trace_start_col, C::DIGEST_WIDTH)], + ); + for j in 0..C::ROUNDS_PER_ROW - 1 { + let w_3 = message_schedule[i * C::ROUNDS_PER_ROW + j - 3]; + cols.schedule_helper + .w_3 + .row_mut(j) + .iter_mut() + .zip( + word_into_u16_limbs::(w_3) + .into_iter() + .map(F::from_canonical_u32) + .collect::>(), + ) + .for_each(|(x, y)| *x = y); + } + *cols.flags.is_round_row = F::ZERO; + *cols.flags.is_first_4_rows = F::ZERO; + *cols.flags.is_digest_row = F::ONE; + *cols.flags.is_last_block = F::from_bool(is_last_block); + cols.flags + .row_idx + .iter_mut() + .zip( + get_flag_pt_array(&self.row_idx_encoder, C::ROUND_ROWS) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + + *cols.flags.global_block_idx = F::from_canonical_u32(global_block_idx); + + *cols.flags.local_block_idx = F::from_canonical_u32(local_block_idx); + let final_hash: Vec = (0..C::HASH_WORDS) + .map(|i| work_vars[i].wrapping_add(prev_hash[i])) + .collect(); + let final_hash_limbs: Vec> = final_hash + .iter() + .map(|word| word_into_u8_limbs::(*word)) + .collect(); + // need to ensure final hash limbs are bytes, in order for + // prev_hash[i] + work_vars[i] == final_hash[i] + // to be constrained correctly + for word in final_hash_limbs.iter() { + for chunk in word.chunks(2) { + bitwise_lookup_chip.request_range(chunk[0], chunk[1]); + } + } + cols.final_hash + .iter_mut() + .zip((0..C::HASH_WORDS).flat_map(|i| { + word_into_u8_limbs::(final_hash[i]) + .into_iter() + .map(F::from_canonical_u32) + .collect::>() + })) + .for_each(|(x, y)| *x = y); + cols.prev_hash + .iter_mut() + .zip(prev_hash.iter().flat_map(|f| { + word_into_u16_limbs::(*f) + .into_iter() + .map(F::from_canonical_u32) + .collect::>() + })) + .for_each(|(x, y)| *x = y); + + let hash = if is_last_block { + C::get_h() + .iter() + .map(|x| word_into_bits::(*x)) + .collect::>() + } else { + cols.final_hash + .rows_mut() + .into_iter() + .map(|f| { + limbs_into_word::( + f.map(|x| x.as_canonical_u32()).as_slice().unwrap(), + ) + }) + .map(word_into_bits::) + .collect() + } + .into_iter() + .map(|x| x.into_iter().map(F::from_canonical_u32)) + .collect::>(); + + for i in 0..C::ROUNDS_PER_ROW { + cols.hash + .a + .row_mut(i) + .iter_mut() + .zip(hash[C::ROUNDS_PER_ROW - i - 1].clone()) + .for_each(|(x, y)| *x = y); + cols.hash + .e + .row_mut(i) + .iter_mut() + .zip(hash[C::ROUNDS_PER_ROW - i + 3].clone()) + .for_each(|(x, y)| *x = y); + } + } + } + + for i in 0..C::ROWS_PER_BLOCK - 1 { + let rows = &mut trace[i * trace_width..(i + 2) * trace_width]; + let (local, next) = rows.split_at_mut(trace_width); + let mut local_cols: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut local[get_range(trace_start_col, C::ROUND_WIDTH)], + ); + let mut next_cols: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut next[get_range(trace_start_col, C::ROUND_WIDTH)], + ); + if i > 0 { + for j in 0..C::ROUNDS_PER_ROW { + next_cols + .schedule_helper + .intermed_8 + .row_mut(j) + .assign(&local_cols.schedule_helper.intermed_4.row(j)); + if (2..C::ROWS_PER_BLOCK - 3).contains(&i) { + next_cols + .schedule_helper + .intermed_12 + .row_mut(j) + .assign(&local_cols.schedule_helper.intermed_8.row(j)); + } + } + } + if i == C::ROWS_PER_BLOCK - 2 { + // `next` is a digest row. + // Fill in `carry_a` and `carry_e` with dummy values so the constraints on `a` and `e` hold. + let const_local_cols = ShaRoundColsRef::::from_mut::(&local_cols); + Self::generate_carry_ae(const_local_cols.clone(), &mut next_cols); + // Fill in row 16's `intermed_4` with dummy values so the message schedule constraints holds on that row + Self::generate_intermed_4(const_local_cols, &mut next_cols); + } + if i <= 2 { + // i is in 0..3. + // Fill in `local.intermed_12` with dummy values so the message schedule constraints hold on rows 1..4. + Self::generate_intermed_12( + &mut local_cols, + ShaRoundColsRef::::from_mut::(&next_cols), + ); + } + } + } + + /// This function will fill in the cells that we couldn't do during the first pass. + /// This function should be called only after `generate_block_trace` was called for all blocks + /// And [`Self::generate_default_row`] is called for all invalid rows + /// Will populate the missing values of `trace`, where the width of the trace is `trace_width` + /// and the starting column for the `ShaAir` is `trace_start_col`. + /// Note: `trace` needs to be the rows 1..C::ROWS_PER_BLOCK of a block and the first row of the next block + pub fn generate_missing_cells( + &self, + trace: &mut [F], + trace_width: usize, + trace_start_col: usize, + ) { + let rows = &mut trace[(C::ROUND_ROWS - 2) * trace_width..(C::ROUND_ROWS + 1) * trace_width]; + let (last_round_row, rows) = rows.split_at_mut(trace_width); + let (digest_row, next_block_first_row) = rows.split_at_mut(trace_width); + let mut cols_last_round_row: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut last_round_row[trace_start_col..trace_start_col + C::ROUND_WIDTH], + ); + let mut cols_digest_row: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut digest_row[trace_start_col..trace_start_col + C::ROUND_WIDTH], + ); + let mut cols_next_block_first_row: ShaRoundColsRefMut = ShaRoundColsRefMut::from::( + &mut next_block_first_row[trace_start_col..trace_start_col + C::ROUND_WIDTH], + ); + // Fill in the last round row's `intermed_12` with dummy values so the message schedule constraints holds on row 16 + Self::generate_intermed_12( + &mut cols_last_round_row, + ShaRoundColsRef::from_mut::(&cols_digest_row), + ); + // Fill in the digest row's `intermed_12` with dummy values so the message schedule constraints holds on the next block's row 0 + Self::generate_intermed_12( + &mut cols_digest_row, + ShaRoundColsRef::from_mut::(&cols_next_block_first_row), + ); + // Fill in the next block's first row's `intermed_4` with dummy values so the message schedule constraints holds on that row + Self::generate_intermed_4( + ShaRoundColsRef::from_mut::(&cols_digest_row), + &mut cols_next_block_first_row, + ); + } + + /// Fills the `cols` as a padding row + /// Note: we still need to correctly fill in the hash values, carries and intermeds + pub fn generate_default_row(&self, mut cols: ShaRoundColsRefMut) { + *cols.flags.is_round_row = F::ZERO; + *cols.flags.is_first_4_rows = F::ZERO; + *cols.flags.is_digest_row = F::ZERO; + + *cols.flags.is_last_block = F::ZERO; + *cols.flags.global_block_idx = F::ZERO; + cols.flags + .row_idx + .iter_mut() + .zip( + get_flag_pt_array(&self.row_idx_encoder, C::ROWS_PER_BLOCK) + .into_iter() + .map(F::from_canonical_u32), + ) + .for_each(|(x, y)| *x = y); + *cols.flags.local_block_idx = F::ZERO; + + cols.message_schedule + .w + .iter_mut() + .for_each(|x| *x = F::ZERO); + cols.message_schedule + .carry_or_buffer + .iter_mut() + .for_each(|x| *x = F::ZERO); + + let hash = C::get_h() + .iter() + .map(|x| word_into_bits::(*x)) + .map(|x| x.into_iter().map(F::from_canonical_u32).collect::>()) + .collect::>(); + + for i in 0..C::ROUNDS_PER_ROW { + cols.work_vars + .a + .row_mut(i) + .iter_mut() + .zip(hash[C::ROUNDS_PER_ROW - i - 1].clone()) + .for_each(|(x, y)| *x = y); + cols.work_vars + .e + .row_mut(i) + .iter_mut() + .zip(hash[C::ROUNDS_PER_ROW - i + 3].clone()) + .for_each(|(x, y)| *x = y); + } + + cols.work_vars + .carry_a + .iter_mut() + .zip((0..C::ROUNDS_PER_ROW).flat_map(|i| { + (0..C::WORD_U16S) + .map(|j| F::from_canonical_u32(C::get_invalid_carry_a(i)[j])) + .collect::>() + })) + .for_each(|(x, y)| *x = y); + cols.work_vars + .carry_e + .iter_mut() + .zip((0..C::ROUNDS_PER_ROW).flat_map(|i| { + (0..C::WORD_U16S) + .map(|j| F::from_canonical_u32(C::get_invalid_carry_e(i)[j])) + .collect::>() + })) + .for_each(|(x, y)| *x = y); + } + + /// The following functions do the calculations in native field since they will be called on padding rows + /// which can overflow and we need to make sure it matches the AIR constraints + /// Puts the correct carrys in the `next_row`, the resulting carrys can be out of bound + pub fn generate_carry_ae( + local_cols: ShaRoundColsRef, + next_cols: &mut ShaRoundColsRefMut, + ) { + let a = [ + local_cols + .work_vars + .a + .rows() + .into_iter() + .collect::>(), + next_cols.work_vars.a.rows().into_iter().collect::>(), + ] + .concat(); + let e = [ + local_cols + .work_vars + .e + .rows() + .into_iter() + .collect::>(), + next_cols.work_vars.e.rows().into_iter().collect::>(), + ] + .concat(); + for i in 0..C::ROUNDS_PER_ROW { + let cur_a = a[i + 4]; + let sig_a = big_sig0_field::(a[i + 3].as_slice().unwrap()); + let maj_abc = maj_field::( + a[i + 3].as_slice().unwrap(), + a[i + 2].as_slice().unwrap(), + a[i + 1].as_slice().unwrap(), + ); + let d = a[i]; + let cur_e = e[i + 4]; + let sig_e = big_sig1_field::(e[i + 3].as_slice().unwrap()); + let ch_efg = ch_field::( + e[i + 3].as_slice().unwrap(), + e[i + 2].as_slice().unwrap(), + e[i + 1].as_slice().unwrap(), + ); + let h = e[i]; + + let t1 = [h.to_vec(), sig_e, ch_efg.to_vec()]; + let t2 = [sig_a, maj_abc]; + for j in 0..C::WORD_U16S { + let t1_limb_sum = t1.iter().fold(F::ZERO, |acc, x| { + acc + compose::(&x[j * 16..(j + 1) * 16], 1) + }); + let t2_limb_sum = t2.iter().fold(F::ZERO, |acc, x| { + acc + compose::(&x[j * 16..(j + 1) * 16], 1) + }); + let d_limb = compose::(&d.as_slice().unwrap()[j * 16..(j + 1) * 16], 1); + let cur_a_limb = compose::(&cur_a.as_slice().unwrap()[j * 16..(j + 1) * 16], 1); + let cur_e_limb = compose::(&cur_e.as_slice().unwrap()[j * 16..(j + 1) * 16], 1); + let sum = d_limb + + t1_limb_sum + + if j == 0 { + F::ZERO + } else { + next_cols.work_vars.carry_e[[i, j - 1]] + } + - cur_e_limb; + let carry_e = sum * (F::from_canonical_u32(1 << 16).inverse()); + + let sum = t1_limb_sum + + t2_limb_sum + + if j == 0 { + F::ZERO + } else { + next_cols.work_vars.carry_a[[i, j - 1]] + } + - cur_a_limb; + let carry_a = sum * (F::from_canonical_u32(1 << 16).inverse()); + next_cols.work_vars.carry_e[[i, j]] = carry_e; + next_cols.work_vars.carry_a[[i, j]] = carry_a; + } + } + } + + /// Puts the correct intermed_4 in the `next_row` + fn generate_intermed_4( + local_cols: ShaRoundColsRef, + next_cols: &mut ShaRoundColsRefMut, + ) { + let w = [ + local_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + next_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + ] + .concat(); + let w_limbs: Vec> = w + .iter() + .map(|x| { + (0..C::WORD_U16S) + .map(|i| compose::(&x.as_slice().unwrap()[i * 16..(i + 1) * 16], 1)) + .collect::>() + }) + .collect(); + for i in 0..C::ROUNDS_PER_ROW { + let sig_w = small_sig0_field::(w[i + 1].as_slice().unwrap()); + let sig_w_limbs: Vec = (0..C::WORD_U16S) + .map(|j| compose::(&sig_w[j * 16..(j + 1) * 16], 1)) + .collect(); + for (j, sig_w_limb) in sig_w_limbs.iter().enumerate() { + next_cols.schedule_helper.intermed_4[[i, j]] = w_limbs[i][j] + *sig_w_limb; + } + } + } + + /// Puts the needed intermed_12 in the `local_row` + fn generate_intermed_12( + local_cols: &mut ShaRoundColsRefMut, + next_cols: ShaRoundColsRef, + ) { + let w = [ + local_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + next_cols + .message_schedule + .w + .rows() + .into_iter() + .collect::>(), + ] + .concat(); + let w_limbs: Vec> = w + .iter() + .map(|x| { + (0..C::WORD_U16S) + .map(|i| compose::(&x.as_slice().unwrap()[i * 16..(i + 1) * 16], 1)) + .collect::>() + }) + .collect(); + for i in 0..C::ROUNDS_PER_ROW { + // sig_1(w_{t-2}) + let sig_w_2: Vec = (0..C::WORD_U16S) + .map(|j| { + compose::( + &small_sig1_field::(w[i + 2].as_slice().unwrap()) + [j * 16..(j + 1) * 16], + 1, + ) + }) + .collect(); + // w_{t-7} + let w_7 = if i < 3 { + local_cols.schedule_helper.w_3.row(i).to_slice().unwrap() + } else { + w_limbs[i - 3].as_slice() + }; + // w_t + let w_cur = w_limbs[i + 4].as_slice(); + for j in 0..C::WORD_U16S { + let carry = next_cols.message_schedule.carry_or_buffer[[i, j * 2]] + + F::TWO * next_cols.message_schedule.carry_or_buffer[[i, j * 2 + 1]]; + let sum = sig_w_2[j] + w_7[j] - carry * F::from_canonical_u32(1 << 16) - w_cur[j] + + if j > 0 { + next_cols.message_schedule.carry_or_buffer[[i, j * 2 - 2]] + + F::from_canonical_u32(2) + * next_cols.message_schedule.carry_or_buffer[[i, j * 2 - 1]] + } else { + F::ZERO + }; + local_cols.schedule_helper.intermed_12[[i, j]] = -sum; + } + } + } +} + +/// `records` consists of pairs of `(input_block, is_last_block)`. +pub fn generate_trace>( + sub_air: &ShaAir, + bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, + records: Vec<(Vec, bool)>, +) -> RowMajorMatrix { + for (input, _) in &records { + debug_assert!(input.len() == C::BLOCK_U8S); + } + + let non_padded_height = records.len() * C::ROWS_PER_BLOCK; + let height = next_power_of_two_or_zero(non_padded_height); + let width = as BaseAir>::width(sub_air); + let mut values = F::zero_vec(height * width); + + struct BlockContext { + prev_hash: Vec, // len is C::HASH_WORDS + local_block_idx: u32, + global_block_idx: u32, + input: Vec, // len is C::BLOCK_U8S + is_last_block: bool, + } + let mut block_ctx: Vec> = Vec::with_capacity(records.len()); + let mut prev_hash = C::get_h().to_vec(); + let mut local_block_idx = 0; + let mut global_block_idx = 1; + for (input, is_last_block) in records { + block_ctx.push(BlockContext { + prev_hash: prev_hash.clone(), + local_block_idx, + global_block_idx, + input: input.clone(), + is_last_block, + }); + global_block_idx += 1; + if is_last_block { + local_block_idx = 0; + prev_hash = C::get_h().to_vec(); + } else { + local_block_idx += 1; + prev_hash = ShaAir::::get_block_hash(&prev_hash, input); + } + } + // first pass + values + .par_chunks_exact_mut(width * C::ROWS_PER_BLOCK) + .zip(block_ctx) + .for_each(|(block, ctx)| { + let BlockContext { + prev_hash, + local_block_idx, + global_block_idx, + input, + is_last_block, + } = ctx; + let input_words = (0..C::BLOCK_WORDS) + .map(|i| { + limbs_into_word::( + &(0..C::WORD_U8S) + .map(|j| input[(i + 1) * C::WORD_U8S - j - 1] as u32) + .collect::>(), + ) + }) + .collect::>(); + let empty_buffer = vec![F::ZERO; C::BUFFER_SIZE]; + let buffer_vals = vec![empty_buffer.as_slice(); C::MESSAGE_ROWS]; + sub_air.generate_block_trace( + block, + width, + 0, + &input_words, + bitwise_lookup_chip.clone(), + &prev_hash, + is_last_block, + global_block_idx, + local_block_idx, + buffer_vals, + ); + }); + // second pass: padding rows + values[width * non_padded_height..] + .par_chunks_mut(width) + .for_each(|row| { + let cols: ShaRoundColsRefMut = ShaRoundColsRefMut::from::(row); + sub_air.generate_default_row(cols); + }); + + // second pass: non-padding rows + values[width..] + .par_chunks_mut(width * C::ROWS_PER_BLOCK) + .take(non_padded_height / C::ROWS_PER_BLOCK) + .for_each(|chunk| { + sub_air.generate_missing_cells(chunk, width, 0); + }); + RowMajorMatrix::new(values, width) +} diff --git a/crates/circuits/sha-air/src/utils.rs b/crates/circuits/sha-air/src/utils.rs new file mode 100644 index 0000000000..417e741c09 --- /dev/null +++ b/crates/circuits/sha-air/src/utils.rs @@ -0,0 +1,282 @@ +pub use openvm_circuit_primitives::utils::compose; +use openvm_circuit_primitives::{ + encoder::Encoder, + utils::{not, select}, +}; +use openvm_stark_backend::{p3_air::AirBuilder, p3_field::FieldAlgebra}; +use rand::{rngs::StdRng, Rng}; + +use crate::{RotateRight, ShaConfig}; + +/// Convert a word into a list of 8-bit limbs in little endian +pub fn word_into_u8_limbs(num: impl Into) -> Vec { + word_into_limbs::(num.into(), C::WORD_U8S) +} + +/// Convert a word into a list of 16-bit limbs in little endian +pub fn word_into_u16_limbs(num: impl Into) -> Vec { + word_into_limbs::(num.into(), C::WORD_U16S) +} + +/// Convert a word into a list of 1-bit limbs in little endian +pub fn word_into_bits(num: impl Into) -> Vec { + word_into_limbs::(num.into(), C::WORD_BITS) +} + +/// Convert a word into a list of limbs in little endian +pub fn word_into_limbs(num: C::Word, num_limbs: usize) -> Vec { + let limb_bits = std::mem::size_of::() * 8 / num_limbs; + (0..num_limbs) + .map(|i| { + let shifted = num >> (limb_bits * i); + let mask: C::Word = ((1u32 << limb_bits) - 1).into(); + let masked = shifted & mask; + masked.try_into().unwrap() + }) + .collect() +} + +/// Convert a u32 into a list of 1-bit limbs in little endian +pub fn u32_into_bits(num: u32) -> Vec { + let limb_bits = 32 / C::WORD_BITS; + (0..C::WORD_BITS) + .map(|i| (num >> (limb_bits * i)) & ((1 << limb_bits) - 1)) + .collect() +} + +/// Convert a list of limbs in little endian into a Word +pub fn limbs_into_word(limbs: &[u32]) -> C::Word { + let limb_bits = C::WORD_BITS / limbs.len(); + limbs.iter().rev().fold(C::Word::from(0), |acc, &limb| { + (acc << limb_bits) | limb.into() + }) +} + +/// Convert a list of limbs in little endian into a u32 +pub fn limbs_into_u32(limbs: &[u32]) -> u32 { + let limb_bits = 32 / limbs.len(); + limbs + .iter() + .rev() + .fold(0, |acc, &limb| (acc << limb_bits) | limb) +} + +/// Rotates `bits` right by `n` bits, assumes `bits` is in little-endian +#[inline] +pub(crate) fn rotr(bits: &[impl Into + Clone], n: usize) -> Vec { + (0..bits.len()) + .map(|i| bits[(i + n) % bits.len()].clone().into()) + .collect() +} + +/// Shifts `bits` right by `n` bits, assumes `bits` is in little-endian +#[inline] +pub(crate) fn shr(bits: &[impl Into + Clone], n: usize) -> Vec { + (0..bits.len()) + .map(|i| { + if i + n < bits.len() { + bits[i + n].clone().into() + } else { + F::ZERO + } + }) + .collect() +} + +/// Computes x ^ y ^ z, where x, y, z are assumed to be boolean +#[inline] +pub(crate) fn xor_bit( + x: impl Into, + y: impl Into, + z: impl Into, +) -> F { + let (x, y, z) = (x.into(), y.into(), z.into()); + (x.clone() * y.clone() * z.clone()) + + (x.clone() * not::(y.clone()) * not::(z.clone())) + + (not::(x.clone()) * y.clone() * not::(z.clone())) + + (not::(x) * not::(y) * z) +} + +/// Computes x ^ y ^ z, where x, y, z are [C::WORD_BITS] bit numbers +#[inline] +pub(crate) fn xor( + x: &[impl Into + Clone], + y: &[impl Into + Clone], + z: &[impl Into + Clone], +) -> Vec { + (0..x.len()) + .map(|i| xor_bit(x[i].clone(), y[i].clone(), z[i].clone())) + .collect() +} + +/// Choose function from the SHA spec +#[inline] +pub fn ch(x: C::Word, y: C::Word, z: C::Word) -> C::Word { + (x & y) ^ ((!x) & z) +} + +/// Computes Ch(x,y,z), where x, y, z are [C::WORD_BITS] bit numbers +#[inline] +pub(crate) fn ch_field( + x: &[impl Into + Clone], + y: &[impl Into + Clone], + z: &[impl Into + Clone], +) -> Vec { + (0..x.len()) + .map(|i| select(x[i].clone(), y[i].clone(), z[i].clone())) + .collect() +} + +/// Majority function from the SHA spec +pub fn maj(x: C::Word, y: C::Word, z: C::Word) -> C::Word { + (x & y) ^ (x & z) ^ (y & z) +} + +/// Computes Maj(x,y,z), where x, y, z are [C::WORD_BITS] bit numbers +#[inline] +pub(crate) fn maj_field( + x: &[impl Into + Clone], + y: &[impl Into + Clone], + z: &[impl Into + Clone], +) -> Vec { + (0..x.len()) + .map(|i| { + let (x, y, z) = ( + x[i].clone().into(), + y[i].clone().into(), + z[i].clone().into(), + ); + x.clone() * y.clone() + x.clone() * z.clone() + y.clone() * z.clone() + - F::TWO * x * y * z + }) + .collect() +} + +/// Big sigma_0 function from the SHA spec +pub fn big_sig0(x: C::Word) -> C::Word { + if C::WORD_BITS == 32 { + x.rotate_right(2) ^ x.rotate_right(13) ^ x.rotate_right(22) + } else { + x.rotate_right(28) ^ x.rotate_right(34) ^ x.rotate_right(39) + } +} + +/// Computes BigSigma0(x), where x is a [C::WORD_BITS] bit number in little-endian +#[inline] +pub(crate) fn big_sig0_field( + x: &[impl Into + Clone], +) -> Vec { + if C::WORD_BITS == 32 { + xor(&rotr::(x, 2), &rotr::(x, 13), &rotr::(x, 22)) + } else { + xor(&rotr::(x, 28), &rotr::(x, 34), &rotr::(x, 39)) + } +} + +/// Big sigma_1 function from the SHA spec +pub fn big_sig1(x: C::Word) -> C::Word { + if C::WORD_BITS == 32 { + x.rotate_right(6) ^ x.rotate_right(11) ^ x.rotate_right(25) + } else { + x.rotate_right(14) ^ x.rotate_right(18) ^ x.rotate_right(41) + } +} + +/// Computes BigSigma1(x), where x is a [C::WORD_BITS] bit number in little-endian +#[inline] +pub(crate) fn big_sig1_field( + x: &[impl Into + Clone], +) -> Vec { + if C::WORD_BITS == 32 { + xor(&rotr::(x, 6), &rotr::(x, 11), &rotr::(x, 25)) + } else { + xor(&rotr::(x, 14), &rotr::(x, 18), &rotr::(x, 41)) + } +} + +/// Small sigma_0 function from the SHA spec +pub fn small_sig0(x: C::Word) -> C::Word { + if C::WORD_BITS == 32 { + x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) + } else { + x.rotate_right(1) ^ x.rotate_right(8) ^ (x >> 7) + } +} + +/// Computes SmallSigma0(x), where x is a [C::WORD_BITS] bit number in little-endian +#[inline] +pub(crate) fn small_sig0_field( + x: &[impl Into + Clone], +) -> Vec { + if C::WORD_BITS == 32 { + xor(&rotr::(x, 7), &rotr::(x, 18), &shr::(x, 3)) + } else { + xor(&rotr::(x, 1), &rotr::(x, 8), &shr::(x, 7)) + } +} + +/// Small sigma_1 function from the SHA spec +pub fn small_sig1(x: C::Word) -> C::Word { + if C::WORD_BITS == 32 { + x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) + } else { + x.rotate_right(19) ^ x.rotate_right(61) ^ (x >> 6) + } +} + +/// Computes SmallSigma1(x), where x is a [C::WORD_BITS] bit number in little-endian +#[inline] +pub(crate) fn small_sig1_field( + x: &[impl Into + Clone], +) -> Vec { + if C::WORD_BITS == 32 { + xor(&rotr::(x, 17), &rotr::(x, 19), &shr::(x, 10)) + } else { + xor(&rotr::(x, 19), &rotr::(x, 61), &shr::(x, 6)) + } +} + +/// Generate a random message of a given length +pub fn get_random_message(rng: &mut StdRng, len: usize) -> Vec { + let mut random_message: Vec = vec![0u8; len]; + rng.fill(&mut random_message[..]); + random_message +} + +/// Wrapper of `get_flag_pt` to get the flag pointer as an array +pub fn get_flag_pt_array(encoder: &Encoder, flag_idx: usize) -> Vec { + encoder.get_flag_pt(flag_idx) +} + +/// Constrain the addition of [C::WORD_BITS] bit words in 16-bit limbs +/// It takes in the terms some in bits some in 16-bit limbs, +/// the expected sum in bits and the carries +pub fn constraint_word_addition( + builder: &mut AB, + terms_bits: &[&[impl Into + Clone]], + terms_limb: &[&[impl Into + Clone]], + expected_sum: &[impl Into + Clone], + carries: &[impl Into + Clone], +) { + debug_assert!(terms_bits.iter().all(|x| x.len() == C::WORD_BITS)); + debug_assert!(terms_limb.iter().all(|x| x.len() == C::WORD_U16S)); + assert_eq!(expected_sum.len(), C::WORD_BITS); + assert_eq!(carries.len(), C::WORD_U16S); + + for i in 0..C::WORD_U16S { + let mut limb_sum = if i == 0 { + AB::Expr::ZERO + } else { + carries[i - 1].clone().into() + }; + for term in terms_bits { + limb_sum += compose::(&term[i * 16..(i + 1) * 16], 1); + } + for term in terms_limb { + limb_sum += term[i].clone().into(); + } + let expected_sum_limb = compose::(&expected_sum[i * 16..(i + 1) * 16], 1) + + carries[i].clone().into() * AB::Expr::from_canonical_u32(1 << 16); + builder.assert_eq(limb_sum, expected_sum_limb); + } +} diff --git a/crates/circuits/sha-macros/Cargo.toml b/crates/circuits/sha-macros/Cargo.toml new file mode 100644 index 0000000000..71ab68feeb --- /dev/null +++ b/crates/circuits/sha-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "openvm-sha-macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +syn = { version = "2.0", features = ["full", "extra-traits"] } +quote = "1.0" +itertools = "0.14" +proc-macro2 = "1.0" + +[dev-dependencies] +openvm-sha-air = { workspace = true } +ndarray = "0.16" + +[lib] +proc-macro = true diff --git a/crates/circuits/sha-macros/src/lib.rs b/crates/circuits/sha-macros/src/lib.rs new file mode 100644 index 0000000000..3b446642e3 --- /dev/null +++ b/crates/circuits/sha-macros/src/lib.rs @@ -0,0 +1,473 @@ +extern crate proc_macro; + +use itertools::Itertools; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, parse_quote, DeriveInput, Expr}; + +#[proc_macro_derive(ColsRef, attributes(dim))] +pub fn cols_ref(input: TokenStream) -> TokenStream { + let derive_input: DeriveInput = parse_macro_input!(input as DeriveInput); + + let span = derive_input.ident.span().clone(); + let res = cols_ref_impl(derive_input); + if res.is_err() { + return syn::Error::new(span, res.err().unwrap().to_string()) + .to_compile_error() + .into(); + } else { + res.unwrap().into() + } +} + +fn cols_ref_impl(derive_input: DeriveInput) -> Result { + let DeriveInput { + ident, + generics, + data, + vis, + .. + } = derive_input; + + let generic_types = generics + .params + .iter() + .filter_map(|p| { + if let syn::GenericParam::Type(type_param) = p { + Some(type_param) + } else { + None + } + }) + .collect::>(); + + if generic_types.len() != 1 { + return Err("Struct must have exactly one generic type parameter".to_string()); + } + + let generic_type = generic_types[0]; + + match data { + syn::Data::Struct(data_struct) => { + let const_field_infos: Vec = data_struct + .fields + .iter() + .map(|f| get_const_cols_ref_fields(f, generic_type)) + .collect::, String>>() + .map_err(|e| format!("Failed to process fields. {}", e))?; + + let const_cols_ref_name = syn::Ident::new(&format!("{}Ref", ident), ident.span()); + let from_args = quote! { slice: &'a [#generic_type] }; + + let struct_info = StructInfo { + name: const_cols_ref_name, + vis: vis.clone(), + generic_type: generic_type.clone(), + field_infos: const_field_infos, + fields: data_struct.fields.clone(), + from_args, + derive_clone: true, + }; + + let const_cols_ref_struct = make_struct(struct_info.clone()); + + let from_mut_impl = make_from_mut(struct_info)?; + + let mut_field_infos: Vec = data_struct + .fields + .iter() + .map(|f| get_mut_cols_ref_fields(f, generic_type)) + .collect::, String>>() + .map_err(|e| format!("Failed to process fields. {}", e))?; + + let mut_cols_ref_name = syn::Ident::new(&format!("{}RefMut", ident), ident.span()); + let from_args = quote! { slice: &'a mut [#generic_type] }; + + let struct_info = StructInfo { + name: mut_cols_ref_name, + vis, + generic_type: generic_type.clone(), + field_infos: mut_field_infos, + fields: data_struct.fields, + from_args, + derive_clone: false, + }; + + let mut_cols_ref_struct = make_struct(struct_info); + + Ok(quote! { + #const_cols_ref_struct + #from_mut_impl + #mut_cols_ref_struct + }) + } + _ => Err("ColsRef can only be derived for structs".to_string()), + } +} + +#[derive(Debug, Clone)] +struct StructInfo { + name: syn::Ident, + vis: syn::Visibility, + generic_type: syn::TypeParam, + field_infos: Vec, + fields: syn::Fields, + from_args: proc_macro2::TokenStream, + derive_clone: bool, +} + +fn make_struct(struct_info: StructInfo) -> proc_macro2::TokenStream { + let StructInfo { + name, + vis, + generic_type, + field_infos, + fields, + from_args, + derive_clone, + } = struct_info; + + let field_types = field_infos.iter().map(|f| &f.ty).collect_vec(); + let length_exprs = field_infos.iter().map(|f| &f.length_expr).collect_vec(); + let prepare_subslices = field_infos + .iter() + .map(|f| &f.prepare_subslice) + .collect_vec(); + let initializers = field_infos.iter().map(|f| &f.initializer).collect_vec(); + + let idents = fields.iter().map(|f| &f.ident).collect_vec(); + + let clone_impl = if derive_clone { + quote! { + #[derive(Clone)] + } + } else { + quote! {} + }; + + quote! { + #clone_impl + #vis struct #name <'a, #generic_type> { + #( pub #idents: #field_types ),* + } + + impl<'a, #generic_type> #name<'a, #generic_type> { + pub fn from(#from_args) -> Self { + #( #prepare_subslices )* + Self { + #( #idents: #initializers ),* + } + } + + pub fn len() -> usize { + 0 #( + #length_exprs )* + } + } + } +} + +fn make_from_mut(struct_info: StructInfo) -> Result { + let StructInfo { + name, + vis: _, + generic_type, + field_infos: _, + fields, + from_args: _, + derive_clone: _, + } = struct_info; + + let fields = match fields { + syn::Fields::Named(fields) => fields.named, + _ => { + return Err("Fields must be named".to_string()); + } + }; + + let from_mut_impl = fields + .iter() + .map(|f| { + let ident = f.ident.clone().unwrap(); + match &f.ty { + syn::Type::Path(type_path) => { + let first_ident = type_path.path.segments.first().unwrap().ident.to_string(); + if first_ident.ends_with("Cols") { + // lifetime 'b is used in from_mut to allow more flexible lifetime of return value + let cols_ref_type = + get_const_cols_ref_type(&f.ty, &generic_type, parse_quote! { 'b }); + Ok(quote! { + <#cols_ref_type>::from_mut::(&other.#ident) + }) + } else { + // Not a ColsRef type, so the type is T + Ok(quote! { + &other.#ident + }) + } + } + syn::Type::Array(_) => { + // type is nested array of T + Ok(quote! { + other.#ident.view() + }) + } + _ => Err(format!("Unsupported type: {:?}", f.ty)), + } + }) + .collect::, String>>()?; + + let field_idents = fields + .iter() + .map(|f| f.ident.clone().unwrap()) + .collect_vec(); + + let mut_struct_ident = format_ident!("{}Mut", name.to_string()); + let mut_struct_type: syn::Type = parse_quote! { + #mut_struct_ident<'a, #generic_type> + }; + + Ok(parse_quote! { + impl<'b, #generic_type> #name<'b, #generic_type> { + pub fn from_mut<'a, C: ShaConfig>(other: &'b #mut_struct_type) -> Self + { + Self { + #( #field_idents: #from_mut_impl ),* + } + } + } + }) +} + +#[derive(Debug, Clone)] +struct FieldInfo { + // type for struct definition + ty: syn::Type, + // an expr calculating the length of the field + length_expr: proc_macro2::TokenStream, + // prepare a subslice of the slice to be used in the 'from' method + prepare_subslice: proc_macro2::TokenStream, + // an expr used in the Self initializer in the 'from' method + // may refer to the subslice declared in prepare_subslice + initializer: proc_macro2::TokenStream, +} + +// Prepare the fields for the const ColsRef struct +fn get_const_cols_ref_fields( + f: &syn::Field, + generic_type: &syn::TypeParam, +) -> Result { + let length_var = format_ident!("{}_length", f.ident.clone().unwrap()); + let slice_var = format_ident!("{}_slice", f.ident.clone().unwrap()); + match get_const_cols_ref_type(&f.ty, generic_type, parse_quote! { 'a }) { + Some(const_cols_ref_type) => Ok(FieldInfo { + ty: parse_quote! { + #const_cols_ref_type + }, + length_expr: quote! { + <#const_cols_ref_type>::len::() + }, + prepare_subslice: quote! { + let #length_var = <#const_cols_ref_type>::len::(); + let (#slice_var, slice) = slice.split_at(#length_var); + let #slice_var = <#const_cols_ref_type>::from::(#slice_var); + }, + initializer: quote! { + #slice_var + }, + }), + None => { + // Not a ColsRef type, so assume it is T (the generic type) or a nested array of T + let dims = get_dims(&f.ty).map_err(|e| { + format!( + "Failed to parse the type of the field '{}'. {}", + f.ident.clone().unwrap(), + e + ) + })?; + + if dims.is_empty() { + // the field has type T + Ok(FieldInfo { + ty: parse_quote! { + &'a #generic_type + }, + length_expr: quote! { + 1 + }, + prepare_subslice: quote! { + let #length_var = 1; + let (#slice_var, slice) = slice.split_at(#length_var); + }, + initializer: quote! { + &#slice_var[0] + }, + }) + } else { + // nested array of T + let ndarray_ident: syn::Ident = format_ident!("ArrayView{}", dims.len()); + let ndarray_type: syn::Type = parse_quote! { + ndarray::#ndarray_ident<'a, #generic_type> + }; + Ok(FieldInfo { + ty: parse_quote! { + #ndarray_type + }, + length_expr: quote! { + 1 #(* C::#dims)* + }, + prepare_subslice: quote! { + let #length_var = 1 #(* C::#dims)*; + let (#slice_var, slice) = slice.split_at(#length_var); + let #slice_var = ndarray::#ndarray_ident::from_shape( ( #(C::#dims),* ) , #slice_var).unwrap(); + }, + initializer: quote! { + #slice_var + }, + }) + } + } + } +} + +// Prepare the fields for the mut ColsRef struct +fn get_mut_cols_ref_fields( + f: &syn::Field, + generic_type: &syn::TypeParam, +) -> Result { + let length_var = format_ident!("{}_length", f.ident.clone().unwrap()); + let slice_var = format_ident!("{}_slice", f.ident.clone().unwrap()); + match get_mut_cols_ref_type(&f.ty, generic_type) { + Some(mut_cols_ref_type) => Ok(FieldInfo { + ty: parse_quote! { + #mut_cols_ref_type + }, + length_expr: quote! { + <#mut_cols_ref_type>::len::() + }, + prepare_subslice: quote! { + let #length_var = <#mut_cols_ref_type>::len::(); + let (mut #slice_var, mut slice) = slice.split_at_mut(#length_var); + let #slice_var = <#mut_cols_ref_type>::from::(#slice_var); + }, + initializer: quote! { + #slice_var + }, + }), + None => { + // Not a ColsRef type, so assume it is T (the generic type) or a nested array of T + let dims = get_dims(&f.ty).map_err(|e| { + format!( + "Failed to parse the type of the field '{}'. {}", + f.ident.clone().unwrap(), + e + ) + })?; + + if dims.is_empty() { + // the field has type T + Ok(FieldInfo { + ty: parse_quote! { + &'a mut #generic_type + }, + length_expr: quote! { + 1 + }, + prepare_subslice: quote! { + let #length_var = 1; + let (mut #slice_var, mut slice) = slice.split_at_mut(#length_var); + }, + initializer: quote! { + &mut #slice_var[0] + }, + }) + } else { + // nested array of T + let ndarray_ident: syn::Ident = format_ident!("ArrayViewMut{}", dims.len()); + let ndarray_type: syn::Type = parse_quote! { + ndarray::#ndarray_ident<'a, #generic_type> + }; + Ok(FieldInfo { + ty: parse_quote! { + #ndarray_type + }, + length_expr: quote! { + 1 #(* C::#dims)* + }, + prepare_subslice: quote! { + let #length_var = 1 #(* C::#dims)*; + let (mut #slice_var, mut slice) = slice.split_at_mut(#length_var); + let mut #slice_var = ndarray::#ndarray_ident::from_shape( ( #(C::#dims),* ) , #slice_var).unwrap(); + }, + initializer: quote! { + #slice_var + }, + }) + } + } + } +} + +// If 'ty' is a struct that derives ColsRef, return the ColsRef struct type +// Otherwise, return None +fn get_const_cols_ref_type( + ty: &syn::Type, + generic_type: &syn::TypeParam, + lifetime: syn::Lifetime, +) -> Option { + if let syn::Type::Path(type_path) = ty { + type_path.path.segments.iter().last().and_then(|s| { + if s.ident.to_string().ends_with("Cols") { + let const_cols_ref_ident = format_ident!("{}Ref", s.ident); + let const_cols_ref_type = parse_quote! { + #const_cols_ref_ident<#lifetime, #generic_type> + }; + Some(const_cols_ref_type) + } else { + None + } + }) + } else { + None + } +} + +// If 'ty' is a struct that derives ColsRef, return the ColsRefMut struct type +// Otherwise, return None +fn get_mut_cols_ref_type(ty: &syn::Type, generic_type: &syn::TypeParam) -> Option { + if let syn::Type::Path(type_path) = ty { + type_path.path.segments.iter().last().and_then(|s| { + if s.ident.to_string().ends_with("Cols") { + let mut_cols_ref_ident = format_ident!("{}RefMut", s.ident); + let mut_cols_ref_type = parse_quote! { + #mut_cols_ref_ident<'a, #generic_type> + }; + Some(mut_cols_ref_type) + } else { + None + } + }) + } else { + None + } +} + +fn get_dims(ty: &syn::Type) -> Result, String> { + get_dims_impl(ty).map(|dims| dims.into_iter().rev().collect()) +} + +fn get_dims_impl(ty: &syn::Type) -> Result, String> { + match ty { + syn::Type::Array(array) => { + let mut dims = get_dims(array.elem.as_ref())?; + match array.len { + syn::Expr::Path(_) => dims.push(array.len.clone()), + _ => return Err("Array lengths must be const generic parameters.".to_string()), + } + Ok(dims) + } + syn::Type::Path(_) => Ok(Vec::new()), + _ => Err( + "Only generic types and (nested) arrays of generic types are supported.".to_string(), + ), + } +} diff --git a/crates/circuits/sha-macros/tests/flags.rs b/crates/circuits/sha-macros/tests/flags.rs new file mode 100644 index 0000000000..6da76c123d --- /dev/null +++ b/crates/circuits/sha-macros/tests/flags.rs @@ -0,0 +1,35 @@ +use openvm_sha_air::{Sha256Config, ShaConfig}; +use openvm_sha_macros::ColsRef; + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaFlagsCols { + pub is_round_row: T, + /// A flag that indicates if the current row is among the first 4 rows of a block + pub is_first_4_rows: T, + pub is_digest_row: T, + pub is_last_block: T, + /// We will encode the row index [0..17) using 5 cells + //#[length(ROW_VAR_CNT)] + pub row_idx: [T; ROW_VAR_CNT], + /// The global index of the current block + pub global_block_idx: T, + /// Will store the index of the current block in the current message starting from 0 + pub local_block_idx: T, +} + +#[test] +fn flags() { + let input = [1; 4 + 5 + 2]; + let test: ShaFlagsColsRef = ShaFlagsColsRef::from::(&input); + println!( + "{}\n{}\n{}\n{}\n{:?}\n{}\n{}", + test.is_round_row, + test.is_first_4_rows, + test.is_digest_row, + test.is_last_block, + test.row_idx, + test.global_block_idx, + test.local_block_idx + ); +} diff --git a/crates/circuits/sha-macros/tests/nested.rs b/crates/circuits/sha-macros/tests/nested.rs new file mode 100644 index 0000000000..2425791706 --- /dev/null +++ b/crates/circuits/sha-macros/tests/nested.rs @@ -0,0 +1,41 @@ +use openvm_sha_air::{Sha256Config, ShaConfig}; +use openvm_sha_macros::ColsRef; + +#[derive(ColsRef)] +struct Test1Cols { + pub a: T, + pub nested: Test2Cols, +} + +#[derive(ColsRef)] +struct Test2Cols { + pub b: T, + pub c: [T; WORD_BITS], +} + +#[test] +fn nested_const() { + let input = [0; 1 + 1 + 32]; + let test: Test1ColsRef = Test1ColsRef::from::(&input); + println!("{}, {}, {}", test.a, test.nested.b, test.nested.c); +} + +#[test] +fn nested_mut() { + let mut input = [0; 1 + 1 + 32]; + let mut test: Test1ColsRefMut = Test1ColsRefMut::from::(&mut input); + *test.nested.b = 1u32; + test.nested.c[0] = 1u32; + println!("{}, {}, {}", test.a, test.nested.b, test.nested.c); +} + +#[test] +fn nested_from_mut() { + let mut mut_input = [0; 1 + 1 + 32]; + let mut mut_test: Test1ColsRefMut = Test1ColsRefMut::from::(&mut mut_input); + let const_test: Test1ColsRef = Test1ColsRef::from_mut::(&mut mut_test); + println!( + "{}, {}, {}", + const_test.a, const_test.nested.b, const_test.nested.c + ); +} diff --git a/crates/circuits/sha-macros/tests/simple.rs b/crates/circuits/sha-macros/tests/simple.rs new file mode 100644 index 0000000000..cf89118c58 --- /dev/null +++ b/crates/circuits/sha-macros/tests/simple.rs @@ -0,0 +1,17 @@ +use openvm_sha_air::{Sha256Config, ShaConfig}; +use openvm_sha_macros::ColsRef; + +#[derive(ColsRef)] +struct Test { + a: T, + b: [T; WORD_BITS], + c: [[T; WORD_BITS]; ROUNDS_PER_ROW], +} + +#[test] +fn simple() { + let input = [0; 1 + 32 + 32 * 4]; + let test: TestRef = TestRef::from::(&input); + println!("{}, {}, {}", test.a, test.b[0], test.b[1]); + println!("{}", test.c); +} diff --git a/crates/circuits/sha-macros/tests/work-vars.rs b/crates/circuits/sha-macros/tests/work-vars.rs new file mode 100644 index 0000000000..6ca6d745cc --- /dev/null +++ b/crates/circuits/sha-macros/tests/work-vars.rs @@ -0,0 +1,28 @@ +use openvm_sha_air::{Sha256Config, ShaConfig}; +use openvm_sha_macros::ColsRef; + +#[repr(C)] +#[derive(Clone, Copy, Debug, ColsRef)] +pub struct ShaWorkVarsCols< + T, + const WORD_BITS: usize, + const ROUNDS_PER_ROW: usize, + const WORD_U16S: usize, +> { + /// `a` and `e` after each iteration as 32-bits + pub a: [[T; WORD_BITS]; ROUNDS_PER_ROW], + pub e: [[T; WORD_BITS]; ROUNDS_PER_ROW], + /// The carry's used for addition during each iteration when computing `a` and `e` + pub carry_a: [[T; WORD_U16S]; ROUNDS_PER_ROW], + pub carry_e: [[T; WORD_U16S]; ROUNDS_PER_ROW], +} + +#[test] +fn work_vars() { + let input = [2; 32 * 4 + 32 * 4 + 2 * 4 + 2 * 4]; + let test: ShaWorkVarsColsRef = ShaWorkVarsColsRef::from::(&input); + println!( + "{:?}\n{:?}\n{:?}\n{:?}", + test.a, test.e, test.carry_a, test.carry_e + ); +} diff --git a/crates/circuits/sha256-air/src/air.rs b/crates/circuits/sha256-air/src/air.rs deleted file mode 100644 index e47aa96504..0000000000 --- a/crates/circuits/sha256-air/src/air.rs +++ /dev/null @@ -1,592 +0,0 @@ -use std::{array, borrow::Borrow, cmp::max, iter::once}; - -use openvm_circuit_primitives::{ - bitwise_op_lookup::BitwiseOperationLookupBus, - encoder::Encoder, - utils::{not, select}, - SubAir, -}; -use openvm_stark_backend::{ - interaction::{BusIndex, InteractionBuilder, PermutationCheckBus}, - p3_air::{AirBuilder, BaseAir}, - p3_field::{Field, FieldAlgebra}, - p3_matrix::Matrix, -}; - -use super::{ - big_sig0_field, big_sig1_field, ch_field, compose, maj_field, small_sig0_field, - small_sig1_field, u32_into_limbs, Sha256DigestCols, Sha256RoundCols, SHA256_DIGEST_WIDTH, - SHA256_H, SHA256_HASH_WORDS, SHA256_K, SHA256_ROUNDS_PER_ROW, SHA256_ROUND_WIDTH, - SHA256_WORD_BITS, SHA256_WORD_U16S, SHA256_WORD_U8S, -}; -use crate::constraint_word_addition; - -/// Expects the message to be padded to a multiple of 512 bits -#[derive(Clone, Debug)] -pub struct Sha256Air { - pub bitwise_lookup_bus: BitwiseOperationLookupBus, - pub row_idx_encoder: Encoder, - /// Internal bus for self-interactions in this AIR. - bus: PermutationCheckBus, -} - -impl Sha256Air { - pub fn new(bitwise_lookup_bus: BitwiseOperationLookupBus, self_bus_idx: BusIndex) -> Self { - Self { - bitwise_lookup_bus, - row_idx_encoder: Encoder::new(18, 2, false), - bus: PermutationCheckBus::new(self_bus_idx), - } - } -} - -impl BaseAir for Sha256Air { - fn width(&self) -> usize { - max( - Sha256RoundCols::::width(), - Sha256DigestCols::::width(), - ) - } -} - -impl SubAir for Sha256Air { - /// The start column for the sub-air to use - type AirContext<'a> - = usize - where - Self: 'a, - AB: 'a, - ::Var: 'a, - ::Expr: 'a; - - fn eval<'a>(&'a self, builder: &'a mut AB, start_col: Self::AirContext<'a>) - where - ::Var: 'a, - ::Expr: 'a, - { - self.eval_row(builder, start_col); - self.eval_transitions(builder, start_col); - } -} - -impl Sha256Air { - /// Implements the single row constraints (i.e. imposes constraints only on local) - /// Implements some sanity constraints on the row index, flags, and work variables - fn eval_row(&self, builder: &mut AB, start_col: usize) { - let main = builder.main(); - let local = main.row_slice(0); - - // Doesn't matter which column struct we use here as we are only interested in the common columns - let local_cols: &Sha256DigestCols = - local[start_col..start_col + SHA256_DIGEST_WIDTH].borrow(); - let flags = &local_cols.flags; - builder.assert_bool(flags.is_round_row); - builder.assert_bool(flags.is_first_4_rows); - builder.assert_bool(flags.is_digest_row); - builder.assert_bool(flags.is_round_row + flags.is_digest_row); - builder.assert_bool(flags.is_last_block); - - self.row_idx_encoder - .eval(builder, &local_cols.flags.row_idx); - builder.assert_one( - self.row_idx_encoder - .contains_flag_range::(&local_cols.flags.row_idx, 0..=17), - ); - builder.assert_eq( - self.row_idx_encoder - .contains_flag_range::(&local_cols.flags.row_idx, 0..=3), - flags.is_first_4_rows, - ); - builder.assert_eq( - self.row_idx_encoder - .contains_flag_range::(&local_cols.flags.row_idx, 0..=15), - flags.is_round_row, - ); - builder.assert_eq( - self.row_idx_encoder - .contains_flag::(&local_cols.flags.row_idx, &[16]), - flags.is_digest_row, - ); - // If padding row we want the row_idx to be 17 - builder.assert_eq( - self.row_idx_encoder - .contains_flag::(&local_cols.flags.row_idx, &[17]), - flags.is_padding_row(), - ); - - // Constrain a, e, being composed of bits: we make sure a and e are always in the same place in the trace matrix - // Note: this has to be true for every row, even padding rows - for i in 0..SHA256_ROUNDS_PER_ROW { - for j in 0..SHA256_WORD_BITS { - builder.assert_bool(local_cols.hash.a[i][j]); - builder.assert_bool(local_cols.hash.e[i][j]); - } - } - } - - /// Implements constraints for a digest row that ensure proper state transitions between blocks - /// This validates that: - /// The work variables are correctly initialized for the next message block - /// For the last message block, the initial state matches SHA256_H constants - fn eval_digest_row( - &self, - builder: &mut AB, - local: &Sha256RoundCols, - next: &Sha256DigestCols, - ) { - // Check that if this is the last row of a message or an inpadding row, the hash should be the [SHA256_H] - for i in 0..SHA256_ROUNDS_PER_ROW { - let a = next.hash.a[i].map(|x| x.into()); - let e = next.hash.e[i].map(|x| x.into()); - for j in 0..SHA256_WORD_U16S { - let a_limb = compose::(&a[j * 16..(j + 1) * 16], 1); - let e_limb = compose::(&e[j * 16..(j + 1) * 16], 1); - - // If it is a padding row or the last row of a message, the `hash` should be the [SHA256_H] - builder - .when( - next.flags.is_padding_row() - + next.flags.is_last_block * next.flags.is_digest_row, - ) - .assert_eq( - a_limb, - AB::Expr::from_canonical_u32( - u32_into_limbs::<2>(SHA256_H[SHA256_ROUNDS_PER_ROW - i - 1])[j], - ), - ); - - builder - .when( - next.flags.is_padding_row() - + next.flags.is_last_block * next.flags.is_digest_row, - ) - .assert_eq( - e_limb, - AB::Expr::from_canonical_u32( - u32_into_limbs::<2>(SHA256_H[SHA256_ROUNDS_PER_ROW - i + 3])[j], - ), - ); - } - } - - // Check if last row of a non-last block, the `hash` should be equal to the final hash of the current block - for i in 0..SHA256_ROUNDS_PER_ROW { - let prev_a = next.hash.a[i].map(|x| x.into()); - let prev_e = next.hash.e[i].map(|x| x.into()); - let cur_a = next.final_hash[SHA256_ROUNDS_PER_ROW - i - 1].map(|x| x.into()); - - let cur_e = next.final_hash[SHA256_ROUNDS_PER_ROW - i + 3].map(|x| x.into()); - for j in 0..SHA256_WORD_U8S { - let prev_a_limb = compose::(&prev_a[j * 8..(j + 1) * 8], 1); - let prev_e_limb = compose::(&prev_e[j * 8..(j + 1) * 8], 1); - - builder - .when(not(next.flags.is_last_block) * next.flags.is_digest_row) - .assert_eq(prev_a_limb, cur_a[j].clone()); - - builder - .when(not(next.flags.is_last_block) * next.flags.is_digest_row) - .assert_eq(prev_e_limb, cur_e[j].clone()); - } - } - - // Assert that the previous hash + work vars == final hash. - // That is, `next.prev_hash[i] + local.work_vars[i] == next.final_hash[i]` - // where addition is done modulo 2^32 - for i in 0..SHA256_HASH_WORDS { - let mut carry = AB::Expr::ZERO; - for j in 0..SHA256_WORD_U16S { - let work_var_limb = if i < SHA256_ROUNDS_PER_ROW { - compose::( - &local.work_vars.a[SHA256_ROUNDS_PER_ROW - 1 - i][j * 16..(j + 1) * 16], - 1, - ) - } else { - compose::( - &local.work_vars.e[SHA256_ROUNDS_PER_ROW + 3 - i][j * 16..(j + 1) * 16], - 1, - ) - }; - let final_hash_limb = - compose::(&next.final_hash[i][j * 2..(j + 1) * 2], 8); - - carry = AB::Expr::from(AB::F::from_canonical_u32(1 << 16).inverse()) - * (next.prev_hash[i][j] + work_var_limb + carry - final_hash_limb); - builder - .when(next.flags.is_digest_row) - .assert_bool(carry.clone()); - } - // constrain the final hash limbs two at a time since we can do two checks per interaction - for chunk in next.final_hash[i].chunks(2) { - self.bitwise_lookup_bus - .send_range(chunk[0], chunk[1]) - .eval(builder, next.flags.is_digest_row); - } - } - } - - fn eval_transitions(&self, builder: &mut AB, start_col: usize) { - let main = builder.main(); - let local = main.row_slice(0); - let next = main.row_slice(1); - - // Doesn't matter what column structs we use here - let local_cols: &Sha256RoundCols = - local[start_col..start_col + SHA256_ROUND_WIDTH].borrow(); - let next_cols: &Sha256RoundCols = - next[start_col..start_col + SHA256_ROUND_WIDTH].borrow(); - - let local_is_padding_row = local_cols.flags.is_padding_row(); - // Note that there will always be a padding row in the trace since the unpadded height is a multiple of 17. - // So the next row is padding iff the current block is the last block in the trace. - let next_is_padding_row = next_cols.flags.is_padding_row(); - - // We check that the very last block has `is_last_block` set to true, which guarantees that - // there is at least one complete message. If other digest rows have `is_last_block` set to true, - // then the trace will be interpreted as containing multiple messages. - builder - .when(next_is_padding_row.clone()) - .when(local_cols.flags.is_digest_row) - .assert_one(local_cols.flags.is_last_block); - // If we are in a round row, the next row cannot be a padding row - builder - .when(local_cols.flags.is_round_row) - .assert_zero(next_is_padding_row.clone()); - // The first row must be a round row - builder - .when_first_row() - .assert_one(local_cols.flags.is_round_row); - // If we are in a padding row, the next row must also be a padding row - builder - .when_transition() - .when(local_is_padding_row.clone()) - .assert_one(next_is_padding_row.clone()); - // If we are in a digest row, the next row cannot be a digest row - builder - .when(local_cols.flags.is_digest_row) - .assert_zero(next_cols.flags.is_digest_row); - // Constrain how much the row index changes by - // round->round: 1 - // round->digest: 1 - // digest->round: -16 - // digest->padding: 1 - // padding->padding: 0 - // Other transitions are not allowed by the above constraints - let delta = local_cols.flags.is_round_row * AB::Expr::ONE - + local_cols.flags.is_digest_row - * next_cols.flags.is_round_row - * AB::Expr::from_canonical_u32(16) - * AB::Expr::NEG_ONE - + local_cols.flags.is_digest_row * next_is_padding_row.clone() * AB::Expr::ONE; - - let local_row_idx = self.row_idx_encoder.flag_with_val::( - &local_cols.flags.row_idx, - &(0..18).map(|i| (i, i)).collect::>(), - ); - let next_row_idx = self.row_idx_encoder.flag_with_val::( - &next_cols.flags.row_idx, - &(0..18).map(|i| (i, i)).collect::>(), - ); - - builder - .when_transition() - .assert_eq(local_row_idx.clone() + delta, next_row_idx.clone()); - builder.when_first_row().assert_zero(local_row_idx); - - // Constrain the global block index - // We set the global block index to 0 for padding rows - // Starting with 1 so it is not the same as the padding rows - - // Global block index is 1 on first row - builder - .when_first_row() - .assert_one(local_cols.flags.global_block_idx); - - // Global block index is constant on all rows in a block - builder.when(local_cols.flags.is_round_row).assert_eq( - local_cols.flags.global_block_idx, - next_cols.flags.global_block_idx, - ); - // Global block index increases by 1 between blocks - builder - .when_transition() - .when(local_cols.flags.is_digest_row) - .when(next_cols.flags.is_round_row) - .assert_eq( - local_cols.flags.global_block_idx + AB::Expr::ONE, - next_cols.flags.global_block_idx, - ); - // Global block index is 0 on padding rows - builder - .when(local_is_padding_row.clone()) - .assert_zero(local_cols.flags.global_block_idx); - - // Constrain the local block index - // We set the local block index to 0 for padding rows - - // Local block index is constant on all rows in a block - // and its value on padding rows is equal to its value on the first block - builder.when(not(local_cols.flags.is_digest_row)).assert_eq( - local_cols.flags.local_block_idx, - next_cols.flags.local_block_idx, - ); - // Local block index increases by 1 between blocks in the same message - builder - .when(local_cols.flags.is_digest_row) - .when(not(local_cols.flags.is_last_block)) - .assert_eq( - local_cols.flags.local_block_idx + AB::Expr::ONE, - next_cols.flags.local_block_idx, - ); - // Local block index is 0 on padding rows - // Combined with the above, this means that the local block index is 0 in the first block - builder - .when(local_cols.flags.is_digest_row) - .when(local_cols.flags.is_last_block) - .assert_zero(next_cols.flags.local_block_idx); - - self.eval_message_schedule::(builder, local_cols, next_cols); - self.eval_work_vars::(builder, local_cols, next_cols); - let next_cols: &Sha256DigestCols = - next[start_col..start_col + SHA256_DIGEST_WIDTH].borrow(); - self.eval_digest_row(builder, local_cols, next_cols); - let local_cols: &Sha256DigestCols = - local[start_col..start_col + SHA256_DIGEST_WIDTH].borrow(); - self.eval_prev_hash::(builder, local_cols, next_is_padding_row); - } - - /// Constrains that the next block's `prev_hash` is equal to the current block's `hash` - /// Note: the constraining is done by interactions with the chip itself on every digest row - fn eval_prev_hash( - &self, - builder: &mut AB, - local: &Sha256DigestCols, - is_last_block_of_trace: AB::Expr, // note this indicates the last block of the trace, not the last block of the message - ) { - // Constrain that next block's `prev_hash` is equal to the current block's `hash` - let composed_hash: [[::Expr; SHA256_WORD_U16S]; SHA256_HASH_WORDS] = - array::from_fn(|i| { - let hash_bits = if i < SHA256_ROUNDS_PER_ROW { - local.hash.a[SHA256_ROUNDS_PER_ROW - 1 - i].map(|x| x.into()) - } else { - local.hash.e[SHA256_ROUNDS_PER_ROW + 3 - i].map(|x| x.into()) - }; - array::from_fn(|j| compose::(&hash_bits[j * 16..(j + 1) * 16], 1)) - }); - // Need to handle the case if this is the very last block of the trace matrix - let next_global_block_idx = select( - is_last_block_of_trace, - AB::Expr::ONE, - local.flags.global_block_idx + AB::Expr::ONE, - ); - // The following interactions constrain certain values from block to block - self.bus.send( - builder, - composed_hash - .into_iter() - .flatten() - .chain(once(next_global_block_idx)), - local.flags.is_digest_row, - ); - - self.bus.receive( - builder, - local - .prev_hash - .into_iter() - .flatten() - .map(|x| x.into()) - .chain(once(local.flags.global_block_idx.into())), - local.flags.is_digest_row, - ); - } - - /// Constrain the message schedule additions for `next` row - /// Note: For every addition we need to constrain the following for each of [SHA256_WORD_U16S] limbs - /// sig_1(w_{t-2})[i] + w_{t-7}[i] + sig_0(w_{t-15})[i] + w_{t-16}[i] + carry_w[t][i-1] - carry_w[t][i] * 2^16 - w_t[i] == 0 - /// Refer to [https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf] - fn eval_message_schedule( - &self, - builder: &mut AB, - local: &Sha256RoundCols, - next: &Sha256RoundCols, - ) { - // This `w` array contains 8 message schedule words - w_{idx}, ..., w_{idx+7} for some idx - let w = [local.message_schedule.w, next.message_schedule.w].concat(); - - // Constrain `w_3` for `next` row - for i in 0..SHA256_ROUNDS_PER_ROW - 1 { - // here we constrain the w_3 of the i_th word of the next row - // w_3 of next is w[i+4-3] = w[i+1] - let w_3 = w[i + 1].map(|x| x.into()); - let expected_w_3 = next.schedule_helper.w_3[i]; - for j in 0..SHA256_WORD_U16S { - let w_3_limb = compose::(&w_3[j * 16..(j + 1) * 16], 1); - builder - .when(local.flags.is_round_row) - .assert_eq(w_3_limb, expected_w_3[j].into()); - } - } - - // Constrain intermed for `next` row - // We will only constrain intermed_12 for rows [3, 14], and let it be unconstrained for other rows - // Other rows should put the needed value in intermed_12 to make the below summation constraint hold - let is_row_3_14 = self - .row_idx_encoder - .contains_flag_range::(&next.flags.row_idx, 3..=14); - // We will only constrain intermed_8 for rows [2, 13], and let it unconstrained for other rows - let is_row_2_13 = self - .row_idx_encoder - .contains_flag_range::(&next.flags.row_idx, 2..=13); - for i in 0..SHA256_ROUNDS_PER_ROW { - // w_idx - let w_idx = w[i].map(|x| x.into()); - // sig_0(w_{idx+1}) - let sig_w = small_sig0_field::(&w[i + 1]); - for j in 0..SHA256_WORD_U16S { - let w_idx_limb = compose::(&w_idx[j * 16..(j + 1) * 16], 1); - let sig_w_limb = compose::(&sig_w[j * 16..(j + 1) * 16], 1); - - // We would like to constrain this only on rows 0..16, but we can't do a conditional check because the degree is already 3. - // So we must fill in `intermed_4` with dummy values on rows 0 and 16 to ensure the constraint holds on these rows. - builder.when_transition().assert_eq( - next.schedule_helper.intermed_4[i][j], - w_idx_limb + sig_w_limb, - ); - - builder.when(is_row_2_13.clone()).assert_eq( - next.schedule_helper.intermed_8[i][j], - local.schedule_helper.intermed_4[i][j], - ); - - builder.when(is_row_3_14.clone()).assert_eq( - next.schedule_helper.intermed_12[i][j], - local.schedule_helper.intermed_8[i][j], - ); - } - } - - // Constrain the message schedule additions for `next` row - for i in 0..SHA256_ROUNDS_PER_ROW { - // Note, here by w_{t} we mean the i_th word of the `next` row - // w_{t-7} - let w_7 = if i < 3 { - local.schedule_helper.w_3[i].map(|x| x.into()) - } else { - let w_3 = w[i - 3].map(|x| x.into()); - array::from_fn(|j| compose::(&w_3[j * 16..(j + 1) * 16], 1)) - }; - // sig_0(w_{t-15}) + w_{t-16} - let intermed_16 = local.schedule_helper.intermed_12[i].map(|x| x.into()); - - let carries = array::from_fn(|j| { - next.message_schedule.carry_or_buffer[i][j * 2] - + AB::Expr::TWO * next.message_schedule.carry_or_buffer[i][j * 2 + 1] - }); - - // Constrain `W_{idx} = sig_1(W_{idx-2}) + W_{idx-7} + sig_0(W_{idx-15}) + W_{idx-16}` - // We would like to constrain this only on rows 4..16, but we can't do a conditional check because the degree of sum is already 3 - // So we must fill in `intermed_12` with dummy values on rows 0..3 and 15 and 16 to ensure the constraint holds on rows - // 0..4 and 16. Note that the dummy value goes in the previous row to make the current row's constraint hold. - constraint_word_addition( - // Note: here we can't do a conditional check because the degree of sum is already 3 - &mut builder.when_transition(), - &[&small_sig1_field::(&w[i + 2])], - &[&w_7, &intermed_16], - &w[i + 4], - &carries, - ); - - for j in 0..SHA256_WORD_U16S { - // When on rows 4..16 message schedule carries should be 0 or 1 - let is_row_4_15 = next.flags.is_round_row - next.flags.is_first_4_rows; - builder - .when(is_row_4_15.clone()) - .assert_bool(next.message_schedule.carry_or_buffer[i][j * 2]); - builder - .when(is_row_4_15) - .assert_bool(next.message_schedule.carry_or_buffer[i][j * 2 + 1]); - } - // Constrain w being composed of bits - for j in 0..SHA256_WORD_BITS { - builder - .when(next.flags.is_round_row) - .assert_bool(next.message_schedule.w[i][j]); - } - } - } - - /// Constrain the work vars on `next` row according to the sha256 documentation - /// Refer to [https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf] - fn eval_work_vars( - &self, - builder: &mut AB, - local: &Sha256RoundCols, - next: &Sha256RoundCols, - ) { - let a = [local.work_vars.a, next.work_vars.a].concat(); - let e = [local.work_vars.e, next.work_vars.e].concat(); - for i in 0..SHA256_ROUNDS_PER_ROW { - for j in 0..SHA256_WORD_U16S { - // Although we need carry_a <= 6 and carry_e <= 5, constraining carry_a, carry_e in [0, 2^8) is enough - // to prevent overflow and ensure the soundness of the addition we want to check - self.bitwise_lookup_bus - .send_range(local.work_vars.carry_a[i][j], local.work_vars.carry_e[i][j]) - .eval(builder, local.flags.is_round_row); - } - - let w_limbs = array::from_fn(|j| { - compose::(&next.message_schedule.w[i][j * 16..(j + 1) * 16], 1) - * next.flags.is_round_row - }); - let k_limbs = array::from_fn(|j| { - self.row_idx_encoder.flag_with_val::( - &next.flags.row_idx, - &(0..16) - .map(|rw_idx| { - ( - rw_idx, - u32_into_limbs::( - SHA256_K[rw_idx * SHA256_ROUNDS_PER_ROW + i], - )[j] as usize, - ) - }) - .collect::>(), - ) - }); - - // Constrain `a = h + sig_1(e) + ch(e, f, g) + K + W + sig_0(a) + Maj(a, b, c)` - // We have to enforce this constraint on all rows since the degree of the constraint is already 3. - // So, we must fill in `carry_a` with dummy values on digest rows to ensure the constraint holds. - constraint_word_addition( - builder, - &[ - &e[i].map(|x| x.into()), // previous `h` - &big_sig1_field::(&e[i + 3]), // sig_1 of previous `e` - &ch_field::(&e[i + 3], &e[i + 2], &e[i + 1]), // Ch of previous `e`, `f`, `g` - &big_sig0_field::(&a[i + 3]), // sig_0 of previous `a` - &maj_field::(&a[i + 3], &a[i + 2], &a[i + 1]), // Maj of previous a, b, c - ], - &[&w_limbs, &k_limbs], // K and W - &a[i + 4], // new `a` - &next.work_vars.carry_a[i], // carries of addition - ); - - // Constrain `e = d + h + sig_1(e) + ch(e, f, g) + K + W` - // We have to enforce this constraint on all rows since the degree of the constraint is already 3. - // So, we must fill in `carry_e` with dummy values on digest rows to ensure the constraint holds. - constraint_word_addition( - builder, - &[ - &a[i].map(|x| x.into()), // previous `d` - &e[i].map(|x| x.into()), // previous `h` - &big_sig1_field::(&e[i + 3]), // sig_1 of previous `e` - &ch_field::(&e[i + 3], &e[i + 2], &e[i + 1]), // Ch of previous `e`, `f`, `g` - ], - &[&w_limbs, &k_limbs], // K and W - &e[i + 4], // new `e` - &next.work_vars.carry_e[i], // carries of addition - ); - } - } -} diff --git a/crates/circuits/sha256-air/src/columns.rs b/crates/circuits/sha256-air/src/columns.rs deleted file mode 100644 index 173aca0943..0000000000 --- a/crates/circuits/sha256-air/src/columns.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! WARNING: the order of fields in the structs is important, do not change it - -use openvm_circuit_primitives::{utils::not, AlignedBorrow}; -use openvm_stark_backend::p3_field::FieldAlgebra; - -use super::{ - SHA256_HASH_WORDS, SHA256_ROUNDS_PER_ROW, SHA256_ROW_VAR_CNT, SHA256_WORD_BITS, - SHA256_WORD_U16S, SHA256_WORD_U8S, -}; - -/// In each SHA256 block: -/// - First 16 rows use Sha256RoundCols -/// - Final row uses Sha256DigestCols -/// -/// Note that for soundness, we require that there is always a padding row after the last digest row in the trace. -/// Right now, this is true because the unpadded height is a multiple of 17, and thus not a power of 2. -/// -/// Sha256RoundCols and Sha256DigestCols share the same first 3 fields: -/// - flags -/// - work_vars/hash (same type, different name) -/// - schedule_helper -/// -/// This design allows for: -/// 1. Common constraints to work on either struct type by accessing these shared fields -/// 2. Specific constraints to use the appropriate struct, with flags helping to do conditional constraints -/// -/// Note that the `Sha256WorkVarsCols` field it is used for different purposes in the two structs. -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256RoundCols { - pub flags: Sha256FlagsCols, - /// Stores the current state of the working variables - pub work_vars: Sha256WorkVarsCols, - pub schedule_helper: Sha256MessageHelperCols, - pub message_schedule: Sha256MessageScheduleCols, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256DigestCols { - pub flags: Sha256FlagsCols, - /// Will serve as previous hash values for the next block. - /// - on non-last blocks, this is the final hash of the current block - /// - on last blocks, this is the initial state constants, SHA256_H. - /// The work variables constraints are applied on all rows, so `carry_a` and `carry_e` - /// must be filled in with dummy values to ensure these constraints hold. - pub hash: Sha256WorkVarsCols, - pub schedule_helper: Sha256MessageHelperCols, - /// The actual final hash values of the given block - /// Note: the above `hash` will be equal to `final_hash` unless we are on the last block - pub final_hash: [[T; SHA256_WORD_U8S]; SHA256_HASH_WORDS], - /// The final hash of the previous block - /// Note: will be constrained using interactions with the chip itself - pub prev_hash: [[T; SHA256_WORD_U16S]; SHA256_HASH_WORDS], -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256MessageScheduleCols { - /// The message schedule words as 32-bit integers - /// The first 16 words will be the message data - pub w: [[T; SHA256_WORD_BITS]; SHA256_ROUNDS_PER_ROW], - /// Will be message schedule carries for rows 4..16 and a buffer for rows 0..4 to be used freely by wrapper chips - /// Note: carries are 2 bit numbers represented using 2 cells as individual bits - pub carry_or_buffer: [[T; SHA256_WORD_U8S]; SHA256_ROUNDS_PER_ROW], -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256WorkVarsCols { - /// `a` and `e` after each iteration as 32-bits - pub a: [[T; SHA256_WORD_BITS]; SHA256_ROUNDS_PER_ROW], - pub e: [[T; SHA256_WORD_BITS]; SHA256_ROUNDS_PER_ROW], - /// The carry's used for addition during each iteration when computing `a` and `e` - pub carry_a: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW], - pub carry_e: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW], -} - -/// These are the columns that are used to help with the message schedule additions -/// Note: these need to be correctly assigned for every row even on padding rows -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256MessageHelperCols { - /// The following are used to move data forward to constrain the message schedule additions - /// The value of `w` (message schedule word) from 3 rounds ago - /// In general, `w_i` means `w` from `i` rounds ago - pub w_3: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW - 1], - /// Here intermediate(i) = w_i + sig_0(w_{i+1}) - /// Intermed_t represents the intermediate t rounds ago - /// This is needed to constrain the message schedule, since we can only constrain on two rows at a time - pub intermed_4: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW], - pub intermed_8: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW], - pub intermed_12: [[T; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW], -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, AlignedBorrow)] -pub struct Sha256FlagsCols { - /// A flag that indicates if the current row is among the first 16 rows of a block. - pub is_round_row: T, - /// A flag that indicates if the current row is among the first 4 rows of a block. - pub is_first_4_rows: T, - /// A flag that indicates if the current row is the last (17th) row of a block. - pub is_digest_row: T, - // A flag that indicates if the current row is the last block of the message. - // This flag is only used in digest rows. - pub is_last_block: T, - /// We will encode the row index [0..17) using 5 cells - pub row_idx: [T; SHA256_ROW_VAR_CNT], - /// The index of the current block in the trace starting at 1. - /// Set to 0 on padding rows. - pub global_block_idx: T, - /// The index of the current block in the current message starting at 0. - /// Resets after every message. - /// Set to 0 on padding rows. - pub local_block_idx: T, -} - -impl> Sha256FlagsCols { - // This refers to the padding rows that are added to the air to make the trace length a power of 2. - // Not to be confused with the padding added to messages as part of the SHA hash function. - pub fn is_not_padding_row(&self) -> O { - self.is_round_row + self.is_digest_row - } - - // This refers to the padding rows that are added to the air to make the trace length a power of 2. - // Not to be confused with the padding added to messages as part of the SHA hash function. - pub fn is_padding_row(&self) -> O - where - O: FieldAlgebra, - { - not(self.is_not_padding_row()) - } -} diff --git a/crates/circuits/sha256-air/src/tests.rs b/crates/circuits/sha256-air/src/tests.rs deleted file mode 100644 index 903b7b0695..0000000000 --- a/crates/circuits/sha256-air/src/tests.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::{array, borrow::BorrowMut, cmp::max, sync::Arc}; - -use openvm_circuit::arch::{ - instructions::riscv::RV32_CELL_BITS, - testing::{VmChipTestBuilder, BITWISE_OP_LOOKUP_BUS}, -}; -use openvm_circuit_primitives::{ - bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip}, - SubAir, -}; -use openvm_stark_backend::{ - config::{StarkGenericConfig, Val}, - interaction::{BusIndex, InteractionBuilder}, - p3_air::{Air, BaseAir}, - p3_field::{Field, FieldAlgebra, PrimeField32}, - p3_maybe_rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}, - prover::types::AirProofInput, - rap::{get_air_name, BaseAirWithPublicValues, PartitionedBaseAir}, - AirRef, Chip, ChipUsageGetter, -}; -use openvm_stark_sdk::utils::create_seeded_rng; -use rand::Rng; - -use crate::{ - compose, small_sig0_field, Sha256Air, Sha256RoundCols, SHA256_BLOCK_U8S, SHA256_DIGEST_WIDTH, - SHA256_HASH_WORDS, SHA256_ROUNDS_PER_ROW, SHA256_ROUND_WIDTH, SHA256_ROWS_PER_BLOCK, - SHA256_WORD_U16S, SHA256_WORD_U8S, -}; - -// A wrapper AIR purely for testing purposes -#[derive(Clone, Debug)] -pub struct Sha256TestAir { - pub sub_air: Sha256Air, -} - -impl BaseAirWithPublicValues for Sha256TestAir {} -impl PartitionedBaseAir for Sha256TestAir {} -impl BaseAir for Sha256TestAir { - fn width(&self) -> usize { - >::width(&self.sub_air) - } -} - -impl Air for Sha256TestAir { - fn eval(&self, builder: &mut AB) { - self.sub_air.eval(builder, 0); - } -} - -// A wrapper Chip purely for testing purposes -pub struct Sha256TestChip { - pub air: Sha256TestAir, - pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, - pub records: Vec<([u8; SHA256_BLOCK_U8S], bool)>, -} - -impl Chip for Sha256TestChip -where - Val: PrimeField32, -{ - fn air(&self) -> AirRef { - Arc::new(self.air.clone()) - } - - fn generate_air_proof_input(self) -> AirProofInput { - let trace = crate::generate_trace::>( - &self.air.sub_air, - self.bitwise_lookup_chip.clone(), - self.records, - ); - AirProofInput::simple_no_pis(trace) - } -} - -impl ChipUsageGetter for Sha256TestChip { - fn air_name(&self) -> String { - get_air_name(&self.air) - } - fn current_trace_height(&self) -> usize { - self.records.len() * SHA256_ROWS_PER_BLOCK - } - - fn trace_width(&self) -> usize { - max(SHA256_ROUND_WIDTH, SHA256_DIGEST_WIDTH) - } -} - -const SELF_BUS_IDX: BusIndex = 28; -#[test] -fn rand_sha256_test() { - let mut rng = create_seeded_rng(); - let tester = VmChipTestBuilder::default(); - let bitwise_bus = BitwiseOperationLookupBus::new(BITWISE_OP_LOOKUP_BUS); - let bitwise_chip = SharedBitwiseOperationLookupChip::::new(bitwise_bus); - let len = rng.gen_range(1..100); - let random_records: Vec<_> = (0..len) - .map(|i| { - ( - array::from_fn(|_| rng.gen::()), - rng.gen::() || i == len - 1, - ) - }) - .collect(); - let chip = Sha256TestChip { - air: Sha256TestAir { - sub_air: Sha256Air::new(bitwise_bus, SELF_BUS_IDX), - }, - bitwise_lookup_chip: bitwise_chip.clone(), - records: random_records, - }; - - let tester = tester.build().load(chip).load(bitwise_chip).finalize(); - tester.simple_test().expect("Verification failed"); -} - -// A wrapper Chip to test that the final_hash is properly constrained. -// This chip implements a malicious trace gen that violates the final_hash constraints. -pub struct Sha256TestBadFinalHashChip { - pub air: Sha256TestAir, - pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, - pub records: Vec<([u8; SHA256_BLOCK_U8S], bool)>, -} - -impl Chip for Sha256TestBadFinalHashChip -where - Val: PrimeField32, -{ - fn air(&self) -> AirRef { - Arc::new(self.air.clone()) - } - - fn generate_air_proof_input(self) -> AirProofInput { - let mut trace = crate::generate_trace::>( - &self.air.sub_air, - self.bitwise_lookup_chip.clone(), - self.records.clone(), - ); - - // Set the final_hash in the digest row of the last block of each hash to zero. - // That is, every hash that this chip does will result in a final_hash of zero. - for (i, row) in self.records.iter().enumerate() { - if row.1 { - let last_digest_row_idx = (i + 1) * SHA256_ROWS_PER_BLOCK - 1; - let last_digest_row: &mut crate::Sha256DigestCols> = - trace.row_mut(last_digest_row_idx)[..SHA256_DIGEST_WIDTH].borrow_mut(); - // Set the final_hash to all zeros - for i in 0..SHA256_HASH_WORDS { - for j in 0..SHA256_WORD_U8S { - last_digest_row.final_hash[i][j] = Val::::ZERO; - } - } - - let (last_round_row, last_digest_row) = - trace.row_pair_mut(last_digest_row_idx - 1, last_digest_row_idx); - let last_round_row: &mut crate::Sha256RoundCols> = - last_round_row.borrow_mut(); - let last_digest_row: &mut crate::Sha256RoundCols> = - last_digest_row.borrow_mut(); - // fix the intermed_4 for the digest row - generate_intermed_4(last_round_row, last_digest_row); - } - } - - let non_padded_height = self.records.len() * SHA256_ROWS_PER_BLOCK; - let width = >>::width(&self.air.sub_air); - // recalculate the missing cells (second pass of generate_trace) - trace.values[width..] - .par_chunks_mut(width * SHA256_ROWS_PER_BLOCK) - .take(non_padded_height / SHA256_ROWS_PER_BLOCK) - .for_each(|chunk| { - self.air.sub_air.generate_missing_cells(chunk, width, 0); - }); - - AirProofInput::simple_no_pis(trace) - } -} - -// Copy of private method in Sha256Air used for testing -/// Puts the correct intermed_4 in the `next_row` -fn generate_intermed_4( - local_cols: &Sha256RoundCols, - next_cols: &mut Sha256RoundCols, -) { - let w = [local_cols.message_schedule.w, next_cols.message_schedule.w].concat(); - let w_limbs: Vec<[F; SHA256_WORD_U16S]> = w - .iter() - .map(|x| array::from_fn(|i| compose::(&x[i * 16..(i + 1) * 16], 1))) - .collect(); - for i in 0..SHA256_ROUNDS_PER_ROW { - let sig_w = small_sig0_field::(&w[i + 1]); - let sig_w_limbs: [F; SHA256_WORD_U16S] = - array::from_fn(|j| compose::(&sig_w[j * 16..(j + 1) * 16], 1)); - for (j, sig_w_limb) in sig_w_limbs.iter().enumerate() { - next_cols.schedule_helper.intermed_4[i][j] = w_limbs[i][j] + *sig_w_limb; - } - } -} - -impl ChipUsageGetter for Sha256TestBadFinalHashChip { - fn air_name(&self) -> String { - get_air_name(&self.air) - } - fn current_trace_height(&self) -> usize { - self.records.len() * SHA256_ROWS_PER_BLOCK - } - - fn trace_width(&self) -> usize { - max(SHA256_ROUND_WIDTH, SHA256_DIGEST_WIDTH) - } -} - -#[test] -#[should_panic] -fn test_sha256_final_hash_constraints() { - let mut rng = create_seeded_rng(); - let tester = VmChipTestBuilder::default(); - let bitwise_bus = BitwiseOperationLookupBus::new(BITWISE_OP_LOOKUP_BUS); - let bitwise_chip = SharedBitwiseOperationLookupChip::::new(bitwise_bus); - let len = rng.gen_range(1..100); - let random_records: Vec<_> = (0..len) - .map(|_| (array::from_fn(|_| rng.gen::()), true)) - .collect(); - let chip = Sha256TestBadFinalHashChip { - air: Sha256TestAir { - sub_air: Sha256Air::new(bitwise_bus, SELF_BUS_IDX), - }, - bitwise_lookup_chip: bitwise_chip.clone(), - records: random_records, - }; - - let tester = tester.build().load(chip).load(bitwise_chip).finalize(); - tester.simple_test().expect("Verification failed"); -} diff --git a/crates/circuits/sha256-air/src/trace.rs b/crates/circuits/sha256-air/src/trace.rs deleted file mode 100644 index 3862cc0443..0000000000 --- a/crates/circuits/sha256-air/src/trace.rs +++ /dev/null @@ -1,565 +0,0 @@ -use std::{array, borrow::BorrowMut, ops::Range}; - -use openvm_circuit_primitives::{ - bitwise_op_lookup::SharedBitwiseOperationLookupChip, utils::next_power_of_two_or_zero, -}; -use openvm_stark_backend::{ - p3_air::BaseAir, p3_field::PrimeField32, p3_matrix::dense::RowMajorMatrix, - p3_maybe_rayon::prelude::*, -}; -use sha2::{compress256, digest::generic_array::GenericArray}; - -use super::{ - air::Sha256Air, big_sig0_field, big_sig1_field, ch_field, columns::Sha256RoundCols, compose, - get_flag_pt_array, maj_field, small_sig0_field, small_sig1_field, SHA256_BLOCK_WORDS, - SHA256_DIGEST_WIDTH, SHA256_HASH_WORDS, SHA256_ROUND_WIDTH, -}; -use crate::{ - big_sig0, big_sig1, ch, columns::Sha256DigestCols, limbs_into_u32, maj, small_sig0, small_sig1, - u32_into_limbs, SHA256_BLOCK_U8S, SHA256_BUFFER_SIZE, SHA256_H, SHA256_INVALID_CARRY_A, - SHA256_INVALID_CARRY_E, SHA256_K, SHA256_ROUNDS_PER_ROW, SHA256_ROWS_PER_BLOCK, - SHA256_WORD_BITS, SHA256_WORD_U16S, SHA256_WORD_U8S, -}; - -/// The trace generation of SHA256 should be done in two passes. -/// The first pass should do `get_block_trace` for every block and generate the invalid rows through `get_default_row` -/// The second pass should go through all the blocks and call `generate_missing_cells` -impl Sha256Air { - /// This function takes the input_message (padding not handled), the previous hash, - /// and returns the new hash after processing the block input - pub fn get_block_hash( - prev_hash: &[u32; SHA256_HASH_WORDS], - input: [u8; SHA256_BLOCK_U8S], - ) -> [u32; SHA256_HASH_WORDS] { - let mut new_hash = *prev_hash; - let input_array = [GenericArray::from(input)]; - compress256(&mut new_hash, &input_array); - new_hash - } - - /// This function takes a 512-bit chunk of the input message (padding not handled), the previous hash, - /// a flag indicating if it's the last block, the global block index, the local block index, - /// and the buffer values that will be put in rows 0..4. - /// Will populate the given `trace` with the trace of the block, where the width of the trace is `trace_width` - /// and the starting column for the `Sha256Air` is `trace_start_col`. - /// **Note**: this function only generates some of the required trace. Another pass is required, refer to [`Self::generate_missing_cells`] for details. - #[allow(clippy::too_many_arguments)] - pub fn generate_block_trace( - &self, - trace: &mut [F], - trace_width: usize, - trace_start_col: usize, - input: &[u32; SHA256_BLOCK_WORDS], - bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, - prev_hash: &[u32; SHA256_HASH_WORDS], - is_last_block: bool, - global_block_idx: u32, - local_block_idx: u32, - buffer_vals: &[[F; SHA256_BUFFER_SIZE]; 4], - ) { - #[cfg(debug_assertions)] - { - assert!(trace.len() == trace_width * SHA256_ROWS_PER_BLOCK); - assert!(trace_start_col + super::SHA256_WIDTH <= trace_width); - assert!(self.bitwise_lookup_bus == bitwise_lookup_chip.bus()); - if local_block_idx == 0 { - assert!(*prev_hash == SHA256_H); - } - } - let get_range = |start: usize, len: usize| -> Range { start..start + len }; - let mut message_schedule = [0u32; 64]; - message_schedule[..input.len()].copy_from_slice(input); - let mut work_vars = *prev_hash; - for (i, row) in trace.chunks_exact_mut(trace_width).enumerate() { - // doing the 64 rounds in 16 rows - if i < 16 { - let cols: &mut Sha256RoundCols = - row[get_range(trace_start_col, SHA256_ROUND_WIDTH)].borrow_mut(); - cols.flags.is_round_row = F::ONE; - cols.flags.is_first_4_rows = if i < 4 { F::ONE } else { F::ZERO }; - cols.flags.is_digest_row = F::ZERO; - cols.flags.is_last_block = F::from_bool(is_last_block); - cols.flags.row_idx = - get_flag_pt_array(&self.row_idx_encoder, i).map(F::from_canonical_u32); - cols.flags.global_block_idx = F::from_canonical_u32(global_block_idx); - cols.flags.local_block_idx = F::from_canonical_u32(local_block_idx); - - // W_idx = M_idx - if i < SHA256_ROWS_PER_BLOCK / SHA256_ROUNDS_PER_ROW { - for j in 0..SHA256_ROUNDS_PER_ROW { - cols.message_schedule.w[j] = u32_into_limbs::( - input[i * SHA256_ROUNDS_PER_ROW + j], - ) - .map(F::from_canonical_u32); - cols.message_schedule.carry_or_buffer[j] = - array::from_fn(|k| buffer_vals[i][j * SHA256_WORD_U16S * 2 + k]); - } - } - // W_idx = SIG1(W_{idx-2}) + W_{idx-7} + SIG0(W_{idx-15}) + W_{idx-16} - else { - for j in 0..SHA256_ROUNDS_PER_ROW { - let idx = i * SHA256_ROUNDS_PER_ROW + j; - let nums: [u32; 4] = [ - small_sig1(message_schedule[idx - 2]), - message_schedule[idx - 7], - small_sig0(message_schedule[idx - 15]), - message_schedule[idx - 16], - ]; - let w: u32 = nums.iter().fold(0, |acc, &num| acc.wrapping_add(num)); - cols.message_schedule.w[j] = - u32_into_limbs::(w).map(F::from_canonical_u32); - - let nums_limbs = nums - .iter() - .map(|x| u32_into_limbs::(*x)) - .collect::>(); - let w_limbs = u32_into_limbs::(w); - - // fill in the carrys - for k in 0..SHA256_WORD_U16S { - let mut sum = nums_limbs.iter().fold(0, |acc, num| acc + num[k]); - if k > 0 { - sum += (cols.message_schedule.carry_or_buffer[j][k * 2 - 2] - + F::TWO * cols.message_schedule.carry_or_buffer[j][k * 2 - 1]) - .as_canonical_u32(); - } - let carry = (sum - w_limbs[k]) >> 16; - cols.message_schedule.carry_or_buffer[j][k * 2] = - F::from_canonical_u32(carry & 1); - cols.message_schedule.carry_or_buffer[j][k * 2 + 1] = - F::from_canonical_u32(carry >> 1); - } - // update the message schedule - message_schedule[idx] = w; - } - } - // fill in the work variables - for j in 0..SHA256_ROUNDS_PER_ROW { - // t1 = h + SIG1(e) + ch(e, f, g) + K_idx + W_idx - let t1 = [ - work_vars[7], - big_sig1(work_vars[4]), - ch(work_vars[4], work_vars[5], work_vars[6]), - SHA256_K[i * SHA256_ROUNDS_PER_ROW + j], - limbs_into_u32(cols.message_schedule.w[j].map(|f| f.as_canonical_u32())), - ]; - let t1_sum: u32 = t1.iter().fold(0, |acc, &num| acc.wrapping_add(num)); - - // t2 = SIG0(a) + maj(a, b, c) - let t2 = [ - big_sig0(work_vars[0]), - maj(work_vars[0], work_vars[1], work_vars[2]), - ]; - - let t2_sum: u32 = t2.iter().fold(0, |acc, &num| acc.wrapping_add(num)); - - // e = d + t1 - let e = work_vars[3].wrapping_add(t1_sum); - cols.work_vars.e[j] = - u32_into_limbs::(e).map(F::from_canonical_u32); - let e_limbs = u32_into_limbs::(e); - // a = t1 + t2 - let a = t1_sum.wrapping_add(t2_sum); - cols.work_vars.a[j] = - u32_into_limbs::(a).map(F::from_canonical_u32); - let a_limbs = u32_into_limbs::(a); - // fill in the carrys - for k in 0..SHA256_WORD_U16S { - let t1_limb = t1.iter().fold(0, |acc, &num| { - acc + u32_into_limbs::(num)[k] - }); - let t2_limb = t2.iter().fold(0, |acc, &num| { - acc + u32_into_limbs::(num)[k] - }); - - let mut e_limb = - t1_limb + u32_into_limbs::(work_vars[3])[k]; - let mut a_limb = t1_limb + t2_limb; - if k > 0 { - a_limb += cols.work_vars.carry_a[j][k - 1].as_canonical_u32(); - e_limb += cols.work_vars.carry_e[j][k - 1].as_canonical_u32(); - } - let carry_a = (a_limb - a_limbs[k]) >> 16; - let carry_e = (e_limb - e_limbs[k]) >> 16; - cols.work_vars.carry_a[j][k] = F::from_canonical_u32(carry_a); - cols.work_vars.carry_e[j][k] = F::from_canonical_u32(carry_e); - bitwise_lookup_chip.request_range(carry_a, carry_e); - } - - // update working variables - work_vars[7] = work_vars[6]; - work_vars[6] = work_vars[5]; - work_vars[5] = work_vars[4]; - work_vars[4] = e; - work_vars[3] = work_vars[2]; - work_vars[2] = work_vars[1]; - work_vars[1] = work_vars[0]; - work_vars[0] = a; - } - - // filling w_3 and intermed_4 here and the rest later - if i > 0 { - for j in 0..SHA256_ROUNDS_PER_ROW { - let idx = i * SHA256_ROUNDS_PER_ROW + j; - let w_4 = u32_into_limbs::(message_schedule[idx - 4]); - let sig_0_w_3 = u32_into_limbs::(small_sig0( - message_schedule[idx - 3], - )); - cols.schedule_helper.intermed_4[j] = - array::from_fn(|k| F::from_canonical_u32(w_4[k] + sig_0_w_3[k])); - if j < SHA256_ROUNDS_PER_ROW - 1 { - let w_3 = message_schedule[idx - 3]; - cols.schedule_helper.w_3[j] = - u32_into_limbs::(w_3).map(F::from_canonical_u32); - } - } - } - } - // generate the digest row - else { - let cols: &mut Sha256DigestCols = - row[get_range(trace_start_col, SHA256_DIGEST_WIDTH)].borrow_mut(); - for j in 0..SHA256_ROUNDS_PER_ROW - 1 { - let w_3 = message_schedule[i * SHA256_ROUNDS_PER_ROW + j - 3]; - cols.schedule_helper.w_3[j] = - u32_into_limbs::(w_3).map(F::from_canonical_u32); - } - cols.flags.is_round_row = F::ZERO; - cols.flags.is_first_4_rows = F::ZERO; - cols.flags.is_digest_row = F::ONE; - cols.flags.is_last_block = F::from_bool(is_last_block); - cols.flags.row_idx = - get_flag_pt_array(&self.row_idx_encoder, 16).map(F::from_canonical_u32); - cols.flags.global_block_idx = F::from_canonical_u32(global_block_idx); - - cols.flags.local_block_idx = F::from_canonical_u32(local_block_idx); - let final_hash: [u32; SHA256_HASH_WORDS] = - array::from_fn(|i| work_vars[i].wrapping_add(prev_hash[i])); - let final_hash_limbs: [[u32; SHA256_WORD_U8S]; SHA256_HASH_WORDS] = - array::from_fn(|i| u32_into_limbs::(final_hash[i])); - // need to ensure final hash limbs are bytes, in order for - // prev_hash[i] + work_vars[i] == final_hash[i] - // to be constrained correctly - for word in final_hash_limbs.iter() { - for chunk in word.chunks(2) { - bitwise_lookup_chip.request_range(chunk[0], chunk[1]); - } - } - cols.final_hash = array::from_fn(|i| { - array::from_fn(|j| F::from_canonical_u32(final_hash_limbs[i][j])) - }); - cols.prev_hash = prev_hash - .map(|f| u32_into_limbs::(f).map(F::from_canonical_u32)); - let hash = if is_last_block { - SHA256_H.map(u32_into_limbs::) - } else { - cols.final_hash - .map(|f| limbs_into_u32(f.map(|x| x.as_canonical_u32()))) - .map(u32_into_limbs::) - } - .map(|x| x.map(F::from_canonical_u32)); - - for i in 0..SHA256_ROUNDS_PER_ROW { - cols.hash.a[i] = hash[SHA256_ROUNDS_PER_ROW - i - 1]; - cols.hash.e[i] = hash[SHA256_ROUNDS_PER_ROW - i + 3]; - } - } - } - - for i in 0..SHA256_ROWS_PER_BLOCK - 1 { - let rows = &mut trace[i * trace_width..(i + 2) * trace_width]; - let (local, next) = rows.split_at_mut(trace_width); - let local_cols: &mut Sha256RoundCols = - local[get_range(trace_start_col, SHA256_ROUND_WIDTH)].borrow_mut(); - let next_cols: &mut Sha256RoundCols = - next[get_range(trace_start_col, SHA256_ROUND_WIDTH)].borrow_mut(); - if i > 0 { - for j in 0..SHA256_ROUNDS_PER_ROW { - next_cols.schedule_helper.intermed_8[j] = - local_cols.schedule_helper.intermed_4[j]; - if (2..SHA256_ROWS_PER_BLOCK - 3).contains(&i) { - next_cols.schedule_helper.intermed_12[j] = - local_cols.schedule_helper.intermed_8[j]; - } - } - } - if i == SHA256_ROWS_PER_BLOCK - 2 { - // `next` is a digest row. - // Fill in `carry_a` and `carry_e` with dummy values so the constraints on `a` and `e` hold. - Self::generate_carry_ae(local_cols, next_cols); - // Fill in row 16's `intermed_4` with dummy values so the message schedule constraints holds on that row - Self::generate_intermed_4(local_cols, next_cols); - } - if i <= 2 { - // i is in 0..3. - // Fill in `local.intermed_12` with dummy values so the message schedule constraints hold on rows 1..4. - Self::generate_intermed_12(local_cols, next_cols); - } - } - } - - /// This function will fill in the cells that we couldn't do during the first pass. - /// This function should be called only after `generate_block_trace` was called for all blocks - /// And [`Self::generate_default_row`] is called for all invalid rows - /// Will populate the missing values of `trace`, where the width of the trace is `trace_width` - /// and the starting column for the `Sha256Air` is `trace_start_col`. - /// Note: `trace` needs to be the rows 1..17 of a block and the first row of the next block - pub fn generate_missing_cells( - &self, - trace: &mut [F], - trace_width: usize, - trace_start_col: usize, - ) { - // Here row_17 = next blocks row 0 - let rows_15_17 = &mut trace[14 * trace_width..17 * trace_width]; - let (row_15, row_16_17) = rows_15_17.split_at_mut(trace_width); - let (row_16, row_17) = row_16_17.split_at_mut(trace_width); - let cols_15: &mut Sha256RoundCols = - row_15[trace_start_col..trace_start_col + SHA256_ROUND_WIDTH].borrow_mut(); - let cols_16: &mut Sha256RoundCols = - row_16[trace_start_col..trace_start_col + SHA256_ROUND_WIDTH].borrow_mut(); - let cols_17: &mut Sha256RoundCols = - row_17[trace_start_col..trace_start_col + SHA256_ROUND_WIDTH].borrow_mut(); - // Fill in row 15's `intermed_12` with dummy values so the message schedule constraints holds on row 16 - Self::generate_intermed_12(cols_15, cols_16); - // Fill in row 16's `intermed_12` with dummy values so the message schedule constraints holds on the next block's row 0 - Self::generate_intermed_12(cols_16, cols_17); - // Fill in row 0's `intermed_4` with dummy values so the message schedule constraints holds on that row - Self::generate_intermed_4(cols_16, cols_17); - } - - /// Fills the `cols` as a padding row - /// Note: we still need to correctly fill in the hash values, carries and intermeds - pub fn generate_default_row(self: &Sha256Air, cols: &mut Sha256RoundCols) { - cols.flags.is_round_row = F::ZERO; - cols.flags.is_first_4_rows = F::ZERO; - cols.flags.is_digest_row = F::ZERO; - - cols.flags.is_last_block = F::ZERO; - cols.flags.global_block_idx = F::ZERO; - cols.flags.row_idx = - get_flag_pt_array(&self.row_idx_encoder, 17).map(F::from_canonical_u32); - cols.flags.local_block_idx = F::ZERO; - - cols.message_schedule.w = [[F::ZERO; SHA256_WORD_BITS]; SHA256_ROUNDS_PER_ROW]; - cols.message_schedule.carry_or_buffer = - [[F::ZERO; SHA256_WORD_U16S * 2]; SHA256_ROUNDS_PER_ROW]; - - let hash = SHA256_H - .map(u32_into_limbs::) - .map(|x| x.map(F::from_canonical_u32)); - - for i in 0..SHA256_ROUNDS_PER_ROW { - cols.work_vars.a[i] = hash[SHA256_ROUNDS_PER_ROW - i - 1]; - cols.work_vars.e[i] = hash[SHA256_ROUNDS_PER_ROW - i + 3]; - } - - cols.work_vars.carry_a = array::from_fn(|i| { - array::from_fn(|j| F::from_canonical_u32(SHA256_INVALID_CARRY_A[i][j])) - }); - cols.work_vars.carry_e = array::from_fn(|i| { - array::from_fn(|j| F::from_canonical_u32(SHA256_INVALID_CARRY_E[i][j])) - }); - } - - /// The following functions do the calculations in native field since they will be called on padding rows - /// which can overflow and we need to make sure it matches the AIR constraints - /// Puts the correct carrys in the `next_row`, the resulting carrys can be out of bound - fn generate_carry_ae( - local_cols: &Sha256RoundCols, - next_cols: &mut Sha256RoundCols, - ) { - let a = [local_cols.work_vars.a, next_cols.work_vars.a].concat(); - let e = [local_cols.work_vars.e, next_cols.work_vars.e].concat(); - for i in 0..SHA256_ROUNDS_PER_ROW { - let cur_a = a[i + 4]; - let sig_a = big_sig0_field::(&a[i + 3]); - let maj_abc = maj_field::(&a[i + 3], &a[i + 2], &a[i + 1]); - let d = a[i]; - let cur_e = e[i + 4]; - let sig_e = big_sig1_field::(&e[i + 3]); - let ch_efg = ch_field::(&e[i + 3], &e[i + 2], &e[i + 1]); - let h = e[i]; - - let t1 = [h, sig_e, ch_efg]; - let t2 = [sig_a, maj_abc]; - for j in 0..SHA256_WORD_U16S { - let t1_limb_sum = t1.iter().fold(F::ZERO, |acc, x| { - acc + compose::(&x[j * 16..(j + 1) * 16], 1) - }); - let t2_limb_sum = t2.iter().fold(F::ZERO, |acc, x| { - acc + compose::(&x[j * 16..(j + 1) * 16], 1) - }); - let d_limb = compose::(&d[j * 16..(j + 1) * 16], 1); - let cur_a_limb = compose::(&cur_a[j * 16..(j + 1) * 16], 1); - let cur_e_limb = compose::(&cur_e[j * 16..(j + 1) * 16], 1); - let sum = d_limb - + t1_limb_sum - + if j == 0 { - F::ZERO - } else { - next_cols.work_vars.carry_e[i][j - 1] - } - - cur_e_limb; - let carry_e = sum * (F::from_canonical_u32(1 << 16).inverse()); - - let sum = t1_limb_sum - + t2_limb_sum - + if j == 0 { - F::ZERO - } else { - next_cols.work_vars.carry_a[i][j - 1] - } - - cur_a_limb; - let carry_a = sum * (F::from_canonical_u32(1 << 16).inverse()); - next_cols.work_vars.carry_e[i][j] = carry_e; - next_cols.work_vars.carry_a[i][j] = carry_a; - } - } - } - - /// Puts the correct intermed_4 in the `next_row` - fn generate_intermed_4( - local_cols: &Sha256RoundCols, - next_cols: &mut Sha256RoundCols, - ) { - let w = [local_cols.message_schedule.w, next_cols.message_schedule.w].concat(); - let w_limbs: Vec<[F; SHA256_WORD_U16S]> = w - .iter() - .map(|x| array::from_fn(|i| compose::(&x[i * 16..(i + 1) * 16], 1))) - .collect(); - for i in 0..SHA256_ROUNDS_PER_ROW { - let sig_w = small_sig0_field::(&w[i + 1]); - let sig_w_limbs: [F; SHA256_WORD_U16S] = - array::from_fn(|j| compose::(&sig_w[j * 16..(j + 1) * 16], 1)); - for (j, sig_w_limb) in sig_w_limbs.iter().enumerate() { - next_cols.schedule_helper.intermed_4[i][j] = w_limbs[i][j] + *sig_w_limb; - } - } - } - - /// Puts the needed intermed_12 in the `local_row` - fn generate_intermed_12( - local_cols: &mut Sha256RoundCols, - next_cols: &Sha256RoundCols, - ) { - let w = [local_cols.message_schedule.w, next_cols.message_schedule.w].concat(); - let w_limbs: Vec<[F; SHA256_WORD_U16S]> = w - .iter() - .map(|x| array::from_fn(|i| compose::(&x[i * 16..(i + 1) * 16], 1))) - .collect(); - for i in 0..SHA256_ROUNDS_PER_ROW { - // sig_1(w_{t-2}) - let sig_w_2: [F; SHA256_WORD_U16S] = array::from_fn(|j| { - compose::(&small_sig1_field::(&w[i + 2])[j * 16..(j + 1) * 16], 1) - }); - // w_{t-7} - let w_7 = if i < 3 { - local_cols.schedule_helper.w_3[i] - } else { - w_limbs[i - 3] - }; - // w_t - let w_cur = w_limbs[i + 4]; - for j in 0..SHA256_WORD_U16S { - let carry = next_cols.message_schedule.carry_or_buffer[i][j * 2] - + F::TWO * next_cols.message_schedule.carry_or_buffer[i][j * 2 + 1]; - let sum = sig_w_2[j] + w_7[j] - carry * F::from_canonical_u32(1 << 16) - w_cur[j] - + if j > 0 { - next_cols.message_schedule.carry_or_buffer[i][j * 2 - 2] - + F::from_canonical_u32(2) - * next_cols.message_schedule.carry_or_buffer[i][j * 2 - 1] - } else { - F::ZERO - }; - local_cols.schedule_helper.intermed_12[i][j] = -sum; - } - } - } -} - -/// `records` consists of pairs of `(input_block, is_last_block)`. -pub fn generate_trace( - sub_air: &Sha256Air, - bitwise_lookup_chip: SharedBitwiseOperationLookupChip<8>, - records: Vec<([u8; SHA256_BLOCK_U8S], bool)>, -) -> RowMajorMatrix { - let non_padded_height = records.len() * SHA256_ROWS_PER_BLOCK; - let height = next_power_of_two_or_zero(non_padded_height); - let width = >::width(sub_air); - let mut values = F::zero_vec(height * width); - - struct BlockContext { - prev_hash: [u32; 8], - local_block_idx: u32, - global_block_idx: u32, - input: [u8; SHA256_BLOCK_U8S], - is_last_block: bool, - } - let mut block_ctx: Vec = Vec::with_capacity(records.len()); - let mut prev_hash = SHA256_H; - let mut local_block_idx = 0; - let mut global_block_idx = 1; - for (input, is_last_block) in records { - block_ctx.push(BlockContext { - prev_hash, - local_block_idx, - global_block_idx, - input, - is_last_block, - }); - global_block_idx += 1; - if is_last_block { - local_block_idx = 0; - prev_hash = SHA256_H; - } else { - local_block_idx += 1; - prev_hash = Sha256Air::get_block_hash(&prev_hash, input); - } - } - // first pass - values - .par_chunks_exact_mut(width * SHA256_ROWS_PER_BLOCK) - .zip(block_ctx) - .for_each(|(block, ctx)| { - let BlockContext { - prev_hash, - local_block_idx, - global_block_idx, - input, - is_last_block, - } = ctx; - let input_words = array::from_fn(|i| { - limbs_into_u32::(array::from_fn(|j| { - input[(i + 1) * SHA256_WORD_U8S - j - 1] as u32 - })) - }); - sub_air.generate_block_trace( - block, - width, - 0, - &input_words, - bitwise_lookup_chip.clone(), - &prev_hash, - is_last_block, - global_block_idx, - local_block_idx, - &[[F::ZERO; 16]; 4], - ); - }); - // second pass: padding rows - values[width * non_padded_height..] - .par_chunks_mut(width) - .for_each(|row| { - let cols: &mut Sha256RoundCols = row.borrow_mut(); - sub_air.generate_default_row(cols); - }); - // second pass: non-padding rows - values[width..] - .par_chunks_mut(width * SHA256_ROWS_PER_BLOCK) - .take(non_padded_height / SHA256_ROWS_PER_BLOCK) - .for_each(|chunk| { - sub_air.generate_missing_cells(chunk, width, 0); - }); - RowMajorMatrix::new(values, width) -} diff --git a/crates/circuits/sha256-air/src/utils.rs b/crates/circuits/sha256-air/src/utils.rs deleted file mode 100644 index abf8b6e7f2..0000000000 --- a/crates/circuits/sha256-air/src/utils.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::array; - -pub use openvm_circuit_primitives::utils::compose; -use openvm_circuit_primitives::{ - encoder::Encoder, - utils::{not, select}, -}; -use openvm_stark_backend::{p3_air::AirBuilder, p3_field::FieldAlgebra}; -use rand::{rngs::StdRng, Rng}; - -use super::{Sha256DigestCols, Sha256RoundCols}; - -// ==== Do not change these constants! ==== -/// Number of bits in a SHA256 word -pub const SHA256_WORD_BITS: usize = 32; -/// Number of 16-bit limbs in a SHA256 word -pub const SHA256_WORD_U16S: usize = SHA256_WORD_BITS / 16; -/// Number of 8-bit limbs in a SHA256 word -pub const SHA256_WORD_U8S: usize = SHA256_WORD_BITS / 8; -/// Number of words in a SHA256 block -pub const SHA256_BLOCK_WORDS: usize = 16; -/// Number of cells in a SHA256 block -pub const SHA256_BLOCK_U8S: usize = SHA256_BLOCK_WORDS * SHA256_WORD_U8S; -/// Number of bits in a SHA256 block -pub const SHA256_BLOCK_BITS: usize = SHA256_BLOCK_WORDS * SHA256_WORD_BITS; -/// Number of rows per block -pub const SHA256_ROWS_PER_BLOCK: usize = 17; -/// Number of rounds per row -pub const SHA256_ROUNDS_PER_ROW: usize = 4; -/// Number of words in a SHA256 hash -pub const SHA256_HASH_WORDS: usize = 8; -/// Number of vars needed to encode the row index with [Encoder] -pub const SHA256_ROW_VAR_CNT: usize = 5; -/// Width of the Sha256RoundCols -pub const SHA256_ROUND_WIDTH: usize = Sha256RoundCols::::width(); -/// Width of the Sha256DigestCols -pub const SHA256_DIGEST_WIDTH: usize = Sha256DigestCols::::width(); -/// Size of the buffer of the first 4 rows of a block (each row's size) -pub const SHA256_BUFFER_SIZE: usize = SHA256_ROUNDS_PER_ROW * SHA256_WORD_U16S * 2; -/// Width of the Sha256Cols -pub const SHA256_WIDTH: usize = if SHA256_ROUND_WIDTH > SHA256_DIGEST_WIDTH { - SHA256_ROUND_WIDTH -} else { - SHA256_DIGEST_WIDTH -}; -/// We can notice that `carry_a`'s and `carry_e`'s are always the same on invalid rows -/// To optimize the trace generation of invalid rows, we have those values precomputed here -pub(crate) const SHA256_INVALID_CARRY_A: [[u32; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW] = [ - [1230919683, 1162494304], - [266373122, 1282901987], - [1519718403, 1008990871], - [923381762, 330807052], -]; -pub(crate) const SHA256_INVALID_CARRY_E: [[u32; SHA256_WORD_U16S]; SHA256_ROUNDS_PER_ROW] = [ - [204933122, 1994683449], - [443873282, 1544639095], - [719953922, 1888246508], - [194580482, 1075725211], -]; -/// SHA256 constant K's -pub const SHA256_K: [u32; 64] = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, -]; - -/// SHA256 initial hash values -pub const SHA256_H: [u32; 8] = [ - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, -]; - -/// Convert a u32 into a list of limbs in little endian -pub fn u32_into_limbs(num: u32) -> [u32; NUM_LIMBS] { - let limb_bits = 32 / NUM_LIMBS; - array::from_fn(|i| (num >> (limb_bits * i)) & ((1 << limb_bits) - 1)) -} - -/// Convert a list of limbs in little endian into a u32 -pub fn limbs_into_u32(limbs: [u32; NUM_LIMBS]) -> u32 { - let limb_bits = 32 / NUM_LIMBS; - limbs - .iter() - .rev() - .fold(0, |acc, &limb| (acc << limb_bits) | limb) -} - -/// Rotates `bits` right by `n` bits, assumes `bits` is in little-endian -#[inline] -pub(crate) fn rotr( - bits: &[impl Into + Clone; SHA256_WORD_BITS], - n: usize, -) -> [F; SHA256_WORD_BITS] { - array::from_fn(|i| bits[(i + n) % SHA256_WORD_BITS].clone().into()) -} - -/// Shifts `bits` right by `n` bits, assumes `bits` is in little-endian -#[inline] -pub(crate) fn shr( - bits: &[impl Into + Clone; SHA256_WORD_BITS], - n: usize, -) -> [F; SHA256_WORD_BITS] { - array::from_fn(|i| { - if i + n < SHA256_WORD_BITS { - bits[i + n].clone().into() - } else { - F::ZERO - } - }) -} - -/// Computes x ^ y ^ z, where x, y, z are assumed to be boolean -#[inline] -pub(crate) fn xor_bit( - x: impl Into, - y: impl Into, - z: impl Into, -) -> F { - let (x, y, z) = (x.into(), y.into(), z.into()); - (x.clone() * y.clone() * z.clone()) - + (x.clone() * not::(y.clone()) * not::(z.clone())) - + (not::(x.clone()) * y.clone() * not::(z.clone())) - + (not::(x) * not::(y) * z) -} - -/// Computes x ^ y ^ z, where x, y, z are [SHA256_WORD_BITS] bit numbers -#[inline] -pub(crate) fn xor( - x: &[impl Into + Clone; SHA256_WORD_BITS], - y: &[impl Into + Clone; SHA256_WORD_BITS], - z: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - array::from_fn(|i| xor_bit(x[i].clone(), y[i].clone(), z[i].clone())) -} - -/// Choose function from SHA256 -#[inline] -pub fn ch(x: u32, y: u32, z: u32) -> u32 { - (x & y) ^ ((!x) & z) -} - -/// Computes Ch(x,y,z), where x, y, z are [SHA256_WORD_BITS] bit numbers -#[inline] -pub(crate) fn ch_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], - y: &[impl Into + Clone; SHA256_WORD_BITS], - z: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - array::from_fn(|i| select(x[i].clone(), y[i].clone(), z[i].clone())) -} - -/// Majority function from SHA256 -pub fn maj(x: u32, y: u32, z: u32) -> u32 { - (x & y) ^ (x & z) ^ (y & z) -} - -/// Computes Maj(x,y,z), where x, y, z are [SHA256_WORD_BITS] bit numbers -#[inline] -pub(crate) fn maj_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], - y: &[impl Into + Clone; SHA256_WORD_BITS], - z: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - array::from_fn(|i| { - let (x, y, z) = ( - x[i].clone().into(), - y[i].clone().into(), - z[i].clone().into(), - ); - x.clone() * y.clone() + x.clone() * z.clone() + y.clone() * z.clone() - F::TWO * x * y * z - }) -} - -/// Big sigma_0 function from SHA256 -pub fn big_sig0(x: u32) -> u32 { - x.rotate_right(2) ^ x.rotate_right(13) ^ x.rotate_right(22) -} - -/// Computes BigSigma0(x), where x is a [SHA256_WORD_BITS] bit number in little-endian -#[inline] -pub(crate) fn big_sig0_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - xor(&rotr::(x, 2), &rotr::(x, 13), &rotr::(x, 22)) -} - -/// Big sigma_1 function from SHA256 -pub fn big_sig1(x: u32) -> u32 { - x.rotate_right(6) ^ x.rotate_right(11) ^ x.rotate_right(25) -} - -/// Computes BigSigma1(x), where x is a [SHA256_WORD_BITS] bit number in little-endian -#[inline] -pub(crate) fn big_sig1_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - xor(&rotr::(x, 6), &rotr::(x, 11), &rotr::(x, 25)) -} - -/// Small sigma_0 function from SHA256 -pub fn small_sig0(x: u32) -> u32 { - x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) -} - -/// Computes SmallSigma0(x), where x is a [SHA256_WORD_BITS] bit number in little-endian -#[inline] -pub(crate) fn small_sig0_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - xor(&rotr::(x, 7), &rotr::(x, 18), &shr::(x, 3)) -} - -/// Small sigma_1 function from SHA256 -pub fn small_sig1(x: u32) -> u32 { - x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) -} - -/// Computes SmallSigma1(x), where x is a [SHA256_WORD_BITS] bit number in little-endian -#[inline] -pub(crate) fn small_sig1_field( - x: &[impl Into + Clone; SHA256_WORD_BITS], -) -> [F; SHA256_WORD_BITS] { - xor(&rotr::(x, 17), &rotr::(x, 19), &shr::(x, 10)) -} - -/// Generate a random message of a given length -pub fn get_random_message(rng: &mut StdRng, len: usize) -> Vec { - let mut random_message: Vec = vec![0u8; len]; - rng.fill(&mut random_message[..]); - random_message -} - -/// Wrapper of `get_flag_pt` to get the flag pointer as an array -pub fn get_flag_pt_array(encoder: &Encoder, flag_idx: usize) -> [u32; N] { - encoder.get_flag_pt(flag_idx).try_into().unwrap() -} - -/// Constrain the addition of [SHA256_WORD_BITS] bit words in 16-bit limbs -/// It takes in the terms some in bits some in 16-bit limbs, -/// the expected sum in bits and the carries -pub fn constraint_word_addition( - builder: &mut AB, - terms_bits: &[&[impl Into + Clone; SHA256_WORD_BITS]], - terms_limb: &[&[impl Into + Clone; SHA256_WORD_U16S]], - expected_sum: &[impl Into + Clone; SHA256_WORD_BITS], - carries: &[impl Into + Clone; SHA256_WORD_U16S], -) { - for i in 0..SHA256_WORD_U16S { - let mut limb_sum = if i == 0 { - AB::Expr::ZERO - } else { - carries[i - 1].clone().into() - }; - for term in terms_bits { - limb_sum += compose::(&term[i * 16..(i + 1) * 16], 1); - } - for term in terms_limb { - limb_sum += term[i].clone().into(); - } - let expected_sum_limb = compose::(&expected_sum[i * 16..(i + 1) * 16], 1) - + carries[i].clone().into() * AB::Expr::from_canonical_u32(1 << 16); - builder.assert_eq(limb_sum, expected_sum_limb); - } -} diff --git a/extensions/sha256/circuit/Cargo.toml b/extensions/sha256/circuit/Cargo.toml index 0c7100e99b..388cdf17ad 100644 --- a/extensions/sha256/circuit/Cargo.toml +++ b/extensions/sha256/circuit/Cargo.toml @@ -15,7 +15,7 @@ openvm-circuit = { workspace = true } openvm-instructions = { workspace = true } openvm-sha256-transpiler = { workspace = true } openvm-rv32im-circuit = { workspace = true } -openvm-sha256-air = { workspace = true } +openvm-sha-air = { workspace = true } derive-new.workspace = true derive_more = { workspace = true, features = ["from"] } diff --git a/extensions/sha256/circuit/src/sha256_chip/air.rs b/extensions/sha256/circuit/src/sha256_chip/air.rs index 0487314ea0..218bbe5603 100644 --- a/extensions/sha256/circuit/src/sha256_chip/air.rs +++ b/extensions/sha256/circuit/src/sha256_chip/air.rs @@ -11,11 +11,11 @@ use openvm_instructions::{ riscv::{RV32_CELL_BITS, RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS}, LocalOpcode, }; -use openvm_sha256_air::{ +use openvm_sha256_transpiler::Rv32Sha256Opcode; +use openvm_sha_air::{ compose, Sha256Air, SHA256_BLOCK_U8S, SHA256_HASH_WORDS, SHA256_ROUNDS_PER_ROW, SHA256_WORD_U16S, SHA256_WORD_U8S, }; -use openvm_sha256_transpiler::Rv32Sha256Opcode; use openvm_stark_backend::{ interaction::InteractionBuilder, p3_air::{Air, AirBuilder, BaseAir}, diff --git a/extensions/sha256/circuit/src/sha256_chip/columns.rs b/extensions/sha256/circuit/src/sha256_chip/columns.rs index a24014e51c..5c98f4de97 100644 --- a/extensions/sha256/circuit/src/sha256_chip/columns.rs +++ b/extensions/sha256/circuit/src/sha256_chip/columns.rs @@ -6,7 +6,7 @@ use openvm_circuit::{ }; use openvm_circuit_primitives::AlignedBorrow; use openvm_instructions::riscv::RV32_REGISTER_NUM_LIMBS; -use openvm_sha256_air::{Sha256DigestCols, Sha256RoundCols}; +use openvm_sha_air::{Sha256DigestCols, Sha256RoundCols}; use super::{SHA256_REGISTER_READS, SHA256_WRITE_SIZE}; diff --git a/extensions/sha256/circuit/src/sha256_chip/mod.rs b/extensions/sha256/circuit/src/sha256_chip/mod.rs index 5b9bf7bb46..6c8730b605 100644 --- a/extensions/sha256/circuit/src/sha256_chip/mod.rs +++ b/extensions/sha256/circuit/src/sha256_chip/mod.rs @@ -19,8 +19,8 @@ use openvm_instructions::{ LocalOpcode, }; use openvm_rv32im_circuit::adapters::read_rv32_register; -use openvm_sha256_air::{Sha256Air, SHA256_BLOCK_BITS}; use openvm_sha256_transpiler::Rv32Sha256Opcode; +use openvm_sha_air::{Sha256Air, SHA256_BLOCK_BITS}; use openvm_stark_backend::{interaction::BusIndex, p3_field::PrimeField32}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; diff --git a/extensions/sha256/circuit/src/sha256_chip/tests.rs b/extensions/sha256/circuit/src/sha256_chip/tests.rs index 55bc076e2c..db6199c813 100644 --- a/extensions/sha256/circuit/src/sha256_chip/tests.rs +++ b/extensions/sha256/circuit/src/sha256_chip/tests.rs @@ -6,8 +6,8 @@ use openvm_circuit_primitives::bitwise_op_lookup::{ BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip, }; use openvm_instructions::{instruction::Instruction, riscv::RV32_CELL_BITS, LocalOpcode}; -use openvm_sha256_air::get_random_message; use openvm_sha256_transpiler::Rv32Sha256Opcode::{self, *}; +use openvm_sha_air::get_random_message; use openvm_stark_backend::{interaction::BusIndex, p3_field::FieldAlgebra}; use openvm_stark_sdk::{config::setup_tracing, p3_baby_bear::BabyBear, utils::create_seeded_rng}; use rand::{rngs::StdRng, Rng}; diff --git a/extensions/sha256/circuit/src/sha256_chip/trace.rs b/extensions/sha256/circuit/src/sha256_chip/trace.rs index 0d51a21368..746385f16a 100644 --- a/extensions/sha256/circuit/src/sha256_chip/trace.rs +++ b/extensions/sha256/circuit/src/sha256_chip/trace.rs @@ -3,7 +3,7 @@ use std::{array, borrow::BorrowMut, sync::Arc}; use openvm_circuit_primitives::utils::next_power_of_two_or_zero; use openvm_instructions::riscv::{RV32_CELL_BITS, RV32_REGISTER_NUM_LIMBS}; use openvm_rv32im_circuit::adapters::compose; -use openvm_sha256_air::{ +use openvm_sha_air::{ get_flag_pt_array, limbs_into_u32, Sha256Air, SHA256_BLOCK_WORDS, SHA256_BUFFER_SIZE, SHA256_H, SHA256_HASH_WORDS, SHA256_ROWS_PER_BLOCK, SHA256_WORD_U8S, };