diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 000000000..cc5282852 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,61 @@ +# Runs fuzz targets with short execution time for CI smoke testing. +# This is not a replacement for dedicated fuzzing runs, but catches obvious regressions. +# Runs on a schedule (daily) rather than every PR to save CI resources. + +name: fuzz + +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +on: + schedule: + # Run daily at 2 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: # Allow manual triggering + +permissions: + contents: read + +jobs: + fuzz-miden-serde-utils: + name: fuzz miden-serde-utils (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [primitives, collections, string, vint64, goldilocks, budgeted] + timeout-minutes: 15 + steps: + - uses: actions/checkout@main + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-fuzz + run: cargo install cargo-fuzz --locked + - name: Run fuzz target (smoke test) + working-directory: miden-serde-utils + run: | + cargo fuzz run ${{ matrix.target }} -- -max_total_time=60 -runs=10000 + + fuzz-miden-crypto: + name: fuzz miden-crypto (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [word, merkle, smt_serde] + timeout-minutes: 15 + steps: + - uses: actions/checkout@main + - name: Cleanup large tools for build space + uses: ./.github/actions/cleanup-runner + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-fuzz + run: cargo install cargo-fuzz --locked + - name: Run fuzz target (smoke test) + run: | + # Build the fuzz target first + cargo fuzz build --fuzz-dir miden-crypto-fuzz ${{ matrix.target }} + # Run directly to avoid cargo-fuzz wrapper SIGPIPE issue + miden-crypto-fuzz/target/x86_64-unknown-linux-gnu/release/${{ matrix.target }} -max_total_time=60 -runs=10000 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3670487cc..eac85c607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.21.0 (2026-01-14) + +- Use more idiomatic Plonky3 APIs ([#743](https://github.com/0xMiden/crypto/pull/743)). +- [BREAKING] Removed `p3-compat` and `winter-compat` features ([#745](https://github.com/0xMiden/crypto/pull/745)). +- Made concurrent feature interact with plonky3's parallel features, replace homegrown iterator macros with p3-maybe-rayon ([#749](https://github.com/0xMiden/crypto/pull/749)). +- Reduced dependency on std in tests, add test helpers to access Rngs in no-std contexts ([#752](https://github.com/0xMiden/crypto/pull/752)). +- [BREAKING] Changed sponge state layout from `[CAPACITY, RATE1, RATE0]` (BE) to `[RATE0, RATE1, CAPACITY]` (LE) ([#755](https://github.com/0xMiden/crypto/pull/755)). +- [BREAKING] Added length-prefixing to Serializable/Deserializable impls for collections, fuzz deserialization for panics ([#757](https://github.com/0xMiden/crypto/pull/757)). +- Added `SmtLeaf::try_from_elements()` ([#773](https://github.com/0xMiden/crypto/pull/773)). +- Copied `WordWrapper` macro from `miden-base` to `miden-crypto-derive`. + # 0.20.1 (2025-12-29) - Added more re-exports from Plonky3 dependencies ([#741](https://github.com/0xMiden/crypto/pull/741)). diff --git a/Cargo.lock b/Cargo.lock index c5739dbd3..47fb32f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bindgen" @@ -145,15 +145,16 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", ] [[package]] @@ -189,9 +190,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -289,9 +290,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -299,9 +300,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -323,9 +324,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -341,9 +342,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "cpufeatures" @@ -594,9 +595,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "flume" @@ -678,9 +679,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -775,9 +776,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -867,9 +868,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -945,7 +946,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miden-crypto" -version = "0.20.1" +version = "0.21.0" dependencies = [ "assert_matches", "blake3", @@ -969,14 +970,16 @@ dependencies = [ "p3-air", "p3-challenger", "p3-field", + "p3-goldilocks", + "p3-maybe-rayon", "p3-miden-air", - "p3-miden-goldilocks", "p3-miden-prover", "p3-symmetric", + "p3-util", "proptest", "rand", "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_hc", "rayon", "rocksdb", @@ -994,7 +997,7 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.20.1" +version = "0.21.0" dependencies = [ "quote", "syn", @@ -1002,12 +1005,10 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.20.1" +version = "0.21.0" dependencies = [ "p3-field", - "p3-miden-goldilocks", - "winter-math", - "winter-utils", + "p3-goldilocks", ] [[package]] @@ -1022,7 +1023,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -1135,9 +1136,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "p3-air" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60414dc4fe4b8676bd4b6136b309185e6b3c006eb5564ef4cf5dfae6d9d47f32" +checksum = "0141a56ed9924ce0265e7e91cd29bbcd230262744b7a7f0c448bfbf212f73182" dependencies = [ "p3-field", "p3-matrix", @@ -1145,9 +1146,9 @@ dependencies = [ [[package]] name = "p3-challenger" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a66da8af6115b9e2df4363cd55efebf2c6d30de0af3e99dac56dd7b77aff24" +checksum = "20e42ba74a49c08c6e99f74cd9b343bfa31aa5721fea55079b18e3fd65f1dcbc" dependencies = [ "p3-field", "p3-maybe-rayon", @@ -1159,9 +1160,9 @@ dependencies = [ [[package]] name = "p3-commit" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95104feb4b9895733f92204ec70ba8944dbab39c39b235c0a00adf1456149619" +checksum = "498211e7b9a0f8366b410b4a9283ae82ff2fc91f473b1c5816aa6e90e74b125d" dependencies = [ "itertools 0.14.0", "p3-challenger", @@ -1174,9 +1175,9 @@ dependencies = [ [[package]] name = "p3-dft" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b2f57569293b9964b1bae68d64e796bfbf3c271718268beb53a0fb761a5819" +checksum = "e63fa5eb1bd12a240089e72ae3fe10350944d9c166d00a3bfd2a1794db65cf5c" dependencies = [ "itertools 0.14.0", "p3-field", @@ -1189,9 +1190,9 @@ dependencies = [ [[package]] name = "p3-field" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56aae7630ff6df83fb7421d5bd97df27620e5f0e29422b7e8f6a294d44cce297" +checksum = "2ebfdb6ef992ae64e9e8f449ac46516ffa584f11afbdf9ee244288c2a633cdf4" dependencies = [ "itertools 0.14.0", "num-bigint", @@ -1203,11 +1204,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "p3-goldilocks" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64716244b5612622d4e78a4f48b74f6d3bb7b4085b7b6b25364b1dfca7198c66" +dependencies = [ + "num-bigint", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand", + "serde", +] + [[package]] name = "p3-interpolation" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0bb6a709b26cead74e7c605f4e51e793642870e54a7c280a05cd66b7914866" +checksum = "1d877565a94a527c89459fc8ccb0eb58769d8c86456575d1315a1651bd24616d" dependencies = [ "p3-field", "p3-matrix", @@ -1217,9 +1237,9 @@ dependencies = [ [[package]] name = "p3-matrix" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d916550e4261126457d4f139fc3156fc796b1cf2f2687bf1c9b269b1efa8ad42" +checksum = "5542f96504dae8100c91398fb1e3f5ec669eb9c73d9e0b018a93b5fe32bad230" dependencies = [ "itertools 0.14.0", "p3-field", @@ -1233,15 +1253,18 @@ dependencies = [ [[package]] name = "p3-maybe-rayon" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db6a290f867061aed54593d48f0dfd7ff2d0f706a603d03209fd0eac79518f3" +checksum = "0e5669ca75645f99cd001e9d0289a4eeff2bc2cd9dc3c6c3aaf22643966e83df" +dependencies = [ + "rayon", +] [[package]] name = "p3-mds" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745a478473a5f3699f76b284378651eaa9d74e74f820b34ea563a4a72ab8a4a6" +checksum = "038763af23df9da653065867fd85b38626079031576c86fd537097e5be6a0da0" dependencies = [ "p3-dft", "p3-field", @@ -1252,39 +1275,20 @@ dependencies = [ [[package]] name = "p3-miden-air" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4217edd6ea816e81c1282c8e052516973582cb86add4729036bdbaabdb38d8f" +checksum = "45a88e6ee9c92ff6c0b64f1ec0d61eda72fb432bda45337d876c46bd43748508" dependencies = [ "p3-air", "p3-field", "p3-matrix", ] -[[package]] -name = "p3-miden-goldilocks" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a883bc797df1c43b06f2d467340625ee736f748928d1ceb402d27eb05e694394" -dependencies = [ - "num-bigint", - "p3-challenger", - "p3-dft", - "p3-field", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", - "p3-util", - "paste", - "rand", - "serde", -] - [[package]] name = "p3-miden-prover" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e043fcf77772ac765e21335d73b513d76ba9b4adb54994d925ba38dcd557811" +checksum = "f05a61c10cc2d6a73e192ac34a9884e4f26bd877f3eaea441d7b7ebfdffdf6c7" dependencies = [ "itertools 0.14.0", "p3-challenger", @@ -1303,9 +1307,9 @@ dependencies = [ [[package]] name = "p3-miden-uni-stark" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6580b5e0fca4e3fb03e9a1d0a70e2068ec26ea04fe84932815a49c02bedf319f" +checksum = "0a78b6a5b5f6bdc55439d343d2a0a2a8e7cb6544b03296f54d2214a84e91e130" dependencies = [ "itertools 0.14.0", "p3-air", @@ -1324,9 +1328,9 @@ dependencies = [ [[package]] name = "p3-monty-31" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f124f989bc5697728a9e71d2094eda673c45a536c6a8b8ec87b7f3660393aad0" +checksum = "57a981d60da3d8cbf8561014e2c186068578405fd69098fa75b43d4afb364a47" dependencies = [ "itertools 0.14.0", "num-bigint", @@ -1348,9 +1352,9 @@ dependencies = [ [[package]] name = "p3-poseidon2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0c96988fd809e7a3086d8d683ddb93c965f8bb08b37c82e3617d12347bf77f" +checksum = "903b73e4f9a7781a18561c74dc169cf03333497b57a8dd02aaeb130c0f386599" dependencies = [ "p3-field", "p3-mds", @@ -1361,9 +1365,9 @@ dependencies = [ [[package]] name = "p3-symmetric" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1c93a83305b291118dec6632357da69f3137d33fc1791225e38fcb615836" +checksum = "3cd788f04e86dd5c35dd87cad29eefdb6371d2fd5f7664451382eeacae3c3ed0" dependencies = [ "itertools 0.14.0", "p3-field", @@ -1372,9 +1376,9 @@ dependencies = [ [[package]] name = "p3-uni-stark" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88335b341c58063007e0f09773a0bc9136b03cc123db61ac4282031e97dbf7cc" +checksum = "68d409704a8cbdb6c77f6b83a05c6b16a3c8a2c00d880146fa34181977a0d3ac" dependencies = [ "itertools 0.14.0", "p3-air", @@ -1392,10 +1396,11 @@ dependencies = [ [[package]] name = "p3-util" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92074eab13c8a30d23ad7bcf99b82787a04c843133a0cba39ca1cf39d434492" +checksum = "663b16021930bc600ecada915c6c3965730a3b9d6a6c23434ccf70bfc29d6881" dependencies = [ + "rayon", "serde", ] @@ -1492,9 +1497,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -1515,9 +1520,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1535,7 +1540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1545,7 +1550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1554,14 +1559,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -1581,7 +1586,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1795,9 +1800,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -1897,9 +1902,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -2176,15 +2181,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winter-math" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" -dependencies = [ - "winter-utils", -] - [[package]] name = "winter-rand-utils" version = "0.13.1" @@ -2219,18 +2215,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2245,6 +2241,6 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zmij" -version = "1.0.2" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4a4e8e9dc5c62d159f04fcdbe07f4c3fb710415aab4754bf11505501e3251d" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/Cargo.toml b/Cargo.toml index 724d01e0b..c71e0f641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,11 @@ keywords = ["crypto", "hash", "merkle", "miden"] license = "MIT OR Apache-2.0" repository = "https://github.com/0xMiden/crypto" rust-version = "1.90" -version = "0.20.1" +version = "0.21.0" [workspace.dependencies] -miden-crypto-derive = { path = "miden-crypto-derive", version = "0.20" } -miden-serde-utils = { features = ["p3-compat", "winter-compat"], path = "miden-serde-utils", version = "0.20" } +miden-crypto-derive = { path = "miden-crypto-derive", version = "0.21" } +miden-serde-utils = { path = "miden-serde-utils", version = "0.21" } [workspace.lints.rust] # Suppress warnings about `cfg(fuzzing)`, which is automatically set when using `cargo-fuzz`. diff --git a/Makefile b/Makefile index 8cee09f21..ff45aa857 100644 --- a/Makefile +++ b/Makefile @@ -149,9 +149,21 @@ bench-large-smt-rocksdb-open: ## Run large SMT benchmarks with rocksdb storage a # --- fuzzing -------------------------------------------------------------------------------- .PHONY: fuzz-smt -fuzz-smt: ## Run fuzzing for SMT +fuzz-smt: ## Run fuzzing for SMT (sequential vs parallel consistency) cargo +nightly fuzz run smt --release --fuzz-dir miden-crypto-fuzz -- -max_len=10485760 +.PHONY: fuzz-word +fuzz-word: ## Run fuzzing for Word serialization + cargo +nightly fuzz run word --release --fuzz-dir miden-crypto-fuzz + +.PHONY: fuzz-merkle +fuzz-merkle: ## Run fuzzing for Merkle tree serialization + cargo +nightly fuzz run merkle --release --fuzz-dir miden-crypto-fuzz + +.PHONY: fuzz-smt-serde +fuzz-smt-serde: ## Run fuzzing for SMT serialization + cargo +nightly fuzz run smt_serde --release --fuzz-dir miden-crypto-fuzz + # --- installing ---------------------------------------------------------------------------------- .PHONY: check-tools diff --git a/miden-crypto-derive/src/lib.rs b/miden-crypto-derive/src/lib.rs index 1975588a9..9c5b080ed 100644 --- a/miden-crypto-derive/src/lib.rs +++ b/miden-crypto-derive/src/lib.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{DeriveInput, parse_macro_input}; +use syn::{Data, DeriveInput, Fields, Type, parse_macro_input}; + +// SILENT DEBUG MACRO +// ================================================================================================ /// Derives a Debug implementation that elides secret values. /// @@ -37,6 +40,9 @@ pub fn silent_debug(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +// SILENT DISPLAY MACRO +// ================================================================================================ + /// Derives a Display implementation that elides secret values. /// /// This macro generates a Display implementation that outputs `` @@ -72,3 +78,159 @@ pub fn silent_display(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } + +// WORD WRAPPER MACRO +// ================================================================================================ + +/// Generates accessor methods for tuple structs wrapping a `Word` type. +/// +/// Automatically implements: +/// - `from_raw(Word) -> Self` - Construct without further checks +/// - `as_elements(&self) -> &[Felt]` - Returns the elements representation +/// - `as_bytes(&self) -> [u8; 32]` - Returns the byte representation +/// - `to_hex(&self) -> String` - Returns a big-endian, hex-encoded string +/// - `as_word(&self) -> Word` - Returns the underlying Word +/// +/// Note: This macro does NOT generate `From` trait implementations. If you need conversions +/// to/from `Word` or `[u8; 32]`, implement them manually for your type. +/// +/// # Example +/// +/// ```ignore +/// #[derive(WordWrapper)] +/// pub struct NoteId(Word); +/// ``` +/// +/// This will generate implementations equivalent to: +/// +/// ```ignore +/// impl NoteId { +/// /// Construct without further checks from a given `Word` +/// /// +/// /// # Warning +/// /// +/// /// This requires the caller to uphold the guarantees/invariants of this type (if any). +/// /// Check the type-level documentation for guarantees/invariants. +/// pub fn from_raw(word: Word) -> Self { +/// Self(word) +/// } +/// +/// pub fn as_elements(&self) -> &[Felt] { +/// self.0.as_elements() +/// } +/// +/// pub fn as_bytes(&self) -> [u8; 32] { +/// self.0.as_bytes() +/// } +/// +/// pub fn to_hex(&self) -> String { +/// self.0.to_hex() +/// } +/// +/// pub fn as_word(&self) -> Word { +/// self.0 +/// } +/// } +/// ``` +#[proc_macro_derive(WordWrapper)] +pub fn word_wrapper_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + // Validate that this is a tuple struct with a single field + let field_type = match &input.data { + Data::Struct(data_struct) => match &data_struct.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => match fields.unnamed.first() { + Some(field) => &field.ty, + None => { + return syn::Error::new_spanned( + &input, + "WordWrapper requires exactly one field", + ) + .to_compile_error() + .into(); + }, + }, + _ => { + return syn::Error::new_spanned( + &input, + "WordWrapper can only be derived for tuple structs with exactly one field", + ) + .to_compile_error() + .into(); + }, + }, + _ => { + return syn::Error::new_spanned(&input, "WordWrapper can only be derived for structs") + .to_compile_error() + .into(); + }, + }; + + // Verify that the field type is 'Word' (or a path ending in 'Word') + if let Type::Path(type_path) = field_type { + let last_segment = type_path.path.segments.last(); + if let Some(segment) = last_segment { + if segment.ident != "Word" { + return syn::Error::new_spanned( + field_type, + "WordWrapper can only be derived for types wrapping a 'Word' field", + ) + .to_compile_error() + .into(); + } + } else { + return syn::Error::new_spanned( + field_type, + "WordWrapper can only be derived for types wrapping a 'Word' field", + ) + .to_compile_error() + .into(); + } + } else { + return syn::Error::new_spanned( + field_type, + "WordWrapper can only be derived for types wrapping a 'Word' field", + ) + .to_compile_error() + .into(); + } + + let expanded = quote! { + impl #impl_generics #name #ty_generics #where_clause { + /// Construct without further checks from a given `Word`. + /// + /// # Warning + /// + /// This requires the caller to uphold the guarantees/invariants of this type (if any). + /// Check the type-level documentation for guarantees/invariants. + pub fn from_raw(word: Word) -> Self { + Self(word) + } + + /// Returns the elements representation of this value. + pub fn as_elements(&self) -> &[Felt] { + self.0.as_elements() + } + + /// Returns the byte representation of this value. + pub fn as_bytes(&self) -> [u8; 32] { + self.0.as_bytes() + } + + /// Returns a big-endian, hex-encoded string. + pub fn to_hex(&self) -> String { + self.0.to_hex() + } + + /// Returns the underlying word of this value. + pub fn as_word(&self) -> Word { + self.0 + } + } + }; + + TokenStream::from(expanded) +} diff --git a/miden-crypto-fuzz/Cargo.lock b/miden-crypto-fuzz/Cargo.lock index ae429a5f1..c48dfa5f5 100644 --- a/miden-crypto-fuzz/Cargo.lock +++ b/miden-crypto-fuzz/Cargo.lock @@ -50,15 +50,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "blake3" @@ -84,16 +78,17 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" -version = "1.2.33" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -101,9 +96,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" @@ -200,9 +195,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -344,6 +339,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + [[package]] name = "flume" version = "0.11.1" @@ -353,7 +354,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin", + "spin 0.9.8", ] [[package]] @@ -394,20 +395,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] @@ -429,15 +430,16 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", "rayon", "serde", + "serde_core", ] [[package]] @@ -467,21 +469,30 @@ dependencies = [ "generic-array", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -512,9 +523,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libfuzzer-sys" @@ -543,7 +554,7 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.19.0" +version = "0.21.0" dependencies = [ "blake3", "cc", @@ -556,8 +567,18 @@ dependencies = [ "hkdf", "k256", "miden-crypto-derive", + "miden-serde-utils", "num", "num-complex", + "p3-air", + "p3-challenger", + "p3-field", + "p3-maybe-rayon", + "p3-miden-air", + "p3-miden-goldilocks", + "p3-miden-prover", + "p3-symmetric", + "p3-util", "rand", "rand_chacha", "rand_core 0.9.3", @@ -567,15 +588,12 @@ dependencies = [ "sha3", "subtle", "thiserror", - "winter-crypto", - "winter-math", - "winter-utils", "x25519-dalek", ] [[package]] name = "miden-crypto-derive" -version = "0.19.0" +version = "0.21.0" dependencies = [ "quote", "syn", @@ -590,6 +608,14 @@ dependencies = [ "rand", ] +[[package]] +name = "miden-serde-utils" +version = "0.21.0" +dependencies = [ + "p3-field", + "p3-miden-goldilocks", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -685,6 +711,288 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "p3-air" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60414dc4fe4b8676bd4b6136b309185e6b3c006eb5564ef4cf5dfae6d9d47f32" +dependencies = [ + "p3-field", + "p3-matrix", +] + +[[package]] +name = "p3-challenger" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a66da8af6115b9e2df4363cd55efebf2c6d30de0af3e99dac56dd7b77aff24" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-monty-31", + "p3-symmetric", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-commit" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95104feb4b9895733f92204ec70ba8944dbab39c39b235c0a00adf1456149619" +dependencies = [ + "itertools", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-util", + "serde", +] + +[[package]] +name = "p3-dft" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b2f57569293b9964b1bae68d64e796bfbf3c271718268beb53a0fb761a5819" +dependencies = [ + "itertools", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "spin 0.10.0", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56aae7630ff6df83fb7421d5bd97df27620e5f0e29422b7e8f6a294d44cce297" +dependencies = [ + "itertools", + "num-bigint", + "p3-maybe-rayon", + "p3-util", + "paste", + "rand", + "serde", + "tracing", +] + +[[package]] +name = "p3-interpolation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0bb6a709b26cead74e7c605f4e51e793642870e54a7c280a05cd66b7914866" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", +] + +[[package]] +name = "p3-matrix" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d916550e4261126457d4f139fc3156fc796b1cf2f2687bf1c9b269b1efa8ad42" +dependencies = [ + "itertools", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db6a290f867061aed54593d48f0dfd7ff2d0f706a603d03209fd0eac79518f3" +dependencies = [ + "rayon", +] + +[[package]] +name = "p3-mds" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745a478473a5f3699f76b284378651eaa9d74e74f820b34ea563a4a72ab8a4a6" +dependencies = [ + "p3-dft", + "p3-field", + "p3-symmetric", + "p3-util", + "rand", +] + +[[package]] +name = "p3-miden-air" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4217edd6ea816e81c1282c8e052516973582cb86add4729036bdbaabdb38d8f" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", +] + +[[package]] +name = "p3-miden-goldilocks" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a883bc797df1c43b06f2d467340625ee736f748928d1ceb402d27eb05e694394" +dependencies = [ + "num-bigint", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand", + "serde", +] + +[[package]] +name = "p3-miden-prover" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e043fcf77772ac765e21335d73b513d76ba9b4adb54994d925ba38dcd557811" +dependencies = [ + "itertools", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-interpolation", + "p3-matrix", + "p3-maybe-rayon", + "p3-miden-air", + "p3-miden-uni-stark", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-miden-uni-stark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6580b5e0fca4e3fb03e9a1d0a70e2068ec26ea04fe84932815a49c02bedf319f" +dependencies = [ + "itertools", + "p3-air", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-uni-stark", + "p3-util", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "p3-monty-31" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f124f989bc5697728a9e71d2094eda673c45a536c6a8b8ec87b7f3660393aad0" +dependencies = [ + "itertools", + "num-bigint", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand", + "serde", + "spin 0.10.0", + "tracing", + "transpose", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0c96988fd809e7a3086d8d683ddb93c965f8bb08b37c82e3617d12347bf77f" +dependencies = [ + "p3-field", + "p3-mds", + "p3-symmetric", + "p3-util", + "rand", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabf1c93a83305b291118dec6632357da69f3137d33fc1791225e38fcb615836" +dependencies = [ + "itertools", + "p3-field", + "serde", +] + +[[package]] +name = "p3-uni-stark" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88335b341c58063007e0f09773a0bc9136b03cc123db61ac4282031e97dbf7cc" +dependencies = [ + "itertools", + "p3-air", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "p3-util" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92074eab13c8a30d23ad7bcf99b82787a04c843133a0cba39ca1cf39d434492" +dependencies = [ + "rayon", + "serde", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pkcs8" version = "0.10.2" @@ -717,18 +1025,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -774,7 +1082,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -859,18 +1167,28 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -923,6 +1241,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -933,6 +1260,12 @@ dependencies = [ "der", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "subtle" version = "2.6.1" @@ -941,9 +1274,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -952,35 +1285,73 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "universal-hash" @@ -1005,19 +1376,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -1028,9 +1399,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1038,9 +1409,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -1051,48 +1422,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] -name = "winter-crypto" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" -dependencies = [ - "blake3", - "sha3", - "winter-math", - "winter-utils", -] - -[[package]] -name = "winter-math" -version = "0.13.1" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" -dependencies = [ - "winter-utils", -] - -[[package]] -name = "winter-utils" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "x25519-dalek" @@ -1106,18 +1447,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/miden-crypto-fuzz/Cargo.toml b/miden-crypto-fuzz/Cargo.toml index e0eeb10d8..90ab43485 100644 --- a/miden-crypto-fuzz/Cargo.toml +++ b/miden-crypto-fuzz/Cargo.toml @@ -12,6 +12,7 @@ libfuzzer-sys = "0.4" miden-crypto = { features = ["concurrent", "fuzzing"], path = "../miden-crypto" } rand = { default-features = false, version = "0.9" } +# Existing SMT fuzz target (for sequential vs parallel consistency) [[bin]] bench = false doc = false @@ -19,5 +20,27 @@ name = "smt" path = "fuzz_targets/smt.rs" test = false +# Serialization/deserialization fuzz targets +[[bin]] +bench = false +doc = false +name = "word" +path = "fuzz_targets/word.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "merkle" +path = "fuzz_targets/merkle.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "smt_serde" +path = "fuzz_targets/smt_serde.rs" +test = false + [lints.rust] unexpected_cfgs = { check-cfg = ['cfg(fuzzing)'], level = "warn" } diff --git a/miden-crypto-fuzz/fuzz_targets/merkle.rs b/miden-crypto-fuzz/fuzz_targets/merkle.rs new file mode 100644 index 000000000..50a0ee22d --- /dev/null +++ b/miden-crypto-fuzz/fuzz_targets/merkle.rs @@ -0,0 +1,28 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_crypto::{ + utils::Deserializable, + merkle::{MerklePath, PartialMerkleTree}, +}; + +fuzz_target!(|data: &[u8]| { + // Test MerklePath deserialization + let _ = MerklePath::read_from_bytes(data); + + // Test Vec + let _ = Vec::::read_from_bytes(data); + + // Test PartialMerkleTree deserialization + let _ = PartialMerkleTree::read_from_bytes(data); + + // Test Vec + let _ = Vec::::read_from_bytes(data); + + // Test Option + let _ = Option::::read_from_bytes(data); + + // Test arrays of Merkle structures + let _ = <[MerklePath; 1]>::read_from_bytes(data); + let _ = <[MerklePath; 2]>::read_from_bytes(data); +}); diff --git a/miden-crypto-fuzz/fuzz_targets/smt_serde.rs b/miden-crypto-fuzz/fuzz_targets/smt_serde.rs new file mode 100644 index 000000000..1e33bee57 --- /dev/null +++ b/miden-crypto-fuzz/fuzz_targets/smt_serde.rs @@ -0,0 +1,26 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_crypto::{ + utils::Deserializable, + merkle::smt::{PartialSmt, Smt}, +}; + +fuzz_target!(|data: &[u8]| { + // Test Smt deserialization - tests complex tree structure + let _ = Smt::read_from_bytes(data); + + // Test PartialSmt deserialization + let _ = PartialSmt::read_from_bytes(data); + + // Test Vec of SMT types + let _ = Vec::::read_from_bytes(data); + let _ = Vec::::read_from_bytes(data); + + // Test Option + let _ = Option::::read_from_bytes(data); + + // Test arrays + let _ = <[Smt; 1]>::read_from_bytes(data); + let _ = <[PartialSmt; 1]>::read_from_bytes(data); +}); diff --git a/miden-crypto-fuzz/fuzz_targets/word.rs b/miden-crypto-fuzz/fuzz_targets/word.rs new file mode 100644 index 000000000..d2a21c300 --- /dev/null +++ b/miden-crypto-fuzz/fuzz_targets/word.rs @@ -0,0 +1,30 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_crypto::{utils::Deserializable, Word, Felt}; + +fuzz_target!(|data: &[u8]| { + // Test Word deserialization - validates each u64 < field modulus + let _ = Word::read_from_bytes(data); + + // Test Vec - tests length prefix handling with validation + let _ = Vec::::read_from_bytes(data); + + // Test arrays of Words + let _ = <[Word; 1]>::read_from_bytes(data); + let _ = <[Word; 2]>::read_from_bytes(data); + let _ = <[Word; 4]>::read_from_bytes(data); + + // Test Option + let _ = Option::::read_from_bytes(data); + + // Test tuples with Word + let _ = <(Word,)>::read_from_bytes(data); + let _ = <(Word, Word)>::read_from_bytes(data); + let _ = <(Word, u64)>::read_from_bytes(data); + + // Test individual Felt elements + let _ = Felt::read_from_bytes(data); + let _ = Vec::::read_from_bytes(data); + let _ = <[Felt; 4]>::read_from_bytes(data); +}); diff --git a/miden-crypto/Cargo.toml b/miden-crypto/Cargo.toml index 03b8cfd6e..772f32fe1 100644 --- a/miden-crypto/Cargo.toml +++ b/miden-crypto/Cargo.toml @@ -70,7 +70,14 @@ name = "rand" required-features = ["std"] [features] -concurrent = ["dep:rayon", "hashbrown?/rayon", "std"] +concurrent = [ + "dep:rayon", + "hashbrown?/rayon", + "p3-maybe-rayon/parallel", + "p3-miden-prover/parallel", + "p3-util/parallel", + "std", +] default = ["concurrent", "std"] executable = ["concurrent", "dep:clap", "dep:rand-utils"] fuzzing = [] @@ -78,15 +85,8 @@ hashmaps = ["dep:hashbrown"] internal = ["concurrent"] rocksdb = ["concurrent", "dep:rocksdb"] serde = ["dep:serde", "serde?/alloc"] -std = [ - "blake3/std", - "dep:cc", - "miden-serde-utils/p3-compat", - "miden-serde-utils/std", - "miden-serde-utils/winter-compat", - "rand/std", - "rand/thread_rng", -] +std = ["blake3/std", "dep:cc", "miden-serde-utils/std", "rand/std", "rand/thread_rng"] +testing = ["dep:proptest"] [dependencies] blake3 = { default-features = false, version = "1.8" } @@ -102,6 +102,7 @@ miden-crypto-derive.workspace = true miden-serde-utils.workspace = true num = { default-features = false, features = ["alloc", "libm"], version = "0.4" } num-complex = { default-features = false, version = "0.4" } +proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.7" } rand = { default-features = false, version = "0.9" } rand-utils = { optional = true, package = "winter-rand-utils", version = "0.13" } rand_chacha = { default-features = false, version = "0.9" } @@ -117,15 +118,17 @@ thiserror = { default-features = false, version = "2.0" } x25519-dalek = { default-features = false, features = ["static_secrets"], version = "2.0" } # Upstream Plonky3 dependencies -p3-air = { default-features = false, version = "0.4" } -p3-challenger = { default-features = false, version = "0.4" } -p3-field = { default-features = false, version = "0.4" } -p3-symmetric = { default-features = false, version = "0.4" } +p3-air = { default-features = false, version = "0.4.2" } +p3-challenger = { default-features = false, version = "0.4.2" } +p3-field = { default-features = false, version = "0.4.2" } +p3-goldilocks = { default-features = false, version = "0.4.2" } +p3-maybe-rayon = { default-features = false, version = "0.4.2" } +p3-symmetric = { default-features = false, version = "0.4.2" } +p3-util = { default-features = false, version = "0.4.2" } # Miden-specific Plonky3 crates -p3-miden-air = { default-features = false, version = "0.4" } -p3-miden-goldilocks = { default-features = false, version = "0.4" } -p3-miden-prover = { default-features = false, version = "0.4" } +p3-miden-air = { default-features = false, version = "0.4.2" } +p3-miden-prover = { default-features = false, version = "0.4.2" } [dev-dependencies] assert_matches = { default-features = false, version = "1.5" } @@ -134,7 +137,6 @@ hex = { default-features = false, features = ["alloc"], version = "0. itertools = { version = "0.14" } proptest = { default-features = false, features = ["alloc"], version = "1.7" } rand-utils = { package = "winter-rand-utils", version = "0.13" } -rand_chacha = { default-features = false, version = "0.9" } rstest = { version = "0.26" } seq-macro = { version = "0.3" } tempfile = { version = "3.20" } @@ -147,7 +149,7 @@ glob = "0.3" workspace = true [package.metadata.cargo-machete] -ignored = ["getrandom", "rand-utils", "winter-math"] +ignored = ["getrandom", "rand-utils"] [[test]] name = "rocksdb_large_smt" diff --git a/miden-crypto/src/aead/aead_rpo/mod.rs b/miden-crypto/src/aead/aead_rpo/mod.rs index 67d971dc4..38e7c2df2 100644 --- a/miden-crypto/src/aead/aead_rpo/mod.rs +++ b/miden-crypto/src/aead/aead_rpo/mod.rs @@ -12,7 +12,7 @@ use core::ops::Range; use miden_crypto_derive::{SilentDebug, SilentDisplay}; use num::Integer; -use p3_field::{PrimeField64, RawDataSerializable, integers::QuotientMap}; +use p3_field::{PrimeField64, RawDataSerializable}; use rand::{ Rng, distr::{Distribution, StandardUniform, Uniform}, @@ -31,7 +31,7 @@ use crate::{ }, }; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod test; // CONSTANTS @@ -528,7 +528,7 @@ impl SpongeState { squeezed_data } - /// Squeezes an authentication tag + /// Squeezes an authentication tag (from RATE0, the first rate word) fn squeeze_tag(&mut self) -> AuthTag { self.permute(); AuthTag( @@ -653,12 +653,10 @@ impl Deserializable for Nonce { impl Serializable for EncryptedData { fn write_into(&self, target: &mut W) { - // we serialize field elements in their canonical form target.write_u8(self.data_type as u8); - target.write_usize(self.ciphertext.len()); - target.write_many(self.ciphertext.iter().map(Felt::as_canonical_u64)); - target.write_many(self.nonce.0.iter().map(Felt::as_canonical_u64)); - target.write_many(self.auth_tag.0.iter().map(Felt::as_canonical_u64)); + self.ciphertext.write_into(target); + target.write_many(self.nonce.0); + target.write_many(self.auth_tag.0); } } @@ -669,29 +667,14 @@ impl Deserializable for EncryptedData { DeserializationError::InvalidValue("invalid data type value".to_string()) })?; - let ciphertext_len = source.read_usize()?; - let ciphertext_bytes = source.read_many(ciphertext_len)?; - let ciphertext = felts_from_u64(ciphertext_bytes) - .ok_or_else(|| DeserializationError::InvalidValue("invalid ciphertext".into()))?; - - let nonce = source.read_many(NONCE_SIZE)?; - let nonce: [Felt; NONCE_SIZE] = felts_from_u64(nonce) - .ok_or_else(|| DeserializationError::InvalidValue("invalid nonce".into()))? - .try_into() - .map_err(|_| { - DeserializationError::InvalidValue("nonce conversion failed".to_string()) - })?; - - let tag = source.read_many(AUTH_TAG_SIZE)?; - let tag: [Felt; AUTH_TAG_SIZE] = felts_from_u64(tag) - .ok_or_else(|| DeserializationError::InvalidValue("invalid tag".into()))? - .try_into() - .expect("deserialization reads exactly AUTH_TAG_SIZE elements"); + let ciphertext = Vec::::read_from(source)?; + let nonce: [Felt; NONCE_SIZE] = source.read()?; + let auth_tag: [Felt; AUTH_TAG_SIZE] = source.read()?; Ok(Self { ciphertext, nonce: Nonce(nonce), - auth_tag: AuthTag(tag), + auth_tag: AuthTag(auth_tag), data_type, }) } @@ -754,12 +737,6 @@ fn unpad(mut plaintext: Vec) -> Result, EncryptionError> { Ok(plaintext) } -/// Converts a vector of u64 values into a vector of field elements, returning `None` if any of -/// the u64 values is not a valid field element. -fn felts_from_u64(input: Vec) -> Option> { - input.into_iter().map(Felt::from_canonical_checked).collect() -} - // AEAD SCHEME IMPLEMENTATION // ================================================================================================ diff --git a/miden-crypto/src/aead/aead_rpo/test.rs b/miden-crypto/src/aead/aead_rpo/test.rs index 2cad357ea..04d268c43 100644 --- a/miden-crypto/src/aead/aead_rpo/test.rs +++ b/miden-crypto/src/aead/aead_rpo/test.rs @@ -353,9 +353,9 @@ fn test_wrong_nonce_detection() { // SECURITY TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod security_tests { - use std::collections::HashSet; + use alloc::collections::BTreeSet; use super::*; @@ -375,7 +375,7 @@ mod security_tests { fn test_key_uniqueness() { let seed = [0_u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); - let mut keys = HashSet::new(); + let mut keys = BTreeSet::new(); // Generate 1000 keys and ensure they're all unique for _ in 0..1000 { @@ -389,7 +389,7 @@ mod security_tests { fn test_nonce_uniqueness() { let seed = [0_u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); - let mut nonces = HashSet::new(); + let mut nonces = BTreeSet::new(); // Generate 1000 nonces and ensure they're all unique for _ in 0..1000 { diff --git a/miden-crypto/src/aead/xchacha/mod.rs b/miden-crypto/src/aead/xchacha/mod.rs index 8b5cb643b..2f5268aa2 100644 --- a/miden-crypto/src/aead/xchacha/mod.rs +++ b/miden-crypto/src/aead/xchacha/mod.rs @@ -28,7 +28,7 @@ use crate::{ }, }; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod test; // CONSTANTS diff --git a/miden-crypto/src/aead/xchacha/test.rs b/miden-crypto/src/aead/xchacha/test.rs index 4751dc5c4..a276eba8f 100644 --- a/miden-crypto/src/aead/xchacha/test.rs +++ b/miden-crypto/src/aead/xchacha/test.rs @@ -2,7 +2,7 @@ use proptest::{ prelude::{any, prop}, prop_assert_eq, prop_assert_ne, proptest, }; -use rand::{SeedableRng, TryRngCore}; +use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use super::*; @@ -13,15 +13,16 @@ use super::*; proptest! { #[test] fn test_encryption_decryption_roundtrip( + seed in any::(), data_len in 1usize..1000, ) { - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::seed_from_u64(seed); let key = SecretKey::with_rng(&mut rng); let nonce = Nonce::with_rng(&mut rng); // Generate random field elements let data: Vec = (0..data_len) - .map(|_| Felt::new(rng.try_next_u64().unwrap())) + .map(|_| Felt::new(rng.random::())) .collect(); let encrypted = key.encrypt_elements_with_nonce(&data, &[], nonce).unwrap(); @@ -32,19 +33,20 @@ proptest! { #[test] fn test_encryption_decryption_with_ad_roundtrip( + seed in any::(), associated_data_len in 1usize..1000, data_len in 1usize..1000, ) { - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::seed_from_u64(seed); let key = SecretKey::with_rng(&mut rng); let nonce = Nonce::with_rng(&mut rng); // Generate random field elements let associated_data: Vec = (0..associated_data_len) - .map(|_| Felt::new(rng.try_next_u64().unwrap())) + .map(|_| Felt::new(rng.random::())) .collect(); let data: Vec = (0..data_len) - .map(|_| Felt::new(rng.try_next_u64().unwrap())) + .map(|_| Felt::new(rng.random::())) .collect(); let encrypted = key.encrypt_elements_with_nonce(&data, &associated_data, nonce).unwrap(); @@ -55,15 +57,16 @@ proptest! { #[test] fn test_bytes_encryption_decryption_roundtrip( + seed in any::(), data_len in 0usize..1000, ) { - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::seed_from_u64(seed); let key = SecretKey::with_rng(&mut rng); let nonce = Nonce::with_rng(&mut rng); // Generate random bytes let mut data = vec![0_u8; data_len]; - let _ = rng.try_fill_bytes(&mut data); + rng.fill(&mut data[..]); let encrypted = key.encrypt_bytes_with_nonce(&data, &[], nonce).unwrap(); let decrypted = key.decrypt_bytes(&encrypted).unwrap(); @@ -73,19 +76,20 @@ proptest! { #[test] fn test_bytes_encryption_decryption_with_ad_roundtrip( + seed in any::(), associated_data_len in 0usize..1000, data_len in 0usize..1000, ) { - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::seed_from_u64(seed); let key = SecretKey::with_rng(&mut rng); let nonce = Nonce::with_rng(&mut rng); // Generate random bytes let mut associated_data = vec![0_u8; associated_data_len]; - let _ = rng.try_fill_bytes(&mut associated_data); + rng.fill(&mut associated_data[..]); let mut data = vec![0_u8; data_len]; - let _ = rng.try_fill_bytes(&mut data); + rng.fill(&mut data[..]); let encrypted = key.encrypt_bytes_with_nonce(&data, &associated_data, nonce).unwrap(); @@ -96,41 +100,45 @@ proptest! { #[test] fn test_different_keys_different_outputs( + seed1 in any::(), + seed2 in any::(), associated_data in prop::collection::vec(any::(), 1..500), data in prop::collection::vec(any::(), 1..500), ) { - - let mut rng1 = rand::rng(); - let mut rng2 = rand::rng(); + let mut rng1 = ChaCha20Rng::seed_from_u64(seed1); + let mut rng2 = ChaCha20Rng::seed_from_u64(seed2); let key1 = SecretKey::with_rng(&mut rng1); let key2 = SecretKey::with_rng(&mut rng2); let mut nonce_bytes = [0_u8; 24]; - let _ = rng2.try_fill_bytes(&mut nonce_bytes); + rng2.fill(&mut nonce_bytes[..]); let nonce1 = Nonce::from_slice(&nonce_bytes); let nonce2 = Nonce::from_slice(&nonce_bytes); let encrypted1 = key1.encrypt_bytes_with_nonce(&data, &associated_data, nonce1).unwrap(); let encrypted2 = key2.encrypt_bytes_with_nonce(&data, &associated_data, nonce2).unwrap(); - // Different keys should produce different ciphertexts - prop_assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext); + // Different keys should produce different ciphertexts (unless seeds collide) + if seed1 != seed2 { + prop_assert_ne!(encrypted1.ciphertext, encrypted2.ciphertext); + } } #[test] fn test_different_nonces_different_outputs( + seed in any::(), associated_data in prop::collection::vec(any::(), 1..500), data in prop::collection::vec(any::(), 1..500), ) { - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::seed_from_u64(seed); let key = SecretKey::with_rng(&mut rng); let mut nonce_bytes = [0_u8; 24]; - let _ = rng.try_fill_bytes(&mut nonce_bytes); + rng.fill(&mut nonce_bytes[..]); let nonce1 = Nonce::from_slice(&nonce_bytes); - let _ = rng.try_fill_bytes(&mut nonce_bytes); + rng.fill(&mut nonce_bytes[..]); let nonce2 = Nonce::from_slice(&nonce_bytes); - let encrypted1 = key.encrypt_bytes_with_nonce(&data,&associated_data, nonce1).unwrap(); + let encrypted1 = key.encrypt_bytes_with_nonce(&data, &associated_data, nonce1).unwrap(); let encrypted2 = key.encrypt_bytes_with_nonce(&data, &associated_data, nonce2).unwrap(); // Different nonces should produce different ciphertexts (with very high probability) @@ -334,9 +342,9 @@ fn test_wrong_nonce_detection() { // SECURITY TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod security_tests { - use std::collections::HashSet; + use alloc::collections::BTreeSet; use super::*; @@ -345,7 +353,7 @@ mod security_tests { let seed = [0_u8; 32]; let mut rng = ChaCha20Rng::from_seed(seed); - let mut keys = HashSet::new(); + let mut keys = BTreeSet::new(); // Generate 1000 keys and ensure they're all unique for _ in 0..1000 { @@ -361,7 +369,7 @@ mod security_tests { let mut rng = ChaCha20Rng::from_seed(seed); // Generate 1000 nonces and ensure they're all unique - let mut nonces = HashSet::new(); + let mut nonces = BTreeSet::new(); for _ in 0..1000 { let nonce = Nonce::with_rng(&mut rng); let nonce_bytes = format!("{:?}", nonce.inner); diff --git a/miden-crypto/src/dsa/ecdsa_k256_keccak/mod.rs b/miden-crypto/src/dsa/ecdsa_k256_keccak/mod.rs index c59ec38f4..be70b10b7 100644 --- a/miden-crypto/src/dsa/ecdsa_k256_keccak/mod.rs +++ b/miden-crypto/src/dsa/ecdsa_k256_keccak/mod.rs @@ -21,7 +21,7 @@ use crate::{ }, }; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests; // CONSTANTS diff --git a/miden-crypto/src/dsa/ecdsa_k256_keccak/tests.rs b/miden-crypto/src/dsa/ecdsa_k256_keccak/tests.rs index 2d6da8114..96d17076c 100644 --- a/miden-crypto/src/dsa/ecdsa_k256_keccak/tests.rs +++ b/miden-crypto/src/dsa/ecdsa_k256_keccak/tests.rs @@ -1,11 +1,9 @@ -use rand::rng; - use super::*; -use crate::Felt; +use crate::{Felt, rand::test_utils::seeded_rng}; #[test] fn test_key_generation() { - let mut rng = rng(); + let mut rng = seeded_rng([0u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); let public_key = secret_key.public_key(); @@ -22,7 +20,7 @@ fn test_key_generation() { #[test] fn test_public_key_recovery() { - let mut rng = rng(); + let mut rng = seeded_rng([1u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); let public_key = secret_key.public_key(); @@ -43,7 +41,7 @@ fn test_public_key_recovery() { #[test] fn test_sign_and_verify() { - let mut rng = rng(); + let mut rng = seeded_rng([2u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); let public_key = secret_key.public_key(); @@ -64,7 +62,7 @@ fn test_sign_and_verify() { #[test] fn test_signature_serialization_default() { - let mut rng = rng(); + let mut rng = seeded_rng([3u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); let message = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)].into(); @@ -78,7 +76,7 @@ fn test_signature_serialization_default() { #[test] fn test_signature_serialization() { - let mut rng = rng(); + let mut rng = seeded_rng([4u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); let message = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)].into(); @@ -100,7 +98,7 @@ fn test_signature_serialization() { #[test] fn test_secret_key_debug_redaction() { - let mut rng = rng(); + let mut rng = seeded_rng([5u8; 32]); let secret_key = SecretKey::with_rng(&mut rng); // Verify Debug impl produces expected redacted output diff --git a/miden-crypto/src/dsa/eddsa_25519_sha512/mod.rs b/miden-crypto/src/dsa/eddsa_25519_sha512/mod.rs index 70c8171b9..110238a6a 100644 --- a/miden-crypto/src/dsa/eddsa_25519_sha512/mod.rs +++ b/miden-crypto/src/dsa/eddsa_25519_sha512/mod.rs @@ -18,7 +18,7 @@ use crate::{ }, }; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests; // CONSTANTS diff --git a/miden-crypto/src/dsa/eddsa_25519_sha512/tests.rs b/miden-crypto/src/dsa/eddsa_25519_sha512/tests.rs index 96da1ecbf..e0adfc5bc 100644 --- a/miden-crypto/src/dsa/eddsa_25519_sha512/tests.rs +++ b/miden-crypto/src/dsa/eddsa_25519_sha512/tests.rs @@ -1,10 +1,9 @@ use super::*; +use crate::rand::test_utils::seeded_rng; #[test] fn sign_and_verify_roundtrip() { - use rand::rng; - - let mut rng = rng(); + let mut rng = seeded_rng([0u8; 32]); let sk = SecretKey::with_rng(&mut rng); let pk = sk.public_key(); @@ -16,7 +15,7 @@ fn sign_and_verify_roundtrip() { #[test] fn test_key_generation_serialization() { - let mut rng = rand::rng(); + let mut rng = seeded_rng([1u8; 32]); let sk = SecretKey::with_rng(&mut rng); let pk = sk.public_key(); @@ -36,7 +35,7 @@ fn test_key_generation_serialization() { #[test] fn test_secret_key_debug_redaction() { - let mut rng = rand::rng(); + let mut rng = seeded_rng([2u8; 32]); let sk = SecretKey::with_rng(&mut rng); // Verify Debug impl produces expected redacted output @@ -50,7 +49,7 @@ fn test_secret_key_debug_redaction() { #[test] fn test_compute_challenge_k_equivalence() { - let mut rng = rand::rng(); + let mut rng = seeded_rng([3u8; 32]); let sk = SecretKey::with_rng(&mut rng); let pk = sk.public_key(); diff --git a/miden-crypto/src/dsa/falcon512_rpo/hash_to_point.rs b/miden-crypto/src/dsa/falcon512_rpo/hash_to_point.rs index 0109cec5a..2adfdea15 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/hash_to_point.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/hash_to_point.rs @@ -54,7 +54,7 @@ pub fn hash_to_point_rpo256(message: Word, nonce: &Nonce) -> Polynomial Polynomial { use sha3::{ Shake256, @@ -101,7 +101,7 @@ fn felt_to_falcon_felt(value: Felt) -> FalconFelt { /// Note that since `FalconFelt::new` accepts `i16`, we first reduce the `u32` value modulo /// the Falcon prime and then cast the resulting value to an `i16`. /// Note that this final cast is safe as the Falcon prime is less than `i16::MAX`. -#[cfg(all(test, feature = "std"))] +#[cfg(test)] fn u32_to_falcon_felt(value: u32) -> FalconFelt { FalconFelt::new((value % MODULUS as u32) as i16) } diff --git a/miden-crypto/src/dsa/falcon512_rpo/keys/mod.rs b/miden-crypto/src/dsa/falcon512_rpo/keys/mod.rs index eeef326a0..c308094b2 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/keys/mod.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/keys/mod.rs @@ -13,7 +13,7 @@ pub(crate) use secret_key::{WIDTH_BIG_POLY_COEFFICIENT, WIDTH_SMALL_POLY_COEFFIC // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use rand::SeedableRng; use rand_chacha::ChaCha20Rng; diff --git a/miden-crypto/src/dsa/falcon512_rpo/keys/secret_key.rs b/miden-crypto/src/dsa/falcon512_rpo/keys/secret_key.rs index df9f8f69f..02ee8efca 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/keys/secret_key.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/keys/secret_key.rs @@ -167,7 +167,7 @@ impl SecretKey { /// number generators for generating the nonce and in `Self::sign_helper`. /// /// These changes make the signature algorithm compliant with the reference implementation. - #[cfg(all(test, feature = "std"))] + #[cfg(test)] pub fn sign_with_rng_testing(&self, message: &[u8], rng: &mut R) -> Signature { use crate::dsa::falcon512_rpo::{hash_to_point::hash_to_point_shake256, tests::ChaCha}; diff --git a/miden-crypto/src/dsa/falcon512_rpo/math/ffsampling.rs b/miden-crypto/src/dsa/falcon512_rpo/math/ffsampling.rs index 2ea7cc4b1..566b33f4a 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/math/ffsampling.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/math/ffsampling.rs @@ -195,7 +195,7 @@ pub fn ffsampling( // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use num_complex::Complex64; use rand::{Rng, SeedableRng}; diff --git a/miden-crypto/src/dsa/falcon512_rpo/math/polynomial.rs b/miden-crypto/src/dsa/falcon512_rpo/math/polynomial.rs index bbcc02972..86be9061b 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/math/polynomial.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/math/polynomial.rs @@ -648,15 +648,15 @@ impl ZeroizeOnDrop for Polynomial {} // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use super::{FalconFelt, N, Polynomial}; - use crate::rand::test_utils::rand_array; + use crate::rand::test_utils::prng_array; #[test] fn test_negacyclic_reduction() { - let coef1: [u8; N] = rand_array(); - let coef2: [u8; N] = rand_array(); + let coef1: [u8; N] = prng_array([0u8; 32]); + let coef2: [u8; N] = prng_array([1u8; 32]); let poly1 = Polynomial::new(coef1.iter().map(|&a| FalconFelt::new(a as i16)).collect()); let poly2 = Polynomial::new(coef2.iter().map(|&a| FalconFelt::new(a as i16)).collect()); diff --git a/miden-crypto/src/dsa/falcon512_rpo/math/samplerz.rs b/miden-crypto/src/dsa/falcon512_rpo/math/samplerz.rs index 0ad34aa1c..f6b377899 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/math/samplerz.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/math/samplerz.rs @@ -124,7 +124,7 @@ pub(crate) fn sampler_z(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R } } -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod test { use super::approx_exp; diff --git a/miden-crypto/src/dsa/falcon512_rpo/mod.rs b/miden-crypto/src/dsa/falcon512_rpo/mod.rs index a405de874..ec33bd4ad 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/mod.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/mod.rs @@ -42,7 +42,7 @@ mod keys; mod math; mod signature; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests; pub use self::{ @@ -101,7 +101,7 @@ pub const SK_LEN: usize = 1281; const SIG_POLY_BYTE_LEN: usize = 625; /// Signature size when serialized as a u8 vector. -#[cfg(all(test, feature = "std"))] +#[cfg(test)] const SIG_SERIALIZED_LEN: usize = 1524; /// Bound on the squared-norm of the signature. @@ -145,7 +145,7 @@ impl Nonce { /// /// This is used only in testing against the test vectors of the reference (non-deterministic) /// Falcon DSA implementation. - #[cfg(all(test, feature = "std"))] + #[cfg(test)] pub fn random(rng: &mut R) -> Self { let mut nonce_bytes = [0u8; SIG_NONCE_LEN]; rng.fill_bytes(&mut nonce_bytes); diff --git a/miden-crypto/src/dsa/falcon512_rpo/signature.rs b/miden-crypto/src/dsa/falcon512_rpo/signature.rs index e3fc8efc5..c69c7c5c5 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/signature.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/signature.rs @@ -374,7 +374,7 @@ fn are_coefficients_valid(x: &[i16]) -> bool { // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use rand::SeedableRng; use rand_chacha::ChaCha20Rng; diff --git a/miden-crypto/src/dsa/falcon512_rpo/tests/data.rs b/miden-crypto/src/dsa/falcon512_rpo/tests/data.rs index a3447ce8a..6e5c14d50 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/tests/data.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/tests/data.rs @@ -1755,60 +1755,59 @@ pub(crate) static SK_POLYS: [[[i16; 512]; 4]; NUM_TEST_VECTORS] = [ /// Serialized deterministic RPO-Falcon-512 signature intended for use as a test vector /// for the determinism in the signing procedure across platforms. /// -/// This was generated on an `Intel Core i5-8279U` running on Linux kernel `5.4.0-144-generic` and -/// built with Rust `1.88.0`. +/// This was generated on an `M4` running on `Sequoia 15.7` and built with Rust `1.90.0`. pub(crate) const DETERMINISTIC_SIGNATURE: [u8; SIG_SERIALIZED_LEN] = [ - 185, 1, 22, 144, 158, 211, 85, 196, 199, 194, 123, 220, 135, 121, 154, 120, 141, 154, 66, 32, - 56, 47, 239, 41, 38, 121, 124, 172, 190, 21, 238, 237, 69, 36, 36, 245, 63, 146, 222, 205, 107, - 153, 60, 69, 60, 10, 91, 243, 160, 222, 120, 21, 132, 54, 134, 200, 184, 209, 102, 174, 244, - 236, 77, 155, 224, 162, 181, 104, 251, 8, 40, 30, 9, 14, 184, 153, 181, 189, 100, 74, 238, 146, - 99, 20, 84, 157, 181, 82, 118, 220, 172, 2, 233, 176, 72, 241, 169, 13, 245, 117, 157, 112, 30, - 76, 218, 217, 199, 73, 94, 220, 50, 113, 143, 125, 218, 52, 196, 133, 90, 209, 27, 230, 125, - 153, 181, 235, 98, 178, 151, 63, 190, 194, 43, 186, 139, 54, 38, 8, 203, 17, 91, 137, 246, 187, - 114, 179, 210, 11, 61, 177, 55, 129, 172, 18, 200, 29, 53, 203, 254, 78, 168, 251, 249, 209, 1, - 103, 177, 6, 28, 222, 220, 220, 55, 158, 166, 228, 43, 183, 31, 38, 32, 174, 174, 113, 247, - 108, 148, 225, 245, 15, 228, 225, 234, 160, 25, 161, 201, 189, 147, 158, 12, 249, 57, 71, 113, - 17, 104, 43, 187, 53, 240, 35, 244, 54, 198, 79, 88, 154, 133, 242, 85, 168, 180, 233, 161, - 103, 77, 75, 161, 81, 33, 75, 155, 10, 247, 73, 46, 24, 55, 237, 87, 219, 83, 17, 138, 226, 41, - 250, 159, 229, 73, 94, 89, 161, 70, 82, 45, 13, 193, 6, 33, 70, 127, 181, 120, 203, 81, 171, - 39, 166, 31, 201, 41, 65, 240, 178, 93, 136, 58, 71, 147, 38, 27, 204, 158, 63, 123, 120, 81, - 136, 101, 47, 63, 22, 238, 79, 226, 137, 126, 71, 217, 53, 217, 204, 96, 108, 222, 34, 161, 31, - 162, 42, 186, 101, 139, 61, 37, 97, 145, 133, 179, 65, 163, 79, 87, 19, 49, 80, 126, 112, 246, - 92, 214, 184, 153, 247, 246, 187, 199, 133, 116, 184, 45, 223, 6, 33, 101, 117, 101, 227, 207, - 127, 238, 91, 114, 134, 53, 127, 98, 204, 219, 219, 168, 136, 63, 210, 153, 218, 186, 138, 170, - 76, 215, 67, 34, 132, 146, 12, 38, 42, 149, 76, 172, 209, 231, 24, 77, 212, 205, 171, 235, 236, - 159, 220, 92, 62, 9, 164, 54, 49, 51, 192, 47, 238, 3, 229, 98, 26, 100, 47, 101, 132, 194, 8, - 142, 141, 173, 107, 191, 102, 19, 181, 209, 71, 168, 61, 175, 33, 37, 125, 37, 203, 19, 116, - 144, 176, 55, 4, 165, 47, 238, 101, 20, 131, 197, 146, 167, 222, 185, 140, 132, 80, 128, 226, - 150, 93, 203, 160, 196, 162, 141, 105, 190, 50, 92, 98, 31, 136, 102, 46, 24, 153, 6, 55, 78, - 135, 146, 24, 147, 221, 31, 74, 189, 115, 157, 83, 74, 147, 64, 255, 204, 79, 255, 31, 74, 65, - 143, 115, 35, 72, 59, 244, 26, 130, 173, 69, 96, 26, 215, 61, 97, 41, 69, 236, 230, 105, 119, - 30, 220, 90, 128, 250, 48, 134, 130, 205, 142, 196, 49, 184, 190, 101, 220, 199, 168, 217, 105, - 242, 157, 100, 135, 163, 156, 205, 172, 241, 35, 148, 124, 244, 45, 97, 213, 114, 55, 10, 126, - 117, 173, 135, 77, 239, 135, 58, 68, 243, 200, 222, 100, 52, 219, 26, 19, 217, 109, 32, 39, - 118, 130, 139, 38, 101, 231, 38, 126, 228, 20, 197, 91, 211, 248, 253, 74, 27, 201, 4, 52, 158, - 38, 116, 79, 62, 17, 107, 99, 75, 166, 247, 119, 31, 140, 97, 229, 48, 73, 179, 23, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 9, 155, 125, 185, 64, 84, 225, 93, 95, 10, 178, 100, 198, 160, 180, 110, 66, - 53, 41, 212, 204, 170, 160, 237, 167, 160, 122, 168, 168, 68, 93, 180, 2, 255, 84, 191, 48, - 157, 91, 57, 228, 201, 192, 75, 145, 62, 104, 175, 135, 156, 9, 128, 122, 1, 210, 73, 150, 34, - 200, 59, 228, 99, 89, 40, 54, 25, 217, 86, 245, 170, 187, 28, 224, 144, 102, 208, 225, 180, - 106, 60, 184, 144, 29, 213, 222, 166, 45, 212, 135, 24, 164, 201, 83, 26, 181, 36, 130, 38, - 173, 109, 97, 235, 192, 26, 43, 248, 157, 36, 62, 182, 118, 28, 234, 32, 6, 171, 179, 224, 30, - 170, 75, 80, 101, 228, 221, 195, 238, 144, 170, 6, 57, 109, 171, 41, 157, 234, 0, 103, 243, - 207, 105, 76, 164, 83, 35, 27, 73, 134, 177, 241, 38, 98, 86, 16, 200, 61, 190, 53, 115, 49, - 157, 169, 143, 109, 119, 196, 109, 151, 31, 54, 94, 22, 246, 174, 164, 162, 173, 60, 74, 18, - 220, 166, 122, 176, 32, 166, 107, 100, 229, 32, 161, 185, 210, 8, 49, 230, 61, 184, 212, 197, - 41, 114, 239, 214, 114, 177, 9, 39, 254, 197, 24, 151, 86, 141, 25, 206, 200, 146, 167, 36, 29, - 224, 66, 141, 123, 73, 246, 49, 80, 207, 109, 160, 72, 249, 70, 164, 10, 211, 190, 15, 104, - 147, 186, 216, 202, 251, 27, 246, 250, 104, 57, 91, 119, 19, 98, 173, 247, 70, 85, 8, 13, 70, - 69, 120, 52, 21, 87, 112, 50, 11, 75, 213, 167, 79, 42, 106, 58, 250, 77, 12, 133, 174, 108, - 113, 82, 17, 17, 98, 126, 97, 172, 87, 218, 221, 79, 84, 113, 33, 148, 62, 105, 150, 66, 152, - 153, 39, 237, 96, 75, 81, 1, 56, 6, 98, 92, 138, 114, 242, 189, 40, 38, 197, 118, 96, 130, 145, - 229, 138, 153, 44, 49, 89, 120, 209, 167, 205, 202, 28, 65, 174, 219, 125, 99, 31, 88, 48, 254, - 227, 34, 88, 138, 138, 60, 144, 106, 148, 158, 248, 154, 181, 53, 3, 45, 233, 164, 68, 80, 207, - 42, 209, 157, 159, 128, 94, 241, 55, 166, 231, 115, 130, 41, 132, 19, 135, 225, 120, 36, 101, - 204, 210, 161, 84, 197, 63, 5, 36, 178, 4, 229, 237, 43, 49, 212, 80, 219, 20, 172, 182, 189, - 9, 193, 112, 73, 63, 37, 148, 148, 184, 201, 96, 83, 62, 32, 186, 249, 54, 103, 208, 112, 216, + 185, 1, 49, 100, 34, 177, 39, 53, 190, 227, 187, 229, 174, 59, 206, 209, 55, 168, 94, 121, 223, + 102, 175, 213, 188, 26, 185, 233, 198, 252, 249, 138, 82, 22, 171, 253, 118, 25, 164, 99, 187, + 36, 109, 69, 198, 5, 16, 70, 234, 156, 145, 45, 71, 247, 255, 137, 71, 108, 215, 161, 85, 46, + 110, 45, 26, 71, 171, 47, 181, 153, 48, 142, 250, 169, 149, 108, 193, 17, 239, 43, 255, 253, + 190, 217, 63, 139, 49, 228, 103, 101, 201, 241, 236, 162, 110, 246, 146, 195, 202, 159, 63, + 237, 121, 235, 235, 216, 41, 27, 127, 141, 61, 13, 41, 133, 97, 80, 207, 33, 90, 59, 250, 95, + 240, 34, 95, 25, 43, 115, 31, 51, 124, 214, 88, 143, 111, 143, 65, 29, 175, 167, 200, 233, 68, + 194, 224, 232, 184, 183, 95, 49, 65, 81, 81, 9, 82, 27, 60, 196, 39, 103, 33, 209, 97, 88, 92, + 214, 121, 201, 66, 191, 172, 175, 76, 165, 196, 191, 205, 5, 147, 179, 11, 173, 36, 186, 173, + 211, 229, 41, 235, 251, 245, 44, 234, 164, 157, 66, 166, 146, 187, 156, 43, 23, 184, 108, 107, + 30, 45, 252, 98, 185, 136, 152, 185, 94, 120, 149, 133, 200, 96, 255, 188, 183, 9, 122, 52, + 220, 92, 171, 53, 43, 119, 97, 73, 36, 69, 194, 117, 179, 10, 158, 180, 173, 216, 34, 147, 150, + 73, 137, 38, 104, 147, 147, 128, 76, 28, 9, 134, 72, 86, 33, 109, 238, 37, 19, 189, 248, 222, + 221, 252, 185, 150, 102, 200, 66, 208, 254, 154, 102, 110, 46, 180, 253, 181, 90, 136, 15, 15, + 99, 250, 71, 8, 41, 206, 249, 247, 177, 87, 27, 246, 193, 91, 240, 148, 39, 138, 141, 166, 109, + 36, 20, 109, 14, 103, 47, 30, 48, 13, 38, 188, 151, 233, 74, 148, 7, 147, 132, 238, 106, 86, + 146, 36, 206, 56, 89, 102, 213, 66, 84, 151, 47, 116, 223, 164, 206, 177, 164, 17, 55, 231, 93, + 236, 115, 92, 161, 28, 171, 33, 153, 86, 140, 123, 224, 201, 107, 121, 129, 63, 212, 221, 148, + 62, 172, 44, 184, 103, 217, 88, 67, 173, 172, 42, 115, 151, 179, 29, 118, 114, 186, 202, 80, + 153, 89, 92, 81, 0, 74, 55, 201, 247, 54, 90, 199, 243, 119, 172, 31, 15, 182, 170, 200, 127, + 183, 91, 189, 237, 241, 154, 248, 229, 16, 117, 149, 15, 79, 156, 246, 160, 147, 77, 38, 144, + 194, 119, 69, 131, 46, 23, 185, 43, 66, 194, 77, 185, 30, 206, 92, 4, 218, 161, 156, 24, 54, + 238, 89, 201, 37, 148, 39, 185, 89, 137, 206, 171, 148, 189, 181, 185, 205, 168, 104, 182, 93, + 82, 17, 77, 143, 31, 188, 108, 11, 168, 116, 147, 166, 55, 160, 209, 153, 5, 146, 59, 46, 231, + 219, 112, 200, 110, 9, 148, 200, 94, 93, 247, 234, 48, 90, 88, 104, 34, 18, 120, 235, 25, 231, + 42, 156, 145, 165, 233, 143, 17, 227, 155, 44, 216, 185, 202, 54, 242, 53, 233, 206, 161, 176, + 221, 204, 124, 208, 104, 87, 80, 128, 163, 122, 150, 178, 5, 184, 146, 50, 121, 95, 174, 151, + 57, 4, 174, 208, 27, 157, 135, 190, 121, 1, 68, 57, 81, 69, 235, 205, 137, 82, 161, 209, 12, + 212, 9, 9, 77, 22, 251, 36, 37, 13, 50, 61, 89, 164, 69, 105, 92, 233, 136, 195, 210, 103, 102, + 73, 20, 5, 28, 162, 56, 169, 245, 255, 8, 134, 70, 53, 38, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 9, 155, 125, 185, 64, 84, 225, 93, 95, 10, 178, 100, 198, 160, 180, 110, 66, 53, + 41, 212, 204, 170, 160, 237, 167, 160, 122, 168, 168, 68, 93, 180, 2, 255, 84, 191, 48, 157, + 91, 57, 228, 201, 192, 75, 145, 62, 104, 175, 135, 156, 9, 128, 122, 1, 210, 73, 150, 34, 200, + 59, 228, 99, 89, 40, 54, 25, 217, 86, 245, 170, 187, 28, 224, 144, 102, 208, 225, 180, 106, 60, + 184, 144, 29, 213, 222, 166, 45, 212, 135, 24, 164, 201, 83, 26, 181, 36, 130, 38, 173, 109, + 97, 235, 192, 26, 43, 248, 157, 36, 62, 182, 118, 28, 234, 32, 6, 171, 179, 224, 30, 170, 75, + 80, 101, 228, 221, 195, 238, 144, 170, 6, 57, 109, 171, 41, 157, 234, 0, 103, 243, 207, 105, + 76, 164, 83, 35, 27, 73, 134, 177, 241, 38, 98, 86, 16, 200, 61, 190, 53, 115, 49, 157, 169, + 143, 109, 119, 196, 109, 151, 31, 54, 94, 22, 246, 174, 164, 162, 173, 60, 74, 18, 220, 166, + 122, 176, 32, 166, 107, 100, 229, 32, 161, 185, 210, 8, 49, 230, 61, 184, 212, 197, 41, 114, + 239, 214, 114, 177, 9, 39, 254, 197, 24, 151, 86, 141, 25, 206, 200, 146, 167, 36, 29, 224, 66, + 141, 123, 73, 246, 49, 80, 207, 109, 160, 72, 249, 70, 164, 10, 211, 190, 15, 104, 147, 186, + 216, 202, 251, 27, 246, 250, 104, 57, 91, 119, 19, 98, 173, 247, 70, 85, 8, 13, 70, 69, 120, + 52, 21, 87, 112, 50, 11, 75, 213, 167, 79, 42, 106, 58, 250, 77, 12, 133, 174, 108, 113, 82, + 17, 17, 98, 126, 97, 172, 87, 218, 221, 79, 84, 113, 33, 148, 62, 105, 150, 66, 152, 153, 39, + 237, 96, 75, 81, 1, 56, 6, 98, 92, 138, 114, 242, 189, 40, 38, 197, 118, 96, 130, 145, 229, + 138, 153, 44, 49, 89, 120, 209, 167, 205, 202, 28, 65, 174, 219, 125, 99, 31, 88, 48, 254, 227, + 34, 88, 138, 138, 60, 144, 106, 148, 158, 248, 154, 181, 53, 3, 45, 233, 164, 68, 80, 207, 42, + 209, 157, 159, 128, 94, 241, 55, 166, 231, 115, 130, 41, 132, 19, 135, 225, 120, 36, 101, 204, + 210, 161, 84, 197, 63, 5, 36, 178, 4, 229, 237, 43, 49, 212, 80, 219, 20, 172, 182, 189, 9, + 193, 112, 73, 63, 37, 148, 148, 184, 201, 96, 83, 62, 32, 186, 249, 54, 103, 208, 112, 216, 216, 217, 97, 70, 4, 18, 42, 182, 117, 21, 222, 204, 168, 164, 123, 1, 189, 145, 70, 80, 218, 192, 136, 81, 22, 159, 137, 194, 70, 246, 187, 150, 50, 54, 154, 203, 214, 73, 174, 205, 44, 192, 105, 138, 192, 109, 238, 21, 64, 232, 181, 218, 129, 125, 92, 145, 87, 64, 222, 169, 183, diff --git a/miden-crypto/src/dsa/falcon512_rpo/tests/mod.rs b/miden-crypto/src/dsa/falcon512_rpo/tests/mod.rs index 9ef4106a7..8d7adb01a 100644 --- a/miden-crypto/src/dsa/falcon512_rpo/tests/mod.rs +++ b/miden-crypto/src/dsa/falcon512_rpo/tests/mod.rs @@ -1,7 +1,8 @@ use alloc::vec::Vec; use data::{ - EXPECTED_SIG, EXPECTED_SIG_POLYS, NUM_TEST_VECTORS, SK_POLYS, SYNC_DATA_FOR_TEST_VECTOR, + DETERMINISTIC_SIGNATURE, EXPECTED_SIG, EXPECTED_SIG_POLYS, NUM_TEST_VECTORS, SK_POLYS, + SYNC_DATA_FOR_TEST_VECTOR, }; use prng::Shake256Testing; use rand::{RngCore, SeedableRng}; @@ -10,7 +11,6 @@ use rand_chacha::ChaCha20Rng; use super::{Serializable, math::Polynomial}; use crate::dsa::falcon512_rpo::{ PREVERSIONED_NONCE, PREVERSIONED_NONCE_LEN, SIG_NONCE_LEN, SIG_POLY_BYTE_LEN, SecretKey, - tests::data::DETERMINISTIC_SIGNATURE, }; mod data; diff --git a/miden-crypto/src/ecdh/k256.rs b/miden-crypto/src/ecdh/k256.rs index 2f94849fe..d9fe468ee 100644 --- a/miden-crypto/src/ecdh/k256.rs +++ b/miden-crypto/src/ecdh/k256.rs @@ -230,20 +230,18 @@ impl KeyAgreementScheme for K256 { // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod test { - - use rand::rng; - use super::{EphemeralPublicKey, EphemeralSecretKey}; use crate::{ dsa::ecdsa_k256_keccak::SecretKey, + rand::test_utils::seeded_rng, utils::{Deserializable, Serializable}, }; #[test] fn key_agreement() { - let mut rng = rng(); + let mut rng = seeded_rng([0u8; 32]); // 1. Generate the static key-pair for Alice let sk = SecretKey::with_rng(&mut rng); @@ -271,7 +269,7 @@ mod test { #[test] fn test_serialization_round_trip() { - let mut rng = rng(); + let mut rng = seeded_rng([1u8; 32]); let sk_e = EphemeralSecretKey::with_rng(&mut rng); let pk_e = sk_e.public_key(); diff --git a/miden-crypto/src/ecdh/x25519.rs b/miden-crypto/src/ecdh/x25519.rs index bc34af77e..5c4e198bf 100644 --- a/miden-crypto/src/ecdh/x25519.rs +++ b/miden-crypto/src/ecdh/x25519.rs @@ -213,16 +213,14 @@ impl KeyAgreementScheme for X25519 { // TESTS // ================================================================================================ -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { - use rand::rng; - use super::*; - use crate::dsa::eddsa_25519_sha512::SecretKey; + use crate::{dsa::eddsa_25519_sha512::SecretKey, rand::test_utils::seeded_rng}; #[test] fn key_agreement() { - let mut rng = rng(); + let mut rng = seeded_rng([0u8; 32]); // 1. Generate the static key-pair for Alice let sk = SecretKey::with_rng(&mut rng); diff --git a/miden-crypto/src/hash/algebraic_sponge/mod.rs b/miden-crypto/src/hash/algebraic_sponge/mod.rs index 97ce24503..30f83bc8d 100644 --- a/miden-crypto/src/hash/algebraic_sponge/mod.rs +++ b/miden-crypto/src/hash/algebraic_sponge/mod.rs @@ -25,38 +25,28 @@ pub(crate) mod rescue; // CONSTANTS // ================================================================================================ -/// Sponge state is set to 12 field elements or 96 bytes; 8 elements are reserved for rate and -/// the remaining 4 elements are reserved for capacity. +/// Sponge state is set to 12 field elements or 96 bytes; 8 elements are reserved for the rate and +/// the remaining 4 elements are reserved for the capacity. pub(crate) const STATE_WIDTH: usize = 12; -/// The rate portion of the state is located in elements 4 through 11. -pub(crate) const RATE_RANGE: Range = 4..12; +/// The rate portion of the state is located in elements 0 through 7. +pub(crate) const RATE_RANGE: Range = 0..8; pub(crate) const RATE_WIDTH: usize = RATE_RANGE.end - RATE_RANGE.start; -pub(crate) const INPUT1_RANGE: Range = 4..8; -pub(crate) const INPUT2_RANGE: Range = 8..12; +/// The first and second 4-element words of the rate portion. +pub(crate) const RATE0_RANGE: Range = 0..4; +pub(crate) const RATE1_RANGE: Range = 4..8; -/// The capacity portion of the state is located in elements 0, 1, 2, and 3. -pub(crate) const CAPACITY_RANGE: Range = 0..4; +/// The capacity portion of the state is located in elements 8, 9, 10, and 11. +pub(crate) const CAPACITY_RANGE: Range = 8..12; -/// The output of the hash function is a digest which consists of 4 field elements or 32 bytes. -/// -/// The digest is returned from state elements 4, 5, 6, and 7 (the first four elements of the -/// rate portion). -pub(crate) const DIGEST_RANGE: Range = 4..8; +/// The output of the hash function is a digest which consists of 4 field elements or 32 bytes, +/// taken from the first word of the rate portion of the state. +pub(crate) const DIGEST_RANGE: Range = 0..4; /// The number of byte chunks defining a field element when hashing a sequence of bytes const BINARY_CHUNK_SIZE: usize = 7; -/// S-Box and Inverse S-Box powers; -/// -/// The constants are defined for tests only because the exponentiations in the code are unrolled -/// for efficiency reasons. -#[cfg(all(test, feature = "std"))] -pub(crate) const ALPHA: u64 = 7; -#[cfg(all(test, feature = "std"))] -pub(crate) const INV_ALPHA: u64 = 10540996611094048183; - // ALGEBRAIC SPONGE // ================================================================================================ @@ -105,7 +95,7 @@ pub(crate) trait AlgebraicSponge { Self::apply_permutation(&mut state); } - // return the first 4 elements of the state as hash result + // return the digest portion of the state as hash result Word::new(state[DIGEST_RANGE].try_into().unwrap()) } @@ -178,7 +168,7 @@ pub(crate) trait AlgebraicSponge { Self::apply_permutation(&mut state); } - // return the first 4 elements of the rate as hash result. + // return the digest portion of the rate as hash result. Word::new(state[DIGEST_RANGE].try_into().unwrap()) } @@ -213,12 +203,12 @@ pub(crate) trait AlgebraicSponge { // - if the value doesn't fit into a single field element, split it into two field elements, // copy them into rate elements 5 and 6 and set the first capacity element to 6. let mut state = [ZERO; STATE_WIDTH]; - state[INPUT1_RANGE].copy_from_slice(seed.as_elements()); - state[INPUT2_RANGE.start] = Felt::new(value); + state[RATE0_RANGE].copy_from_slice(seed.as_elements()); + state[RATE1_RANGE.start] = Felt::new(value); if value < Felt::ORDER_U64 { state[CAPACITY_RANGE.start] = Felt::from_u8(5_u8); } else { - state[INPUT2_RANGE.start + 1] = Felt::new(value / Felt::ORDER_U64); + state[RATE1_RANGE.start + 1] = Felt::new(value / Felt::ORDER_U64); state[CAPACITY_RANGE.start] = Felt::from_u8(6_u8); } diff --git a/miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs b/miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs index a13ae459d..b798568d3 100644 --- a/miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs +++ b/miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs @@ -1,7 +1,7 @@ use super::{ - AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, RATE_RANGE, Range, STATE_WIDTH, Word, ZERO, + AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, PrimeCharacteristicRing, RATE_RANGE, + RATE0_RANGE, RATE1_RANGE, Range, STATE_WIDTH, Word, ZERO, }; -use crate::field::PrimeCharacteristicRing; mod constants; use constants::{ @@ -102,17 +102,24 @@ impl Poseidon2 { /// Number of internal rounds. pub const NUM_INTERNAL_ROUNDS: usize = NUM_INTERNAL_ROUNDS; - /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for rate and - /// the remaining 4 elements are reserved for capacity. + /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for the + /// rate and the remaining 4 elements are reserved for the capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The first 4-element word of the rate portion. + pub const RATE0_RANGE: Range = RATE0_RANGE; + + /// The second 4-element word of the rate portion. + pub const RATE1_RANGE: Range = RATE1_RANGE; + + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3 (the first + /// word of the state). pub const DIGEST_RANGE: Range = DIGEST_RANGE; /// Matrix used for computing the linear layers of internal rounds. @@ -328,9 +335,9 @@ use p3_symmetric::{ /// `CryptographicPermutation` traits, allowing Poseidon2 to be used within the Plonky3 ecosystem. /// /// The permutation operates on a state of 12 field elements (STATE_WIDTH = 12), with: -/// - Rate: 8 elements (positions 4-11) -/// - Capacity: 4 elements (positions 0-3) -/// - Digest output: 4 elements (positions 4-7) +/// - Rate: 8 elements (positions 0-7) +/// - Capacity: 4 elements (positions 8-11) +/// - Digest output: 4 elements (positions 0-3) #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct Poseidon2Permutation256; @@ -348,13 +355,13 @@ impl Poseidon2Permutation256 { /// the remaining 4 elements are reserved for capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = Poseidon2::RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = Poseidon2::CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3. pub const DIGEST_RANGE: Range = Poseidon2::DIGEST_RANGE; // POSEIDON2 PERMUTATION @@ -410,140 +417,3 @@ pub type Poseidon2Compression = TruncatedPermutation = DuplexChallenger; - -#[cfg(test)] -mod p3_tests { - use p3_symmetric::{CryptographicHasher, PseudoCompressionFunction}; - - use super::*; - - #[test] - fn test_poseidon2_permutation_basic() { - let mut state = [Felt::new(0); STATE_WIDTH]; - - // Apply permutation - let perm = Poseidon2Permutation256; - perm.permute_mut(&mut state); - - // State should be different from all zeros after permutation - assert_ne!(state, [Felt::new(0); STATE_WIDTH]); - } - - #[test] - fn test_poseidon2_permutation_consistency() { - let mut state1 = [Felt::new(0); STATE_WIDTH]; - let mut state2 = [Felt::new(0); STATE_WIDTH]; - - // Apply permutation using the trait - let perm = Poseidon2Permutation256; - perm.permute_mut(&mut state1); - - // Apply permutation directly - Poseidon2Permutation256::apply_permutation(&mut state2); - - // Both should produce the same result - assert_eq!(state1, state2); - } - - #[test] - fn test_poseidon2_permutation_deterministic() { - let input = [ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - Felt::new(9), - Felt::new(10), - Felt::new(11), - Felt::new(12), - ]; - - let mut state1 = input; - let mut state2 = input; - - let perm = Poseidon2Permutation256; - perm.permute_mut(&mut state1); - perm.permute_mut(&mut state2); - - // Same input should produce same output - assert_eq!(state1, state2); - } - - #[test] - #[ignore] // TODO: Re-enable after migrating Poseidon2 state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] - fn test_poseidon2_hasher_vs_hash_elements() { - // Test with empty input - let expected: [Felt; 4] = Poseidon2::hash_elements::(&[]).into(); - let hasher = Poseidon2Hasher::new(Poseidon2Permutation256); - let result = hasher.hash_iter([]); - assert_eq!(result, expected, "Empty input should produce same digest"); - - // Test with 4 elements (one digest worth) - let input4 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let expected: [Felt; 4] = Poseidon2::hash_elements(&input4).into(); - let result = hasher.hash_iter(input4); - assert_eq!(result, expected, "4 elements should produce same digest"); - - // Test with 8 elements (exactly one rate) - let input8 = [ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - ]; - let expected: [Felt; 4] = Poseidon2::hash_elements(&input8).into(); - let result = hasher.hash_iter(input8); - assert_eq!(result, expected, "8 elements (one rate) should produce same digest"); - - // Test with 16 elements (two rates) - let input16 = [ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - Felt::new(9), - Felt::new(10), - Felt::new(11), - Felt::new(12), - Felt::new(13), - Felt::new(14), - Felt::new(15), - Felt::new(16), - ]; - let expected: [Felt; 4] = Poseidon2::hash_elements(&input16).into(); - let result = hasher.hash_iter(input16); - assert_eq!(result, expected, "16 elements (two rates) should produce same digest"); - } - - #[test] - #[ignore] // TODO: Re-enable after migrating Poseidon2 state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] - fn test_poseidon2_compression_vs_merge() { - let digest1 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let digest2 = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; - - // Poseidon2::merge expects &[Word; 2] - let expected: [Felt; 4] = Poseidon2::merge(&[digest1.into(), digest2.into()]).into(); - - // Poseidon2Compression expects [[Felt; 4]; 2] - let compress = Poseidon2Compression::new(Poseidon2Permutation256); - let result = compress.compress([digest1, digest2]); - - assert_eq!(result, expected, "Poseidon2Compression should match Poseidon2::merge"); - } -} diff --git a/miden-crypto/src/hash/algebraic_sponge/poseidon2/test.rs b/miden-crypto/src/hash/algebraic_sponge/poseidon2/test.rs index 0d2cdf193..52d42bba2 100644 --- a/miden-crypto/src/hash/algebraic_sponge/poseidon2/test.rs +++ b/miden-crypto/src/hash/algebraic_sponge/poseidon2/test.rs @@ -1,4 +1,6 @@ -use super::{Felt, ZERO}; +use p3_symmetric::{CryptographicHasher, PseudoCompressionFunction}; + +use super::*; use crate::hash::{algebraic_sponge::AlgebraicSponge, poseidon2::Poseidon2}; #[test] @@ -36,3 +38,117 @@ fn permutation_test_vector() { assert_eq!(perm[10], Felt::new(0xec467926508fbe67)); assert_eq!(perm[11], Felt::new(0x6a50450ddf85a6ed)); } + +#[test] +fn test_poseidon2_permutation_basic() { + let mut state = [Felt::new(0); STATE_WIDTH]; + + // Apply permutation + let perm = Poseidon2Permutation256; + perm.permute_mut(&mut state); + + // State should be different from all zeros after permutation + assert_ne!(state, [Felt::new(0); STATE_WIDTH]); +} + +#[test] +fn test_poseidon2_permutation_consistency() { + let mut state1 = [Felt::new(0); STATE_WIDTH]; + let mut state2 = [Felt::new(0); STATE_WIDTH]; + + // Apply permutation using the trait + let perm = Poseidon2Permutation256; + perm.permute_mut(&mut state1); + + // Apply permutation directly + Poseidon2Permutation256::apply_permutation(&mut state2); + + // Both should produce the same result + assert_eq!(state1, state2); +} + +#[test] +fn test_poseidon2_permutation_deterministic() { + let input = [ + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + Felt::new(9), + Felt::new(10), + Felt::new(11), + Felt::new(12), + ]; + + let mut state1 = input; + let mut state2 = input; + + let perm = Poseidon2Permutation256; + perm.permute_mut(&mut state1); + perm.permute_mut(&mut state2); + + // Same input should produce same output + assert_eq!(state1, state2); +} + +#[test] +fn test_poseidon2_hasher_vs_hash_elements() { + let hasher = Poseidon2Hasher::new(Poseidon2Permutation256); + + // Test with 8 elements (exactly one rate) + let input8 = [ + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + ]; + let expected: [Felt; 4] = Poseidon2::hash_elements(&input8).into(); + let result = hasher.hash_iter(input8); + assert_eq!(result, expected, "8 elements (one rate) should produce same digest"); + + // Test with 16 elements (two rates) + let input16 = [ + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + Felt::new(9), + Felt::new(10), + Felt::new(11), + Felt::new(12), + Felt::new(13), + Felt::new(14), + Felt::new(15), + Felt::new(16), + ]; + let expected: [Felt; 4] = Poseidon2::hash_elements(&input16).into(); + let result = hasher.hash_iter(input16); + assert_eq!(result, expected, "16 elements (two rates) should produce same digest"); +} + +#[test] +fn test_poseidon2_compression_vs_merge() { + let digest1 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; + let digest2 = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; + + // Poseidon2::merge expects &[Word; 2] + let expected: [Felt; 4] = Poseidon2::merge(&[digest1.into(), digest2.into()]).into(); + + // Poseidon2Compression expects [[Felt; 4]; 2] + let compress = Poseidon2Compression::new(Poseidon2Permutation256); + let result = compress.compress([digest1, digest2]); + + assert_eq!(result, expected, "Poseidon2Compression should match Poseidon2::merge"); +} diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/mod.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/mod.rs index b8209a349..cf73963ea 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/mod.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/mod.rs @@ -1,9 +1,8 @@ use p3_field::{Field, PrimeCharacteristicRing}; -#[cfg(all(test, feature = "std"))] -pub(crate) use super::{ALPHA, INV_ALPHA}; use super::{ - AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, RATE_RANGE, Range, STATE_WIDTH, Word, ZERO, + AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, RATE_RANGE, RATE0_RANGE, RATE1_RANGE, + Range, STATE_WIDTH, Word, ZERO, }; mod arch; diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs index 94dd72f91..f122f05b3 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs @@ -1,9 +1,9 @@ use super::{ - ARK1, ARK2, AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, NUM_ROUNDS, RATE_RANGE, Range, - STATE_WIDTH, Word, add_constants, add_constants_and_apply_inv_sbox, - add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, apply_sbox, + ARK1, ARK2, AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, MDS, NUM_ROUNDS, RATE_RANGE, + RATE0_RANGE, RATE1_RANGE, Range, STATE_WIDTH, Word, add_constants, + add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, + apply_sbox, }; -use crate::hash::algebraic_sponge::rescue::mds::MDS; #[cfg(test)] mod tests; @@ -91,17 +91,24 @@ impl Rpo256 { /// The number of rounds is set to 7 to target 128-bit security level. pub const NUM_ROUNDS: usize = NUM_ROUNDS; - /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for rate and - /// the remaining 4 elements are reserved for capacity. + /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for the + /// rate and the remaining 4 elements are reserved for the capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The first 4-element word of the rate portion. + pub const RATE0_RANGE: Range = RATE0_RANGE; + + /// The second 4-element word of the rate portion. + pub const RATE1_RANGE: Range = RATE1_RANGE; + + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3 (the first + /// word of the state). pub const DIGEST_RANGE: Range = DIGEST_RANGE; /// MDS matrix used for computing the linear layer in a RPO round. @@ -209,9 +216,9 @@ use p3_symmetric::{ /// `CryptographicPermutation` traits, allowing RPO to be used within the Plonky3 ecosystem. /// /// The permutation operates on a state of 12 field elements (STATE_WIDTH = 12), with: -/// - Rate: 8 elements (positions 4-11) -/// - Capacity: 4 elements (positions 0-3) -/// - Digest output: 4 elements (positions 4-7) +/// - Rate: 8 elements (positions 0-7) +/// - Capacity: 4 elements (positions 8-11) +/// - Digest output: 4 elements (positions 0-3) #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct RpoPermutation256; @@ -226,13 +233,13 @@ impl RpoPermutation256 { /// the remaining 4 elements are reserved for capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = Rpo256::RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = Rpo256::CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3. pub const DIGEST_RANGE: Range = Rpo256::DIGEST_RANGE; // RESCUE PERMUTATION diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs index 18bf0ff88..eecb4f666 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs @@ -4,13 +4,20 @@ use alloc::{collections::BTreeSet, vec::Vec}; use proptest::prelude::*; use super::{ - super::{ALPHA, INV_ALPHA, apply_inv_sbox, apply_sbox}, + super::{apply_inv_sbox, apply_sbox}, Felt, Rpo256, STATE_WIDTH, }; + +/// S-Box power for Rescue Prime hash function. +const ALPHA: u64 = 7; +/// Inverse S-Box power for Rescue Prime hash function. +const INV_ALPHA: u64 = 10540996611094048183; use crate::{ ONE, Word, ZERO, field::{PrimeCharacteristicRing, PrimeField64}, - hash::algebraic_sponge::{AlgebraicSponge, BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_WIDTH}, + hash::algebraic_sponge::{ + AlgebraicSponge, BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_RANGE, RATE_WIDTH, + }, rand::test_utils::rand_value, }; @@ -146,7 +153,8 @@ fn hash_padding_no_extra_permutation_call() { let mut state = [ZERO; STATE_WIDTH]; // padding when hashing bytes state[CAPACITY_RANGE.start] = Felt::from_u8(RATE_WIDTH as u8); - *state.last_mut().unwrap() = Felt::new(u64::from_le_bytes(final_chunk)); + // place the final padded chunk into the last rate element + state[RATE_RANGE.end - 1] = Felt::new(u64::from_le_bytes(final_chunk)); Rpo256::apply_permutation(&mut state); assert_eq!(&r1[0..4], &state[DIGEST_RANGE]); @@ -228,7 +236,7 @@ fn hash_test_vectors() { ]; for i in 0..elements.len() { - let expected = Word::new(*EXPECTED[i]); + let expected = EXPECTED[i]; let result = Rpo256::hash_elements(&elements[..(i + 1)]); assert_eq!(result, expected); } @@ -273,118 +281,118 @@ proptest! { const EXPECTED: [Word; 19] = [ Word::new([ - Felt::new(18126731724905382595), - Felt::new(7388557040857728717), - Felt::new(14290750514634285295), - Felt::new(7852282086160480146), + Felt::new(8563248028282119176), + Felt::new(14757918088501470722), + Felt::new(14042820149444308297), + Felt::new(7607140247535155355), ]), Word::new([ - Felt::new(10139303045932500183), - Felt::new(2293916558361785533), - Felt::new(15496361415980502047), - Felt::new(17904948502382283940), + Felt::new(8762449007102993687), + Felt::new(4386081033660325954), + Felt::new(5000814629424193749), + Felt::new(8171580292230495897), ]), Word::new([ - Felt::new(17457546260239634015), - Felt::new(803990662839494686), - Felt::new(10386005777401424878), - Felt::new(18168807883298448638), + Felt::new(16710087681096729759), + Felt::new(10808706421914121430), + Felt::new(14661356949236585983), + Felt::new(5683478730832134441), ]), Word::new([ - Felt::new(13072499238647455740), - Felt::new(10174350003422057273), - Felt::new(9201651627651151113), - Felt::new(6872461887313298746), + Felt::new(5309818427047650994), + Felt::new(17172251659920546244), + Felt::new(8288476618870804357), + Felt::new(18080473279382182941), ]), Word::new([ - Felt::new(2903803350580990546), - Felt::new(1838870750730563299), - Felt::new(4258619137315479708), - Felt::new(17334260395129062936), + Felt::new(3647545403045515695), + Felt::new(3358383208908083302), + Felt::new(8797161010298072910), + Felt::new(2412100201132087248), ]), Word::new([ - Felt::new(8571221005243425262), - Felt::new(3016595589318175865), - Felt::new(13933674291329928438), - Felt::new(678640375034313072), + Felt::new(8409780526028662686), + Felt::new(214479528340808320), + Felt::new(13626616722984122219), + Felt::new(13991752159726061594), ]), Word::new([ - Felt::new(16314113978986502310), - Felt::new(14587622368743051587), - Felt::new(2808708361436818462), - Felt::new(10660517522478329440), + Felt::new(4800410126693035096), + Felt::new(8293686005479024958), + Felt::new(16849389505608627981), + Felt::new(12129312715917897796), ]), Word::new([ - Felt::new(2242391899857912644), - Felt::new(12689382052053305418), - Felt::new(235236990017815546), - Felt::new(5046143039268215739), + Felt::new(5421234586123900205), + Felt::new(9738602082989433872), + Felt::new(7017816005734536787), + Felt::new(8635896173743411073), ]), Word::new([ - Felt::new(5218076004221736204), - Felt::new(17169400568680971304), - Felt::new(8840075572473868990), - Felt::new(12382372614369863623), + Felt::new(11707446879505873182), + Felt::new(7588005580730590001), + Felt::new(4664404372972250366), + Felt::new(17613162115550587316), ]), Word::new([ - Felt::new(9783834557155203486), - Felt::new(12317263104955018849), - Felt::new(3933748931816109604), - Felt::new(1843043029836917214), + Felt::new(6991094187713033844), + Felt::new(10140064581418506488), + Felt::new(1235093741254112241), + Felt::new(16755357411831959519), ]), Word::new([ - Felt::new(14498234468286984551), - Felt::new(16837257669834682387), - Felt::new(6664141123711355107), - Felt::new(4590460158294697186), + Felt::new(18007834547781860956), + Felt::new(5262789089508245576), + Felt::new(4752286606024269423), + Felt::new(15626544383301396533), ]), Word::new([ - Felt::new(4661800562479916067), - Felt::new(11794407552792839953), - Felt::new(9037742258721863712), - Felt::new(6287820818064278819), + Felt::new(5419895278045886802), + Felt::new(10747737918518643252), + Felt::new(14861255521757514163), + Felt::new(3291029997369465426), ]), Word::new([ - Felt::new(7752693085194633729), - Felt::new(7379857372245835536), - Felt::new(9270229380648024178), - Felt::new(10638301488452560378), + Felt::new(16916426112258580265), + Felt::new(8714377345140065340), + Felt::new(14207246102129706649), + Felt::new(6226142825442954311), ]), Word::new([ - Felt::new(11542686762698783357), - Felt::new(15570714990728449027), - Felt::new(7518801014067819501), - Felt::new(12706437751337583515), + Felt::new(7320977330193495928), + Felt::new(15630435616748408136), + Felt::new(10194509925259146809), + Felt::new(15938750299626487367), ]), Word::new([ - Felt::new(9553923701032839042), - Felt::new(7281190920209838818), - Felt::new(2488477917448393955), - Felt::new(5088955350303368837), + Felt::new(9872217233988117092), + Felt::new(5336302253150565952), + Felt::new(9650742686075483437), + Felt::new(8725445618118634861), ]), Word::new([ - Felt::new(4935426252518736883), - Felt::new(12584230452580950419), - Felt::new(8762518969632303998), - Felt::new(18159875708229758073), + Felt::new(12539853708112793207), + Felt::new(10831674032088582545), + Felt::new(11090804155187202889), + Felt::new(105068293543772992), ]), Word::new([ - Felt::new(12795429638314178838), - Felt::new(14360248269767567855), - Felt::new(3819563852436765058), - Felt::new(10859123583999067291), + Felt::new(7287113073032114129), + Felt::new(6373434548664566745), + Felt::new(8097061424355177769), + Felt::new(14780666619112596652), ]), Word::new([ - Felt::new(2695742617679420093), - Felt::new(9151515850666059759), - Felt::new(15855828029180595485), - Felt::new(17190029785471463210), + Felt::new(17147873541222871127), + Felt::new(17350918081193545524), + Felt::new(5785390176806607444), + Felt::new(12480094913955467088), ]), Word::new([ - Felt::new(13205273108219124830), - Felt::new(2524898486192849221), - Felt::new(14618764355375283547), - Felt::new(10615614265042186874), + Felt::new(17273934282489765074), + Felt::new(8007352780590012415), + Felt::new(16690624932024962846), + Felt::new(8137543572359747206), ]), ]; @@ -456,38 +464,10 @@ mod p3_tests { } #[test] - #[ignore] // TODO: Re-enable after migrating RPO state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] fn test_rpo_hasher_vs_hash_elements() { - // Test with empty input - let expected: [Felt; 4] = Rpo256::hash_elements::(&[]).into(); let hasher = RpoHasher::new(RpoPermutation256); - let result = hasher.hash_iter([]); - assert_eq!(result, expected, "Empty input should produce same digest"); - - // Test with 4 elements (one digest worth) - let input4 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let expected: [Felt; 4] = Rpo256::hash_elements(&input4).into(); - let result = hasher.hash_iter(input4); - assert_eq!(result, expected, "4 elements should produce same digest"); - - // Test with 8 elements (exactly one rate) - let input8 = [ - Felt::new(1), - Felt::new(2), - Felt::new(3), - Felt::new(4), - Felt::new(5), - Felt::new(6), - Felt::new(7), - Felt::new(8), - ]; - let expected: [Felt; 4] = Rpo256::hash_elements(&input8).into(); - let result = hasher.hash_iter(input8); - assert_eq!(result, expected, "8 elements (one rate) should produce same digest"); - // Test with 12 elements (more than one rate) + // Test with 8 elements (one rate) let input12 = [ Felt::new(1), Felt::new(2), @@ -497,10 +477,6 @@ mod p3_tests { Felt::new(6), Felt::new(7), Felt::new(8), - Felt::new(9), - Felt::new(10), - Felt::new(11), - Felt::new(12), ]; let expected: [Felt; 4] = Rpo256::hash_elements(&input12).into(); let result = hasher.hash_iter(input12); @@ -531,9 +507,6 @@ mod p3_tests { } #[test] - #[ignore] // TODO: Re-enable after migrating RPO state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] fn test_rpo_compression_vs_merge() { let digest1 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; let digest2 = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs index 73d4d14c2..6e48f89e1 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs @@ -1,10 +1,9 @@ use super::{ - ARK1, ARK2, CAPACITY_RANGE, DIGEST_RANGE, Felt, MDS, NUM_ROUNDS, RATE_RANGE, Range, - STATE_WIDTH, Word, add_constants, add_constants_and_apply_ext_round, - add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, - apply_sbox, + ARK1, ARK2, AlgebraicSponge, CAPACITY_RANGE, DIGEST_RANGE, Felt, MDS, NUM_ROUNDS, RATE_RANGE, + RATE0_RANGE, RATE1_RANGE, Range, STATE_WIDTH, Word, add_constants, + add_constants_and_apply_ext_round, add_constants_and_apply_inv_sbox, + add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, apply_sbox, }; -use crate::hash::algebraic_sponge::AlgebraicSponge; #[cfg(test)] mod tests; @@ -93,17 +92,24 @@ impl Rpx256 { /// Target collision resistance level in bits. pub const COLLISION_RESISTANCE: u32 = 128; - /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for rate and - /// the remaining 4 elements are reserved for capacity. + /// Sponge state is set to 12 field elements or 768 bytes; 8 elements are reserved for the + /// rate and the remaining 4 elements are reserved for the capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The first 4-element word of the rate portion. + pub const RATE0_RANGE: Range = RATE0_RANGE; + + /// The second 4-element word of the rate portion. + pub const RATE1_RANGE: Range = RATE1_RANGE; + + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3 (the first + /// word of the state). pub const DIGEST_RANGE: Range = DIGEST_RANGE; /// MDS matrix used for computing the linear layer in the (FB) and (E) rounds. @@ -337,9 +343,9 @@ use p3_symmetric::{ /// `CryptographicPermutation` traits, allowing RPX to be used within the Plonky3 ecosystem. /// /// The permutation operates on a state of 12 field elements (STATE_WIDTH = 12), with: -/// - Rate: 8 elements (positions 4-11) -/// - Capacity: 4 elements (positions 0-3) -/// - Digest output: 4 elements (positions 4-7) +/// - Rate: 8 elements (positions 0-7) +/// - Capacity: 4 elements (positions 8-11) +/// - Digest output: 4 elements (positions 0-3) #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct RpxPermutation256; @@ -351,13 +357,14 @@ impl RpxPermutation256 { /// the remaining 4 elements are reserved for capacity. pub const STATE_WIDTH: usize = STATE_WIDTH; - /// The rate portion of the state is located in elements 4 through 11 (inclusive). + /// The rate portion of the state is located in elements 0 through 7 (inclusive). pub const RATE_RANGE: Range = Rpx256::RATE_RANGE; - /// The capacity portion of the state is located in elements 0, 1, 2, and 3. + /// The capacity portion of the state is located in elements 8, 9, 10, and 11. pub const CAPACITY_RANGE: Range = Rpx256::CAPACITY_RANGE; - /// The output of the hash function can be read from state elements 4, 5, 6, and 7. + /// The output of the hash function can be read from state elements 0, 1, 2, and 3 (the first + /// word of the state). pub const DIGEST_RANGE: Range = Rpx256::DIGEST_RANGE; // RPX PERMUTATION diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/tests.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/tests.rs index 723179c23..fcf1ec31a 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/tests.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/rpx/tests.rs @@ -325,21 +325,8 @@ mod p3_tests { } #[test] - #[ignore] // TODO: Re-enable after migrating RPX state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] fn test_rpx_hasher_vs_hash_elements() { - // Test with empty input - let expected: [Felt; 4] = Rpx256::hash_elements::(&[]).into(); let hasher = RpxHasher::new(RpxPermutation256); - let result = hasher.hash_iter([]); - assert_eq!(result, expected, "Empty input should produce same digest"); - - // Test with 4 elements (one digest worth) - let input4 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let expected: [Felt; 4] = Rpx256::hash_elements(&input4).into(); - let result = hasher.hash_iter(input4); - assert_eq!(result, expected, "4 elements should produce same digest"); // Test with 8 elements (exactly one rate) let input8 = [ @@ -381,9 +368,6 @@ mod p3_tests { } #[test] - #[ignore] // TODO: Re-enable after migrating RPX state layout to match Plonky3 - // Miden-crypto: capacity=[0-3], rate=[4-11] - // Plonky3: rate=[0-7], capacity=[8-11] fn test_rpx_compression_vs_merge() { let digest1 = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; let digest2 = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; diff --git a/miden-crypto/src/hash/algebraic_sponge/rescue/tests.rs b/miden-crypto/src/hash/algebraic_sponge/rescue/tests.rs index b8d750230..11ca76828 100644 --- a/miden-crypto/src/hash/algebraic_sponge/rescue/tests.rs +++ b/miden-crypto/src/hash/algebraic_sponge/rescue/tests.rs @@ -1,9 +1,14 @@ #![cfg(feature = "std")] use p3_field::PrimeCharacteristicRing; -use super::{ALPHA, Felt, INV_ALPHA}; +use super::Felt; use crate::rand::test_utils::rand_value; +/// S-Box power for Rescue Prime hash function. +const ALPHA: u64 = 7; +/// Inverse S-Box power for Rescue Prime hash function. +const INV_ALPHA: u64 = 10540996611094048183; + #[test] fn test_alphas() { let e: Felt = Felt::new(rand_value()); diff --git a/miden-crypto/src/hash/blake/mod.rs b/miden-crypto/src/hash/blake/mod.rs index 8d6520b19..cd253a53f 100644 --- a/miden-crypto/src/hash/blake/mod.rs +++ b/miden-crypto/src/hash/blake/mod.rs @@ -6,7 +6,7 @@ use core::{ }; use p3_field::{BasedVectorSpace, PrimeField64}; -use p3_miden_goldilocks::Goldilocks as Felt; +use p3_goldilocks::Goldilocks as Felt; use super::HasherExt; use crate::utils::{ diff --git a/miden-crypto/src/hash/blake/tests.rs b/miden-crypto/src/hash/blake/tests.rs index d92d7855f..57c810af0 100644 --- a/miden-crypto/src/hash/blake/tests.rs +++ b/miden-crypto/src/hash/blake/tests.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use p3_field::PrimeField64; -use p3_miden_goldilocks::Goldilocks as Felt; +use p3_goldilocks::Goldilocks as Felt; use proptest::prelude::*; use super::*; diff --git a/miden-crypto/src/hash/mod.rs b/miden-crypto/src/hash/mod.rs index 57d452988..9afdfd4b7 100644 --- a/miden-crypto/src/hash/mod.rs +++ b/miden-crypto/src/hash/mod.rs @@ -13,7 +13,7 @@ pub mod sha2; /// Poseidon2 hash function. pub mod poseidon2 { - pub use p3_miden_goldilocks::Poseidon2Goldilocks; + pub use p3_goldilocks::Poseidon2Goldilocks; pub use super::algebraic_sponge::poseidon2::{ Poseidon2, Poseidon2Challenger, Poseidon2Compression, Poseidon2Hasher, diff --git a/miden-crypto/src/lib.rs b/miden-crypto/src/lib.rs index 3e13fc7b8..8d41c94fd 100644 --- a/miden-crypto/src/lib.rs +++ b/miden-crypto/src/lib.rs @@ -19,7 +19,7 @@ pub mod word; // RE-EXPORTS // ================================================================================================ -pub use p3_miden_goldilocks::Goldilocks as Felt; +pub use p3_goldilocks::Goldilocks as Felt; pub use word::{Word, WordError}; pub mod field { @@ -33,6 +33,15 @@ pub mod field { }; } +pub mod parallel { + //! Conditional parallel iteration primitives. + //! + //! When the `concurrent` feature is enabled, this module re-exports parallel iterator + //! traits from `p3-maybe-rayon` backed by rayon. Without `concurrent`, these traits + //! fall back to sequential iteration. + pub use p3_maybe_rayon::prelude::*; +} + pub mod stark { //! Foundational components for the STARK proving system based on Plonky3. //! diff --git a/miden-crypto/src/merkle/empty_roots.rs b/miden-crypto/src/merkle/empty_roots.rs index 5c3fe1ae4..b993b4119 100644 --- a/miden-crypto/src/merkle/empty_roots.rs +++ b/miden-crypto/src/merkle/empty_roots.rs @@ -1,6 +1,6 @@ use core::slice; -use super::{EMPTY_WORD, Felt, Word, smt::InnerNode}; +use super::{Felt, Word, smt::InnerNode}; // EMPTY NODES SUBTREES // ================================================================================================ @@ -40,1536 +40,1536 @@ impl EmptySubtreeRoots { const EMPTY_SUBTREES: [Word; 256] = [ Word::new([ - Felt::new(0xee3d94db86d48dc4), - Felt::new(0x3d13166c7aba0368), - Felt::new(0x282e861f2936aa7), - Felt::new(0xf0328a1745537b4), + Felt::new(0x7a1518d5c23cc058), + Felt::new(0x68c3009f92842fe8), + Felt::new(0xd5f1a30fb13293d2), + Felt::new(0x2cbc88b0ef345756), ]), Word::new([ - Felt::new(0x33174b312b730760), - Felt::new(0x9d1e00c5b50352b2), - Felt::new(0x16bf9ec4acef2e42), - Felt::new(0x4263877e63c4cbe7), + Felt::new(0x10192708daa40df6), + Felt::new(0xb3613b10fc0b47ee), + Felt::new(0xc95db182feebc1e5), + Felt::new(0x809d1e4bbb7c7c33), ]), Word::new([ - Felt::new(0xa8e039042672a8bc), - Felt::new(0x1010d951d941d9d4), - Felt::new(0xd3e8f0ecc866ac3b), - Felt::new(0xb2dbbbb80da232ba), + Felt::new(0xad7baa5771559881), + Felt::new(0x5de508956dd867ae), + Felt::new(0x2905c6540347266e), + Felt::new(0x40dc622dd8de98fa), ]), Word::new([ - Felt::new(0xa0d2c1a3e455f299), - Felt::new(0x648e8e13867dc8eb), - Felt::new(0xe79f94ea61189847), - Felt::new(0xb88a89e1f2765a31), + Felt::new(0xbace6cc736e6618d), + Felt::new(0x4c3dabe05eae1960), + Felt::new(0x99a1853ba5a1932e), + Felt::new(0x89ca023beb042e34), ]), Word::new([ - Felt::new(0xe168133fd9ab570d), - Felt::new(0x6fc7d0295ac5a3a2), - Felt::new(0xc973ea026e9411c), - Felt::new(0x63c29ea04552b532), + Felt::new(0x1c6e4ae9e5fc202a), + Felt::new(0x357a7ec4c1cf5318), + Felt::new(0xebf47c3fd4907657), + Felt::new(0xd8b4615d7a06584c), ]), Word::new([ - Felt::new(0x27e57ecc0f9f196), - Felt::new(0xc02f6e29fd19b059), - Felt::new(0x4a42fbc436efb0b6), - Felt::new(0xbff30574a98a1b29), + Felt::new(0x6521715bc4f45483), + Felt::new(0x2d716202b5dc0c89), + Felt::new(0x722e8ddc909837c9), + Felt::new(0x9eef9cbb233433d9), ]), Word::new([ - Felt::new(0x18c32592a4f4d0b), - Felt::new(0x6b9a08797252d5d5), - Felt::new(0xdbfe48f00a088a2), - Felt::new(0x16b4c3e485b173e3), + Felt::new(0x21a2144315f40cc9), + Felt::new(0xfad12b6052f35f53), + Felt::new(0x4f58a0263b6ac06b), + Felt::new(0x1c5a93c3e0ed3b81), ]), Word::new([ - Felt::new(0x26c8902938b831a5), - Felt::new(0x66ee91b36943f92e), - Felt::new(0x4e8deeafef9f5725), - Felt::new(0xbb35751d5dfb0a33), + Felt::new(0xe17df7b2e3f1f4fd), + Felt::new(0x78ddd43c79108b8b), + Felt::new(0xa944f62ebf53debf), + Felt::new(0xdc419e00ea1ac673), ]), Word::new([ - Felt::new(0x74af678f8e020ff4), - Felt::new(0xd4784cda0beed295), - Felt::new(0x4380949d841d793c), - Felt::new(0xdf587011d09d3bbb), + Felt::new(0x13550b71498b1593), + Felt::new(0x6ffdfe94d842999f), + Felt::new(0x17ed8a0d20f1a97a), + Felt::new(0x8470bed4ee7714fb), ]), Word::new([ - Felt::new(0xa211d1da76aaef98), - Felt::new(0xd904ccc6435e268), - Felt::new(0x1c6f16a5d03b8416), - Felt::new(0x87800f7f5da9c93), + Felt::new(0xca8c0f221d45e21c), + Felt::new(0xab7ec8a17338a271), + Felt::new(0x198d2c572ad44b73), + Felt::new(0x97fb2d5d68366dfe), ]), Word::new([ - Felt::new(0xa00bbad0a52adeff), - Felt::new(0xe22179c651da9d76), - Felt::new(0x474f10493a3723f4), - Felt::new(0x84397e6bd34a1f5b), + Felt::new(0xb227f02eca29f805), + Felt::new(0xf481fa56981769e8), + Felt::new(0xe5e143a48c597276), + Felt::new(0x882fd1e2a743442), ]), Word::new([ - Felt::new(0xe8f440afef4d082b), - Felt::new(0x14fff8e329613cc9), - Felt::new(0x78e984bc8b40f4f1), - Felt::new(0x6ed8f02e5be1bab2), + Felt::new(0xfadcfe0374b44556), + Felt::new(0x43a3da6902312ed1), + Felt::new(0x2900f9e01835eac8), + Felt::new(0x2830bc4d5a08631b), ]), Word::new([ - Felt::new(0xda824edf085b5f9f), - Felt::new(0xc8a8f1c1b86d349e), - Felt::new(0xe1bf6975afb7b2de), - Felt::new(0xd7df51ea51028489), + Felt::new(0x6e425eaeaf389630), + Felt::new(0x8060635d9c8d40bd), + Felt::new(0x782055fcc4027bab), + Felt::new(0x1cd8f421713d0039), ]), Word::new([ - Felt::new(0xf64873d31456de99), - Felt::new(0x1fc9cb920b6c72b), - Felt::new(0x96613d9d71af4373), - Felt::new(0x61d607eb097e76c9), + Felt::new(0x7bba2a234f88e315), + Felt::new(0xcd615c602b5fe2cd), + Felt::new(0x5a9e081b1af5d7fa), + Felt::new(0xecfa1e7751d9919), ]), Word::new([ - Felt::new(0xca304d2b3b778719), - Felt::new(0xa54d8602f37eed39), - Felt::new(0xb4574db6dc09bcf2), - Felt::new(0x5e42cd4f1de9587c), + Felt::new(0xac2565372049b11), + Felt::new(0x1399af9f36718fdb), + Felt::new(0x86e0730595695b77), + Felt::new(0xce24042e52ea8f72), ]), Word::new([ - Felt::new(0x17575dfa689d8a07), - Felt::new(0x1db9d374d7436444), - Felt::new(0x21d1e8dca296f38d), - Felt::new(0xbc4aad43a9d93f54), + Felt::new(0x31a44240c5ea1596), + Felt::new(0x6ac2462879567985), + Felt::new(0xc5dff2fa6e4bf9ed), + Felt::new(0x109baf62f5656431), ]), Word::new([ - Felt::new(0x9fa0697330c054cd), - Felt::new(0xd5d57fbf059452e8), - Felt::new(0xe848fafb1c43414c), - Felt::new(0xacb7754fd77c9d52), + Felt::new(0xc0a1d9643a789711), + Felt::new(0x287754605bb44336), + Felt::new(0x96e143926e6b05a7), + Felt::new(0x27896b88297f95fe), ]), Word::new([ - Felt::new(0x406af89b918e596c), - Felt::new(0xb735a2c588ee87df), - Felt::new(0xb40ff1dd1c3c6599), - Felt::new(0x675a582b4c8a68ac), + Felt::new(0x68bf7a3147fe7190), + Felt::new(0xad11cc1491d94621), + Felt::new(0x1c5684427eb041d3), + Felt::new(0xd2cd7f76c89ed840), ]), Word::new([ - Felt::new(0x530ff6be0c86a2f6), - Felt::new(0x5541fabfefd34c91), - Felt::new(0x4af1579d212149ae), - Felt::new(0x23962b7df862f27c), + Felt::new(0xd227c79d40d38aa0), + Felt::new(0xde50cadec8a7f9e8), + Felt::new(0x1966a4fabc12be53), + Felt::new(0x2cc1aec4bf8eb07a), ]), Word::new([ - Felt::new(0x1676b694f41cfc0d), - Felt::new(0x59b165ea5f354fd8), - Felt::new(0x5b45ee14e2501f08), - Felt::new(0xd0c8ca7bc2e01e18), + Felt::new(0x215ee19f91f3b3c), + Felt::new(0xa431547dc8268b58), + Felt::new(0x2a0d094750f517b7), + Felt::new(0x1bed221ea2d85b43), ]), Word::new([ - Felt::new(0x2cff5d1e629ddc7b), - Felt::new(0x5062be34e4351fe), - Felt::new(0xfd76495b9d8ea67), - Felt::new(0xb96453b1c8060ca8), + Felt::new(0x3cdc78415946c8a5), + Felt::new(0x5f907795364f886a), + Felt::new(0xcda1dc461e842b0a), + Felt::new(0x713eef642ca36a92), ]), Word::new([ - Felt::new(0x860b00517d3de1ef), - Felt::new(0xd609c82af07b9dad), - Felt::new(0xa54a528b8f1cbddc), - Felt::new(0xb4fff658ed97e635), + Felt::new(0x53f883132cfd1fc3), + Felt::new(0xed640e5ed33c0033), + Felt::new(0xcb2403903bfcc4a7), + Felt::new(0xfddb8e4711c089c7), ]), Word::new([ - Felt::new(0xd002cea8f347c347), - Felt::new(0xa135cebffdf3ec10), - Felt::new(0xb0200ea08da2cef4), - Felt::new(0x4e6f2f75d627b137), + Felt::new(0xbc76a3d22dc4a6a3), + Felt::new(0xc5132a2358773b7b), + Felt::new(0x160effcc8acea428), + Felt::new(0xc7e8567ab4eb5d75), ]), Word::new([ - Felt::new(0xc1983ce677cabbf4), - Felt::new(0x58f0143480f44788), - Felt::new(0xf8c23e4f84b6c6c1), - Felt::new(0xc9ce41371c4900b8), + Felt::new(0x6227233a672c4d53), + Felt::new(0x7021cea1e48eeef0), + Felt::new(0xeddedec3ef0be480), + Felt::new(0xa12e79c58f0f5195), ]), Word::new([ - Felt::new(0x837d99979bc9a5e6), - Felt::new(0x7621559aa4af175a), - Felt::new(0x6986737347c799c2), - Felt::new(0x8cee99eb47c3e702), + Felt::new(0x968638ed9f01e885), + Felt::new(0xffea5aba5916bc0), + Felt::new(0x839a8aed3e4a4d4a), + Felt::new(0xcc8ced80d6d55b30), ]), Word::new([ - Felt::new(0x42e17ba02508a41f), - Felt::new(0xb95e349bd55ba61f), - Felt::new(0xcc2bfeb29c4c68b2), - Felt::new(0xf268f57860a446b1), + Felt::new(0x420f83d6ff9afeb6), + Felt::new(0x8175237f0a60471d), + Felt::new(0xd2e50161a05523c0), + Felt::new(0x58758141d44bb085), ]), Word::new([ - Felt::new(0xd3ffd4ccc6dda508), - Felt::new(0x81db1910ef04ca07), - Felt::new(0x5c698ee6c3aeab97), - Felt::new(0x2ac1e2c2c5f237de), + Felt::new(0x8f6c9d38bcd56c06), + Felt::new(0x5d76e879f318dbb), + Felt::new(0x63824a7a2d1e847e), + Felt::new(0x157a7b7cd42546a0), ]), Word::new([ - Felt::new(0x1f42a1ef25bd0aad), - Felt::new(0x81b0f63e2760b8db), - Felt::new(0xe9607c7061b018f9), - Felt::new(0xf02a88202294a700), + Felt::new(0x7b09646224c1fe2e), + Felt::new(0xa382a27d974d9aa8), + Felt::new(0xff220f696f8bb528), + Felt::new(0x1abc61b6597dc6c5), ]), Word::new([ - Felt::new(0xea5da09b39b60468), - Felt::new(0xe48ea41d94fe91a7), - Felt::new(0x24dde954ce08b32b), - Felt::new(0xe1bb6e41bd0613e6), + Felt::new(0x2288f10b538108f5), + Felt::new(0x6ae6919992381a06), + Felt::new(0xfb2ff6c6d7a95519), + Felt::new(0x90da6d7a159eab68), ]), Word::new([ - Felt::new(0xc5e9f7188b43a24f), - Felt::new(0x8d7132abc9d901e4), - Felt::new(0xdc09a33ff4d0eb03), - Felt::new(0xa119bb1db594b4cf), + Felt::new(0x8396e3f3e4411a3e), + Felt::new(0x8a7a5d5c8d5b9453), + Felt::new(0xb74a5105cb6340d3), + Felt::new(0xef3a0ed14f6349fb), ]), Word::new([ - Felt::new(0x589002afcbd4a233), - Felt::new(0xe4eae44d3c2a308d), - Felt::new(0x8bc0bca14b6b4dde), - Felt::new(0x3716e0e86a7aaa6c), + Felt::new(0x19661575d6f57881), + Felt::new(0x3979a60e79d44f7c), + Felt::new(0x6bfb0808c2a0170e), + Felt::new(0x63e20a4286bb3f0f), ]), Word::new([ - Felt::new(0xaa4ba9602230007e), - Felt::new(0x2b2c3e14b888a3d4), - Felt::new(0x90a36fb42ec2ba19), - Felt::new(0x2e07ef26b078c4a7), + Felt::new(0xe9b45b0e8461467), + Felt::new(0x49df3dc7fe793402), + Felt::new(0x9b77b3d339e4e9d4), + Felt::new(0x51da4e29d3f43dba), ]), Word::new([ - Felt::new(0x32307da7aad33113), - Felt::new(0x343ed87928b9ab0c), - Felt::new(0x1c01d79482c021f0), - Felt::new(0x6f866afccc595439), + Felt::new(0xe70a0e47287d6ea1), + Felt::new(0x8ecbdce5ed4904e1), + Felt::new(0x48a8d45f3fb7bfe9), + Felt::new(0x303b724ed4de6599), ]), Word::new([ - Felt::new(0x9780804b58b0d066), - Felt::new(0x1329929c6dc19c09), - Felt::new(0xc04add06dbaef6bf), - Felt::new(0xf494a28db17c5c4), + Felt::new(0x11e1af5bdbb1dd3c), + Felt::new(0xd623d0a0e66efba3), + Felt::new(0x820e7f8c20355f29), + Felt::new(0xd14d6d3907f79f54), ]), Word::new([ - Felt::new(0xe9dbb1c64d55571f), - Felt::new(0x663f0f716f28734), - Felt::new(0x7285fd4b8e87a78c), - Felt::new(0x2e152a4595b7597e), + Felt::new(0x264792bb76d1c6c7), + Felt::new(0xe2982176db15e281), + Felt::new(0x8e563c0ffd9cf26f), + Felt::new(0xb84d656d2098fb2c), ]), Word::new([ - Felt::new(0x5531fabfa5960807), - Felt::new(0x8afe79be96d903a4), - Felt::new(0x24321cce4f1942f8), - Felt::new(0xb1829ec9d60aac8f), + Felt::new(0xb4b65da1a2f37ac1), + Felt::new(0x8ce542279c62b719), + Felt::new(0x2bb44a38cd28334), + Felt::new(0x3460af36ee05e6a4), ]), Word::new([ - Felt::new(0x9f7afc6634a82d1), - Felt::new(0x496e26bc17af352b), - Felt::new(0x8216f090e1d13381), - Felt::new(0x610cf5a3b3e190f9), + Felt::new(0xd0c6531207ed16da), + Felt::new(0x65b14c94e53348ca), + Felt::new(0xb146aa53607084ed), + Felt::new(0x1be4eed9b6ae4de7), ]), Word::new([ - Felt::new(0xb5f8c141a9acd007), - Felt::new(0x4430345ace970576), - Felt::new(0x64d97e5533db3170), - Felt::new(0x95c016d769b0fc2d), + Felt::new(0x63a3380d1a006c75), + Felt::new(0xa739bf6245222179), + Felt::new(0x13df3fe01a95dd79), + Felt::new(0xcb589a58b4375231), ]), Word::new([ - Felt::new(0x88820d6a7ba5a94a), - Felt::new(0x27b614d79eb7b30b), - Felt::new(0xff2751e904085d5f), - Felt::new(0x752509a0860b37d), + Felt::new(0x275515c40097d89b), + Felt::new(0x464331cfc86aaf76), + Felt::new(0x2d69a8c1271cc1c9), + Felt::new(0x9f135fa326f0a170), ]), Word::new([ - Felt::new(0x1070bc84bb53a855), - Felt::new(0x1edad3d5da84e59b), - Felt::new(0x8efd48a13e4dfe0d), - Felt::new(0x3ab20af6203aba62), + Felt::new(0x330774dff4622a45), + Felt::new(0xdab997e374e73361), + Felt::new(0x722fd5d7b681296b), + Felt::new(0x652d9ac7fb305ca1), ]), Word::new([ - Felt::new(0xb4d6d3cc85438d08), - Felt::new(0x5592639fb2792724), - Felt::new(0x5939996ea4c52176), - Felt::new(0xaa83a79236367ee7), + Felt::new(0x8ac8a548fa2901b2), + Felt::new(0xe5939b720a40e027), + Felt::new(0xe1c12b4143f08e7), + Felt::new(0x47181ac3bbfe7c55), ]), Word::new([ - Felt::new(0x4c08ac735aa1925a), - Felt::new(0x84951e177ac84e86), - Felt::new(0xd5b2657778d3271a), - Felt::new(0x375f75333654a77c), + Felt::new(0x75f1101119bd9132), + Felt::new(0x48061d0a7e02ff4f), + Felt::new(0x87321a2bb298fa5), + Felt::new(0xaa9e0199e75a8b88), ]), Word::new([ - Felt::new(0x2fcbd8fcd125e5), - Felt::new(0xd8f711ed1b369d43), - Felt::new(0x9688301695b6bcd4), - Felt::new(0x52a010319401179), + Felt::new(0x203e8caee818bbe7), + Felt::new(0x54ec39e86e3bfc74), + Felt::new(0x37a6a845c6b64b5a), + Felt::new(0xd18931d1b800606c), ]), Word::new([ - Felt::new(0x1c67f8fde4c9c070), - Felt::new(0x438ccdf9d82b3a3f), - Felt::new(0xb9324515d5547ff5), - Felt::new(0x85ff37504c8230f0), + Felt::new(0x57e03388213c8b13), + Felt::new(0x47f26439d9764d57), + Felt::new(0xee8b0e6683b76e4d), + Felt::new(0x9d732634cb929b4d), ]), Word::new([ - Felt::new(0xcf8b6fabda4621f3), - Felt::new(0x1df94bb4ea8aeb6d), - Felt::new(0x8efffb7e8996b9e5), - Felt::new(0xa9aef575e8a86c4d), + Felt::new(0x6e22ad3534c01591), + Felt::new(0x2c54132d486f7026), + Felt::new(0x8efa48a4071e3403), + Felt::new(0x9949b817da0f2395), ]), Word::new([ - Felt::new(0x6e20862a64baaaef), - Felt::new(0xc54fbbfa034d6f1b), - Felt::new(0x16d9fd099f5bba71), - Felt::new(0xe4ac4cf3186fae83), + Felt::new(0x48415b26c3ff6633), + Felt::new(0xa3e301f4a2512c0), + Felt::new(0x45223bfb0ea9933f), + Felt::new(0x704bf93dfdb84760), ]), Word::new([ - Felt::new(0x12914625293d7f84), - Felt::new(0xd3b46add4f77be8), - Felt::new(0xaac8846e6eeb9acd), - Felt::new(0xab6a69452b4b167b), + Felt::new(0x18c8d36aca06d30a), + Felt::new(0x599733c783e95db0), + Felt::new(0x47d6e190eae194d9), + Felt::new(0x9bceba0c6f764cef), ]), Word::new([ - Felt::new(0x69652e812cdfe03d), - Felt::new(0x22731622b139de96), - Felt::new(0xd7226e9a887f368d), - Felt::new(0xe9bbf6ad8f51ee13), + Felt::new(0x90617f8240949645), + Felt::new(0xe0a01888d04566a7), + Felt::new(0xc413a63dc2e5b2a7), + Felt::new(0x4b5df8cbc7de8b0a), ]), Word::new([ - Felt::new(0xc39a01964af141d7), - Felt::new(0xb5ab2062263dcaa2), - Felt::new(0x1d7fbcd9204cbd34), - Felt::new(0xd48c517d5543c163), + Felt::new(0xe35b06d63cef6c26), + Felt::new(0xb9f9bb36756e258), + Felt::new(0xb46751fdf68f1f85), + Felt::new(0x95b95fd16bc2e91e), ]), Word::new([ - Felt::new(0x44118fda0c2b4af2), - Felt::new(0x487d307ce7444bb2), - Felt::new(0x171b7c6a17d734b2), - Felt::new(0xd9a737ddf65949d9), + Felt::new(0x5d70054d30c0bc16), + Felt::new(0xec83cbed7d84059d), + Felt::new(0x589fa4f1fb83a737), + Felt::new(0x704ba66a12a7cb82), ]), Word::new([ - Felt::new(0xc2cdc1b940450fec), - Felt::new(0x29864b9632eff0cd), - Felt::new(0x9ae31f150850e78c), - Felt::new(0xf9f9d0ef1092be87), + Felt::new(0xccd71b6ac807031f), + Felt::new(0x27c6d6972ca2f038), + Felt::new(0x15f7b976b1a83b6a), + Felt::new(0x2fc2021f31eda8e8), ]), Word::new([ - Felt::new(0x1703dd34002f3862), - Felt::new(0xf04b44446be81ea1), - Felt::new(0x8da51598849beb99), - Felt::new(0x8112e155f7f856a0), + Felt::new(0x5b83a427101b26d1), + Felt::new(0xe228ce6616b91449), + Felt::new(0x6dd910afce37d26a), + Felt::new(0xd5a859060e53feba), ]), Word::new([ - Felt::new(0x3d4da8351f41dc1c), - Felt::new(0x682e55817f56f30b), - Felt::new(0xf20cc7fe5b98b951), - Felt::new(0x8297d3de042785d4), + Felt::new(0x2ec1ebe8d98ab168), + Felt::new(0x84940081d4b37e6a), + Felt::new(0xb5abbe477682d673), + Felt::new(0xb6e35a3f33737ce5), ]), Word::new([ - Felt::new(0x1f9d07a435a6d13e), - Felt::new(0x789a1330825c199a), - Felt::new(0x6e058e9dbc30f3a0), - Felt::new(0xb09be46b59290984), + Felt::new(0x1f81bb8e5d9b6775), + Felt::new(0x2041277fe91d6647), + Felt::new(0x9f33bcba3329e05), + Felt::new(0x2ab1e65fd5a966e), ]), Word::new([ - Felt::new(0xaf2d49c9a3975d21), - Felt::new(0xebd4d399fc30a751), - Felt::new(0x224a3884ca353e5d), - Felt::new(0xbebba344bbe055a7), + Felt::new(0x33bc5fb208fa395e), + Felt::new(0x5410fc2bf42413d4), + Felt::new(0xe1ad110a80d22fd), + Felt::new(0xa2054e3a0d379015), ]), Word::new([ - Felt::new(0xdf576dc16b0abc3f), - Felt::new(0x40439af403c36338), - Felt::new(0x317b1f2308849c53), - Felt::new(0x91e5c9d14107cb04), + Felt::new(0x9699cc4fdfd42834), + Felt::new(0xa76f357aad26bd8e), + Felt::new(0x7f53ea51859546da), + Felt::new(0xc6cf1c173bfe674d), ]), Word::new([ - Felt::new(0x93af916aa15f97e2), - Felt::new(0x50d4aec3e408fba7), - Felt::new(0xd16bd5f71b6d6915), - Felt::new(0x27b96db871be03ef), + Felt::new(0xa6ed91e61e570a03), + Felt::new(0xddd8836d1bcf7a1b), + Felt::new(0x151fa005c6cbec3e), + Felt::new(0x5a6b90196b04c5e), ]), Word::new([ - Felt::new(0x72fce6dd7d54e348), - Felt::new(0x632a2e8b6177c670), - Felt::new(0xefd897bebdc4ec2b), - Felt::new(0xfe66bfe440033790), + Felt::new(0xa1384b20e2f7f0e0), + Felt::new(0x5997839cc1485d52), + Felt::new(0x764262542d2ce276), + Felt::new(0xbb5ce0fc148f2f8), ]), Word::new([ - Felt::new(0xc581364aef408d6a), - Felt::new(0xfcc7efb35cccae32), - Felt::new(0xee0a97dded065fbf), - Felt::new(0x2b1eb2c45fd0e633), + Felt::new(0xcf58de7ecc74b7d0), + Felt::new(0x2d05d90f8f89a003), + Felt::new(0x140c36afde7a8503), + Felt::new(0x2c85492253719879), ]), Word::new([ - Felt::new(0x9e460e8159152a88), - Felt::new(0xcc5a2946f03bf507), - Felt::new(0x95535e9cf29e4ab9), - Felt::new(0x29b23d31ffe6df18), + Felt::new(0xc43f9d1075e50eb5), + Felt::new(0x21a4177440ee0514), + Felt::new(0x6556ffb110aee068), + Felt::new(0x5c4ac4233182eed8), ]), Word::new([ - Felt::new(0xbae2c405d8ba715d), - Felt::new(0xb886f0545ae16153), - Felt::new(0x728d5965a4cdfc0b), - Felt::new(0x86bd552048f3ebc4), + Felt::new(0xd161b6f2ba6083eb), + Felt::new(0xf9ca3dff0f55b7b0), + Felt::new(0xea722afc3a4ca67c), + Felt::new(0xfa69b06dd2793839), ]), Word::new([ - Felt::new(0x3a4c6dbaa6feda93), - Felt::new(0x8a32917885a3f22c), - Felt::new(0xd6016ba7fc1a0717), - Felt::new(0x3bfd41569497b156), + Felt::new(0x49937dceddf01780), + Felt::new(0xd0923d2e8faf74c4), + Felt::new(0x820f1991402fc8a8), + Felt::new(0x67a2d055ea1f777c), ]), Word::new([ - Felt::new(0xa907fad371653f15), - Felt::new(0x6be9ce6ac746f5bc), - Felt::new(0x1bee5ac8750d2444), - Felt::new(0x16050d83d4f7a90c), + Felt::new(0xc39ffe45524b9e65), + Felt::new(0x956a2ac4ff4c63bb), + Felt::new(0x93eeccaea6136bb2), + Felt::new(0x7b88b7f2852ed69b), ]), Word::new([ - Felt::new(0x4b194182aa7e9324), - Felt::new(0x813af49c845cea5e), - Felt::new(0x6886f4d8628bab16), - Felt::new(0xe3b6ef1419e2432c), + Felt::new(0x7ebf64a852d49f4c), + Felt::new(0x5d2f34df27b34c2b), + Felt::new(0xd82dd65960331467), + Felt::new(0x7ee8a1c82f2775ba), ]), Word::new([ - Felt::new(0x3edc103de28f1fac), - Felt::new(0xb6a05b8802d6ed5c), - Felt::new(0xf320c3f130a175c8), - Felt::new(0x326c8bb02f9a51f6), + Felt::new(0x341eef38eb1b44b0), + Felt::new(0x11e89c557152d8d7), + Felt::new(0xd298bf05bcfe7f29), + Felt::new(0xb6ab5d0889a6a990), ]), Word::new([ - Felt::new(0x5b1ac27a49b5d1da), - Felt::new(0x9e1fa75b04da7545), - Felt::new(0x9a522396a1cd68af), - Felt::new(0x91a4d435f3fcd43f), + Felt::new(0x9d0e247831447b45), + Felt::new(0xb9b37f61a359984e), + Felt::new(0x35acfcf2065910a1), + Felt::new(0x39ed68f475187cd6), ]), Word::new([ - Felt::new(0x318ac5d8f1e489ce), - Felt::new(0x339e7a0b2aec5843), - Felt::new(0x38f15bf9832a2c28), - Felt::new(0x5e3fef94216f72f1), + Felt::new(0x3fac499e38fc1c4f), + Felt::new(0xac5f1f88b4c0b263), + Felt::new(0x8ae162b1de7dd0e9), + Felt::new(0x695b42060377397c), ]), Word::new([ - Felt::new(0xc43e0723d2a7e79c), - Felt::new(0xa06167cc0ebdf1e5), - Felt::new(0xe62f10089af57ba6), - Felt::new(0x838c863d60b859a2), + Felt::new(0xbd58816e80b1327), + Felt::new(0x7c6f6ececda432bc), + Felt::new(0x8a5806e14c3af9fe), + Felt::new(0x6d49dacfbb83465b), ]), Word::new([ - Felt::new(0xd10456af5f30e5d5), - Felt::new(0x235df7fe21fb912c), - Felt::new(0xe5acc29d13d80779), - Felt::new(0x580b83247a1f6524), + Felt::new(0xf6015087f3a73253), + Felt::new(0x175d591b6e76f2cc), + Felt::new(0x7bec8218e9320dd1), + Felt::new(0x7d31b42b1ce3ab16), ]), Word::new([ - Felt::new(0x2a8b1bf7e9bc5675), - Felt::new(0x9e523f2d659a3e30), - Felt::new(0x3ecfdb1615666b74), - Felt::new(0xf53746b86fedee7f), + Felt::new(0xf0c9e338add8f04d), + Felt::new(0xf2dc1dae4ddae881), + Felt::new(0xa2fcda9b1106379e), + Felt::new(0x5a0de26644862d15), ]), Word::new([ - Felt::new(0xa12095b3b22680a9), - Felt::new(0x3010ad751585161d), - Felt::new(0xfb9c0ea33c7437b2), - Felt::new(0x9225d8151ec724a8), + Felt::new(0xb5c03bd3c6896148), + Felt::new(0xfe7a36bfa33c147a), + Felt::new(0x729e44eed48564a8), + Felt::new(0x22cf77310aa13eee), ]), Word::new([ - Felt::new(0x1b09eac8ad815107), - Felt::new(0x33cb241ad41b562d), - Felt::new(0xa04f457b4cd1ece9), - Felt::new(0x84f27a45985d700e), + Felt::new(0x9772b44487c3cd80), + Felt::new(0xc669f2622ac9ca43), + Felt::new(0x942fc94dad4a902e), + Felt::new(0x3f89e3b19deb8c97), ]), Word::new([ - Felt::new(0xe5598d92d1507185), - Felt::new(0x84aa2bf7d87a26e8), - Felt::new(0x158f0e13550dec2a), - Felt::new(0x54d699e5eb65ee63), + Felt::new(0x48485ad32a4c0f96), + Felt::new(0x5613169c77f1b833), + Felt::new(0x25472149c26b0004), + Felt::new(0x63d85088841a9ee8), ]), Word::new([ - Felt::new(0x902e89f122f8f8f7), - Felt::new(0xc2da7127af8c699a), - Felt::new(0x75762e75b77a1662), - Felt::new(0x7e683b3c116af130), + Felt::new(0x7f25634b7a87282d), + Felt::new(0x834e1d48d1253c1d), + Felt::new(0x727e328428579bbc), + Felt::new(0x7ca91d98d4a1bf3c), ]), Word::new([ - Felt::new(0xabc2aa2ecd2316dd), - Felt::new(0x44558fa721857f00), - Felt::new(0xf61dd475fdbc23d0), - Felt::new(0x22ba84332065a9e8), + Felt::new(0x8094a778288c8003), + Felt::new(0xf8696b015bd1d989), + Felt::new(0x987f8a6c08926e30), + Felt::new(0xf2e59da44b90b561), ]), Word::new([ - Felt::new(0x5aa94e045e4bb7ae), - Felt::new(0xf6ddadbdd8747728), - Felt::new(0xeeab65efab2a1d2), - Felt::new(0xd12cc579c49b9db5), + Felt::new(0xbe0f3a746cdbdc00), + Felt::new(0x5680d157e1c756fc), + Felt::new(0x59399408affa9b63), + Felt::new(0x8b226544c63fc2a1), ]), Word::new([ - Felt::new(0x71ea68262a73196a), - Felt::new(0x9612483af09f1bde), - Felt::new(0x7fe5fd69bbf241a4), - Felt::new(0x34de27c57b37975d), + Felt::new(0x1b03f4daa6c14153), + Felt::new(0xcaf37c5daad9a5a8), + Felt::new(0xedf3f3cf082b0a1), + Felt::new(0x7bb50256590e6865), ]), Word::new([ - Felt::new(0xf29bc8ba140714f6), - Felt::new(0xf0b44caca4f6561e), - Felt::new(0x742695d702446774), - Felt::new(0x7e1437b52ee16c0c), + Felt::new(0x684ed80902ec623c), + Felt::new(0x9bb72a38cfd2ef85), + Felt::new(0x4e430aacabcf36a9), + Felt::new(0xb92c1d0058aab97a), ]), Word::new([ - Felt::new(0x13f6180493eaa129), - Felt::new(0x8fa2e77f499c911c), - Felt::new(0x1223e5ccda975bf), - Felt::new(0xc2a362e5449eac8b), + Felt::new(0x583f0f6c7a070004), + Felt::new(0x79ff8256f4b8502a), + Felt::new(0xf526e258a8562a7e), + Felt::new(0x61968b703bd4e7cc), ]), Word::new([ - Felt::new(0xcf1254ec733c8fb0), - Felt::new(0x34359ae1e2272fc9), - Felt::new(0xce928a65262d59d5), - Felt::new(0xc84e1f72e2e78101), + Felt::new(0x518358593f4db560), + Felt::new(0xdb403bba6c33d816), + Felt::new(0x9f4ddc99df045703), + Felt::new(0x92aa4f28f2824faf), ]), Word::new([ - Felt::new(0x8841b659676a2df5), - Felt::new(0x4c808c965135ff8f), - Felt::new(0x374d574fd96ee7d1), - Felt::new(0xa0ae0e5765bc8716), + Felt::new(0x22bb678fbb6ea5f4), + Felt::new(0xf94dddeaf2e4ec8a), + Felt::new(0x54da9c720acfd54b), + Felt::new(0xada06e412b00a686), ]), Word::new([ - Felt::new(0xba3692cf34a6eb7a), - Felt::new(0x384dce8b1fd8fcd5), - Felt::new(0x248f1c83f6cf6055), - Felt::new(0xbf50ca14b3c5b022), + Felt::new(0xa1618e51012a9c5e), + Felt::new(0xa15f5b55de9259d9), + Felt::new(0x618e763dc2525b88), + Felt::new(0x8e835e132a5ac557), ]), Word::new([ - Felt::new(0x18611824fa468341), - Felt::new(0xaab4187ff224ec04), - Felt::new(0x4ad742d8a070d084), - Felt::new(0xfa3bb42df7d86480), + Felt::new(0xef5f289ff8fa900d), + Felt::new(0x9cc1221cebc531a9), + Felt::new(0x47609f2d3797ff59), + Felt::new(0x9ff11778bd6f382b), ]), Word::new([ - Felt::new(0x2ab25bf43fc462b5), - Felt::new(0x6ac0cc243f54b796), - Felt::new(0x2401eabf391a2199), - Felt::new(0x62a71dae211b983), + Felt::new(0x2b34581141c1e8c2), + Felt::new(0x2257c1ce6c15034b), + Felt::new(0xc43eb451d891272), + Felt::new(0x28c2d8cda0d777d7), ]), Word::new([ - Felt::new(0xbc5e568df9f18772), - Felt::new(0xee864850b75a99ba), - Felt::new(0x2a53e3e6776ae456), - Felt::new(0x8eb51bedbe483d7c), + Felt::new(0xe4afdafd0f672a82), + Felt::new(0x9341ac68032a3f58), + Felt::new(0xbe8d67ddf9cfc529), + Felt::new(0x27b3ccb1bac459f3), ]), Word::new([ - Felt::new(0xce8161f4c705bfbb), - Felt::new(0xf1071a4e343a37e9), - Felt::new(0xddc4878a9e5de00f), - Felt::new(0xee33d737cd3c5dc8), + Felt::new(0x9388197772683085), + Felt::new(0x3778f785db5104f2), + Felt::new(0x9ba77ce1b812d2b3), + Felt::new(0xd5d85d366fba18e), ]), Word::new([ - Felt::new(0x9eadd43aebfcd43d), - Felt::new(0xf35cec43429c0a95), - Felt::new(0xcad253fc16b63e5a), - Felt::new(0xea25dc9baaf21d38), + Felt::new(0xe614624c3de46b54), + Felt::new(0x98438853a5f8e423), + Felt::new(0x75fc1975efa50e2e), + Felt::new(0x7b3bc747fe7d9ef5), ]), Word::new([ - Felt::new(0xa85a87fbf220f449), - Felt::new(0x1db1c09109882161), - Felt::new(0xab5139cb30eb2c88), - Felt::new(0xe62f2ade31d95b14), + Felt::new(0x4389e290ab7be5de), + Felt::new(0xb0f8ae1bdfe7a503), + Felt::new(0x4a0bba5e50d04a4f), + Felt::new(0xf404eeb6b51c2105), ]), Word::new([ - Felt::new(0xad3fae6f7f635376), - Felt::new(0x21e5dba9b8e21ac8), - Felt::new(0x86506eeeba6c7151), - Felt::new(0x6bf71fdffc8d9ae7), + Felt::new(0xc35f756e305c9519), + Felt::new(0x4e686fe3acc002a3), + Felt::new(0xac394c30603412b8), + Felt::new(0x18be6e6b58ef489), ]), Word::new([ - Felt::new(0x37ec52a9396f4574), - Felt::new(0xf19404a514aa9285), - Felt::new(0x3ed5ae669769c4e7), - Felt::new(0x2286b493b85c9481), + Felt::new(0xf29ba52464444de2), + Felt::new(0x727389ba7a8880a), + Felt::new(0x177fb574915927fb), + Felt::new(0x8f73e16ebb2cc82a), ]), Word::new([ - Felt::new(0xc37fc37b83940bd2), - Felt::new(0xe3d67417540b620b), - Felt::new(0x1495f7a7848dde0a), - Felt::new(0xeaf4f9c053465ff), + Felt::new(0x997851ba4388feab), + Felt::new(0x8e2a5f7d9ac44c9d), + Felt::new(0x8a3cb20c8e7e8c74), + Felt::new(0x5ae3eee7da848df7), ]), Word::new([ - Felt::new(0x80131752569df8f0), - Felt::new(0x30720a862b82f732), - Felt::new(0xabed5fb95dbe678b), - Felt::new(0x6cf7da37075ad45e), + Felt::new(0x6d45c2cdad589122), + Felt::new(0xced0a31ff9c64e3e), + Felt::new(0x98b60b642b30fa4a), + Felt::new(0x90be60523065a966), ]), Word::new([ - Felt::new(0xa318ea66909473fe), - Felt::new(0x4a6c6ebc4bee8b3c), - Felt::new(0xf0d622f04ce1b02e), - Felt::new(0x92c2f8e192c000a1), + Felt::new(0xc5469bc4f00a8b98), + Felt::new(0xfea50c983bad6953), + Felt::new(0x77a1f18d385da1cf), + Felt::new(0x78258773d7497a4c), ]), Word::new([ - Felt::new(0xb39d728756dca017), - Felt::new(0x4f66acee5bcd7d98), - Felt::new(0xf623331bed29e125), - Felt::new(0xbcfc777f0eb03793), + Felt::new(0xbe1ba2a05855dba2), + Felt::new(0xd25b3fbd2aeff508), + Felt::new(0xa21fba1bed1ec4d), + Felt::new(0x28dd25121e82491b), ]), Word::new([ - Felt::new(0x6cdabd98e067b039), - Felt::new(0xd6356a27c3df3ddc), - Felt::new(0xd5afb88820db9d2f), - Felt::new(0x8203a7adfa667bfc), + Felt::new(0x26ebaf1a66aa491a), + Felt::new(0x11d70f7351ee6327), + Felt::new(0xf535e89e7a333f31), + Felt::new(0x81f0540d0aaf2fcd), ]), Word::new([ - Felt::new(0x1ddef8e482da50e0), - Felt::new(0x7fa3c9c0865609ec), - Felt::new(0x6ca762886d4d6227), - Felt::new(0x9a95160f2a4fe5d9), + Felt::new(0xeaa63a84b49f9af3), + Felt::new(0x2b560a1b9e2f030d), + Felt::new(0xa8058c8c80d5122f), + Felt::new(0xa7d5eb61056af830), ]), Word::new([ - Felt::new(0x607230c3b366dbd5), - Felt::new(0x5b996a7d876b7602), - Felt::new(0xf61df5d15469c8ea), - Felt::new(0x9bb4f5c06ac49403), + Felt::new(0x93f61b54d7c8b14f), + Felt::new(0xa05e12137fbee56f), + Felt::new(0xed0ca87839d2afbc), + Felt::new(0xee1d0c89603096c9), ]), Word::new([ - Felt::new(0x6a27c9e7082595e7), - Felt::new(0xbf93eb89e2090438), - Felt::new(0xd2db18139bedc636), - Felt::new(0x79710c33a1f1f612), + Felt::new(0xd32f7fd432f7c926), + Felt::new(0x9a10ef3aedd9c5cc), + Felt::new(0x987ff803b8ad5608), + Felt::new(0xfe0474bb58ed017d), ]), Word::new([ - Felt::new(0xf54e4461aa09608b), - Felt::new(0x898a7b52804d88c9), - Felt::new(0xbc548fab0257ea25), - Felt::new(0xe783017a62b49474), + Felt::new(0x730b984ab280d1eb), + Felt::new(0x59a4121bb947dd5d), + Felt::new(0xcf3a0ce3778acbad), + Felt::new(0x5db9a2dcb6f5168c), ]), Word::new([ - Felt::new(0xf7efdb376a7734c9), - Felt::new(0x2d4ded56d9ef2076), - Felt::new(0xa17d90a509b879d0), - Felt::new(0xcf012a20045b29e1), + Felt::new(0x1017554397bc5cf1), + Felt::new(0xe4b0c8118343e3fe), + Felt::new(0x874d4c65e29e473b), + Felt::new(0x3fe19c4c0f73bf91), ]), Word::new([ - Felt::new(0x37e40a30232a4f06), - Felt::new(0xfbd9877fb761052e), - Felt::new(0xc4c41f56a70377cd), - Felt::new(0x631e942f6680d4cc), + Felt::new(0x1cd2791b4ac8bb3b), + Felt::new(0x82772647faaa67d8), + Felt::new(0x7cd3cf3dbd601244), + Felt::new(0xa8931437bd6e90a6), ]), Word::new([ - Felt::new(0xcf868b6d54b515a5), - Felt::new(0xa522edf7c43f7aee), - Felt::new(0x66057652f34d479), - Felt::new(0x59f4a86223bc80bd), + Felt::new(0x74fbc0ab37c21d7b), + Felt::new(0x1f8e2d4c14e1ca17), + Felt::new(0x8dadb87ff6a1f8ed), + Felt::new(0xb706bbc88126c74e), ]), Word::new([ - Felt::new(0xb7214ce5a0ba8dfd), - Felt::new(0x5c7a6e583e4e255e), - Felt::new(0xabc8369f8bf38a1c), - Felt::new(0xb5db79ae07f0689c), + Felt::new(0x31b4709960a4d7b4), + Felt::new(0x508dc734dfe651f6), + Felt::new(0xafb61654eb7900f7), + Felt::new(0xc881d1a87e04d076), ]), Word::new([ - Felt::new(0x18c980169ef2d0bb), - Felt::new(0x6526b64df8eb4eac), - Felt::new(0xfe4d8327ca5bd91a), - Felt::new(0xe36d607069c7dd85), + Felt::new(0xf671c8c96ffdf309), + Felt::new(0x4005499dff3633b1), + Felt::new(0x1d46b157205ff55c), + Felt::new(0xcb2e52106d23325d), ]), Word::new([ - Felt::new(0x602a97209948e5cc), - Felt::new(0xb7d19db914da726), - Felt::new(0xe4e43672c24d376c), - Felt::new(0x8bb9f7465e019213), + Felt::new(0xa0f62c3db76f6d96), + Felt::new(0x5e7853c7126abf61), + Felt::new(0x829e4767b3f3d677), + Felt::new(0x6997a407759f40ac), ]), Word::new([ - Felt::new(0x187bff077d393e3d), - Felt::new(0x17fb9a97c5055580), - Felt::new(0x618469c060eb2719), - Felt::new(0xfc7be4b58477e5ac), + Felt::new(0x6c13bfd2f92eb71d), + Felt::new(0x1297af3418d02e44), + Felt::new(0x50c8024a610d8ef4), + Felt::new(0x287f45266da74eda), ]), Word::new([ - Felt::new(0x1d40fcbc7a25cc97), - Felt::new(0xaee142f7cebadbd5), - Felt::new(0x22dbaed94300ddf8), - Felt::new(0xe069c36278753a06), + Felt::new(0x5a1764d52f31ba3d), + Felt::new(0xa9cf37c5cf23f5a6), + Felt::new(0x4e9c41b0d6167ec8), + Felt::new(0x512047298db42fa4), ]), Word::new([ - Felt::new(0xcd1e21c5f02ce44d), - Felt::new(0x3b0ddbaa04daff25), - Felt::new(0xbb55cd14f54818c7), - Felt::new(0xc57f1b84ed302102), + Felt::new(0x72d22ed9f10b4809), + Felt::new(0xe6072223f5d44bff), + Felt::new(0xfc35f03e39f9f907), + Felt::new(0xa6217147569d81e1), ]), Word::new([ - Felt::new(0x5c8e1f56cbdb0f87), - Felt::new(0xeeeb31b4d317cf1d), - Felt::new(0x8bf45cd3659a6d1), - Felt::new(0x9e179aa20693175a), + Felt::new(0xb05f52950422138d), + Felt::new(0xd05697d1fe3a2dcc), + Felt::new(0x77e6ba921c3d88b), + Felt::new(0xe2a049098b844637), ]), Word::new([ - Felt::new(0x10f58975fbb0fca), - Felt::new(0x5f35c19eb0f615c1), - Felt::new(0x9870cdafe46a3d), - Felt::new(0xcec9d9f3925df88b), + Felt::new(0x47c051e54f0aac4f), + Felt::new(0xa2c9c34701a64715), + Felt::new(0x53aa9094fc804523), + Felt::new(0x4ad7c5a854c20094), ]), Word::new([ - Felt::new(0x89e90b2f029b50c0), - Felt::new(0xd78a4223d0036c8a), - Felt::new(0x996b326a1d5cd76d), - Felt::new(0x5b314d29bb1694e3), + Felt::new(0xae5aae06914bebe0), + Felt::new(0xba723bcdede0b2f), + Felt::new(0x57c796a840fde9ac), + Felt::new(0x3e630cb89060c007), ]), Word::new([ - Felt::new(0x1be6e6955ba0f3a8), - Felt::new(0xc7e07c49076315ef), - Felt::new(0x93e91de5c7849fb2), - Felt::new(0xe81bc86fc641596f), + Felt::new(0x20c5789c29a5098b), + Felt::new(0xcd1dbf19d9030f54), + Felt::new(0x94cf701e167bdfbf), + Felt::new(0x13a58075092e3c2a), ]), Word::new([ - Felt::new(0x5320464735f18522), - Felt::new(0x1a741214432ca63d), - Felt::new(0xaf3ed59d324bdbe8), - Felt::new(0x2493eb414c91ac94), + Felt::new(0x1f24ae51589d6ef5), + Felt::new(0x3f603db464d888ac), + Felt::new(0x7fb3e77a51402568), + Felt::new(0xe1681cb7d5007e14), ]), Word::new([ - Felt::new(0x35897b61f231fa86), - Felt::new(0xb1531e954332f229), - Felt::new(0x92e950b1c1f874a), - Felt::new(0x469de0412ca52491), + Felt::new(0xe99e3c5ab6ad7091), + Felt::new(0x45c5aea5b498809a), + Felt::new(0xca221abad77fba94), + Felt::new(0x7c05f1dedf417422), ]), Word::new([ - Felt::new(0x1ecea76deca59ec5), - Felt::new(0xe884b570f5d54e45), - Felt::new(0x58939f3a1b5bc7e1), - Felt::new(0xf14eab10f926958f), + Felt::new(0x82a4f1fc61c2eb21), + Felt::new(0x6545946d94f26f7), + Felt::new(0xcac20af3361044e4), + Felt::new(0x5d1ec31b917476ed), ]), Word::new([ - Felt::new(0x26251aa927a69723), - Felt::new(0xb1808fe0795ab008), - Felt::new(0xd195fe923d1944c9), - Felt::new(0x2334a61c28dc63c), + Felt::new(0xf68ed2385443efd7), + Felt::new(0x8a693e27c0322995), + Felt::new(0x8441621b830d66a2), + Felt::new(0x301f6af5039281c8), ]), Word::new([ - Felt::new(0xe4b659081d9cf4e4), - Felt::new(0xf1174a5f72916819), - Felt::new(0x1de902b42b3b4054), - Felt::new(0xbe2bc215120367d0), + Felt::new(0x8d870b01e938568a), + Felt::new(0xd6b64b50844165b9), + Felt::new(0x95bb2039306f03db), + Felt::new(0x8e03338457833af8), ]), Word::new([ - Felt::new(0xfc87b8043d32428f), - Felt::new(0x8f8cb244e3ddf6da), - Felt::new(0xc7539186ece143a7), - Felt::new(0xf28008f902075229), + Felt::new(0x8cc1aa0c601d1bb), + Felt::new(0xef0e3a67248752ec), + Felt::new(0x6f3fe9f5023ea283), + Felt::new(0xc8d9d24d2b8e2f95), ]), Word::new([ - Felt::new(0xf76c24c9f86c44d3), - Felt::new(0x97c7abcbb6d07d35), - Felt::new(0x9d8e37a1697a0d4), - Felt::new(0xa3f818e48770f5fa), + Felt::new(0x8e054ccf1f409dd), + Felt::new(0x2c464d96520ff0f9), + Felt::new(0x94ffad08bdf83708), + Felt::new(0x1edf4b6cdbdd60c9), ]), Word::new([ - Felt::new(0x885686c79c1cd95e), - Felt::new(0xcdebe76fd203c23e), - Felt::new(0xdf9b7cd5099673ed), - Felt::new(0xe60438536ad13270), + Felt::new(0xb0b696a89d49914), + Felt::new(0x6e776735f98bb4e), + Felt::new(0x5c21944f44b21087), + Felt::new(0x849734992138dc1f), ]), Word::new([ - Felt::new(0x7790809942b9389d), - Felt::new(0xa3d82432c31de99), - Felt::new(0xaea11fece88c7d27), - Felt::new(0x5cc764da96d0b2f0), + Felt::new(0x4bdde21742bfefa3), + Felt::new(0xdafc80a1f0b26a33), + Felt::new(0x72cd9166fb2e8798), + Felt::new(0x4fd0c44ab7647ae2), ]), Word::new([ - Felt::new(0x80e555c41170427f), - Felt::new(0x87e68144276d79c8), - Felt::new(0xebdc63f28aa58a53), - Felt::new(0x168dd22672627819), + Felt::new(0x8fe670a9b7a4cdb), + Felt::new(0x66744865e1c00d3), + Felt::new(0x76fff27436cae08d), + Felt::new(0x565df8deedd57ccd), ]), Word::new([ - Felt::new(0xea1dc59c29da5b6c), - Felt::new(0xa33188c0a077761), - Felt::new(0xabd3c84cddbe1477), - Felt::new(0xd28244bc92f36e0f), + Felt::new(0xc3c67b9b5552028b), + Felt::new(0xac755b3eac5b39cb), + Felt::new(0x2c81b9567fb06b87), + Felt::new(0x4cf63467c28df5aa), ]), Word::new([ - Felt::new(0xdadc2beb7ccfe3fa), - Felt::new(0x218532461f981fb4), - Felt::new(0xf0455f1d4e2f9732), - Felt::new(0xa7338b43d2b7e62d), + Felt::new(0xc6103f36100f0110), + Felt::new(0x7eb64907c28b9fa6), + Felt::new(0x27d7d1b5ebc4aeda), + Felt::new(0x1da94af08a75a5e), ]), Word::new([ - Felt::new(0x195d8bc1cfe2711a), - Felt::new(0x44e392ba7e259f47), - Felt::new(0x480120d41e18ab3c), - Felt::new(0x2056ffb29c2d89d1), + Felt::new(0x2f0c864709131f3b), + Felt::new(0x6bfbc8a702cf804f), + Felt::new(0x3f99016c9064e748), + Felt::new(0xf0c3bebc441e06c4), ]), Word::new([ - Felt::new(0x382e33ba5fe6ada3), - Felt::new(0x45402a8903efebc9), - Felt::new(0xb9b0d63a59c70da), - Felt::new(0x7afebd4726d8cfe5), + Felt::new(0x5a7cd4e8ae2216d1), + Felt::new(0x927f67dcae500db6), + Felt::new(0x988de42eab734530), + Felt::new(0xb351a01e3defb152), ]), Word::new([ - Felt::new(0xbf60bf6b45a4c9d), - Felt::new(0xfb5b9b553646f19c), - Felt::new(0x9949b60ce7639da3), - Felt::new(0x9c62552c0d1868ff), + Felt::new(0x7742e4639683760a), + Felt::new(0x2e5e877b874c7152), + Felt::new(0x5ab41de05a496568), + Felt::new(0xbb4a1da6a81258dc), ]), Word::new([ - Felt::new(0xdb2a0aba0fc5e4f8), - Felt::new(0x8ee4f01d4b0fa49e), - Felt::new(0xd70a17a77b5c4a03), - Felt::new(0x57aaaa5b48fea66e), + Felt::new(0xcba648f645196338), + Felt::new(0xbea16b163870415b), + Felt::new(0x18850f365ea62ce), + Felt::new(0xdb9a08a8f49dde6a), ]), Word::new([ - Felt::new(0x6d635940443564cb), - Felt::new(0xc7fbf0e26b5e3ff6), - Felt::new(0xa45bce664368b65e), - Felt::new(0xd6c5c1a92be0c60d), + Felt::new(0x2ad1b5164eb56b6e), + Felt::new(0xdc58fbf36c6b4fcf), + Felt::new(0xbbb986b3687e607a), + Felt::new(0x134d10fa63d56583), ]), Word::new([ - Felt::new(0x6ea62d6033fb2dd3), - Felt::new(0x1a37910cf90ec6d8), - Felt::new(0x83d826e9933760b5), - Felt::new(0xf8387c90d9c6b5a9), + Felt::new(0xad20e15f0a3ee8b3), + Felt::new(0xab19103387b6db49), + Felt::new(0x4c2ac536c55369c3), + Felt::new(0xe90d3067d5032686), ]), Word::new([ - Felt::new(0x134766f1da2fbc91), - Felt::new(0xcfaeea545df2c757), - Felt::new(0xd0accefaed1eaa0f), - Felt::new(0xec38d4053f84b163), + Felt::new(0xacf847f2722006e1), + Felt::new(0xcccd5bee8edaab77), + Felt::new(0xaf647369cc0bd07d), + Felt::new(0xac1bfe67d11eb1bf), ]), Word::new([ - Felt::new(0xb02ad1e757380aee), - Felt::new(0x4538b8ea13112d4), - Felt::new(0xb2d761fe842a2a85), - Felt::new(0x8e98d58adf5a1f29), + Felt::new(0xe61111070c4fa0cd), + Felt::new(0x678079994529da0b), + Felt::new(0x97f5072ac759665e), + Felt::new(0xd536a68f9a4f607e), ]), Word::new([ - Felt::new(0x44603d9549ddee64), - Felt::new(0x43de72d570967bbb), - Felt::new(0x4a3e71144e62d0fa), - Felt::new(0xffb2fdcb48965939), + Felt::new(0x46b7a14b30fbc9f), + Felt::new(0x90857d5c4265abe1), + Felt::new(0x52b1a794404385c7), + Felt::new(0xc5dcb92c2df08437), ]), Word::new([ - Felt::new(0x606f3ee12fe9ec0c), - Felt::new(0xe7d494ab8e483d87), - Felt::new(0x3b47f7c0d316cd4a), - Felt::new(0x86f941c7fa834581), + Felt::new(0xc79c6daf729eeb36), + Felt::new(0x3c8983a8d9974006), + Felt::new(0x92eff347ec15f71a), + Felt::new(0x17e86b546b852eb4), ]), Word::new([ - Felt::new(0x30c2385facf08b86), - Felt::new(0x4446168e25ac2c21), - Felt::new(0x61c6db1c3f283b21), - Felt::new(0x2fdf6bc360bf803), + Felt::new(0x9ffd0848241eefe3), + Felt::new(0x605c79e50ebb085c), + Felt::new(0xb2bd2ab9f3697b7c), + Felt::new(0xe041389cb7150d9b), ]), Word::new([ - Felt::new(0xeec8d9cc3e46d243), - Felt::new(0x65bcae511dcce39), - Felt::new(0xd3da5bbfdbd09cd3), - Felt::new(0xe7c35fc3d11216a5), + Felt::new(0xe6104dca1a2feec5), + Felt::new(0x5a79f163170b01e9), + Felt::new(0xe84905e5a557ae64), + Felt::new(0xa9e2e8e3aae6976c), ]), Word::new([ - Felt::new(0x841fb6fb35e7b49b), - Felt::new(0xfc4e2e1239caa7b8), - Felt::new(0x37cb93ec88f102e5), - Felt::new(0xa707a1556032152c), + Felt::new(0xc1bd5e298b8eadf1), + Felt::new(0xf42b7225d038f3c1), + Felt::new(0x75503169d1d0f385), + Felt::new(0x3e3c6a4b486af2c), ]), Word::new([ - Felt::new(0x37c67bd7b7cef984), - Felt::new(0x75bbe46da2ee5c90), - Felt::new(0x3a5c568d1f71cab1), - Felt::new(0x36939cdca2dc0b55), + Felt::new(0xe1f61460f57d2438), + Felt::new(0xc36dd5e7c345a99f), + Felt::new(0x873fb3656add0e10), + Felt::new(0x6504cbdd0e44b39c), ]), Word::new([ - Felt::new(0x4f76756a55f3a644), - Felt::new(0xd30f8fa45394aff4), - Felt::new(0x65c55096158b202f), - Felt::new(0x368a5fb0b0d475d0), + Felt::new(0x3fc8825536595d1c), + Felt::new(0xe3a989ac24c442ff), + Felt::new(0x3f00298a381f556a), + Felt::new(0xf0e79665f5120cfc), ]), Word::new([ - Felt::new(0xa9b9acd256cabb0f), - Felt::new(0xd8b1170f301208c7), - Felt::new(0xab152f908d46bf8), - Felt::new(0x1b7a10556730ec16), + Felt::new(0xc52ffaee0c323307), + Felt::new(0x24d4b284d557b6db), + Felt::new(0xc6cc213c02c167c8), + Felt::new(0x8db5af59bd93c51f), ]), Word::new([ - Felt::new(0xd967a72076e3059c), - Felt::new(0xbd1015a08ffe8881), - Felt::new(0xf72f186dde0c6e78), - Felt::new(0xa58910205352895a), + Felt::new(0x784b5f5e63326944), + Felt::new(0xb8b422abe90f40b7), + Felt::new(0x774646e33258bc8d), + Felt::new(0xc84da11887cb5742), ]), Word::new([ - Felt::new(0x130333f2fd400a4d), - Felt::new(0xf20104837a118d6e), - Felt::new(0xda1e5d608fb9062c), - Felt::new(0xb8ac5c76d60950b8), + Felt::new(0x652dc7cb8a1f6c09), + Felt::new(0xee834858c182190f), + Felt::new(0x8b69716414504dca), + Felt::new(0x651eea2ddd727479), ]), Word::new([ - Felt::new(0x65d0deae6fb0c6cb), - Felt::new(0x1b442ae344dcd9e7), - Felt::new(0x1eedabab8fc07fa4), - Felt::new(0xb0dc89b96f256189), + Felt::new(0xc7eacb774a32bb70), + Felt::new(0xfd4ed5472cacbf3), + Felt::new(0x478a07c571d2447d), + Felt::new(0x8eb18bc9d29dec70), ]), Word::new([ - Felt::new(0xef88de626968c17a), - Felt::new(0x569a01072cdbbc2b), - Felt::new(0xc99bbba6d083c68f), - Felt::new(0x9ed4a176fe341849), + Felt::new(0x663c4616d424ebf4), + Felt::new(0xeefdee99f237d5ab), + Felt::new(0xe0b6d308cd592024), + Felt::new(0x4208b7351fa4302a), ]), Word::new([ - Felt::new(0x5d49d1e9d17448a6), - Felt::new(0x6974d510bc47ee66), - Felt::new(0xbcbea4dec0b68586), - Felt::new(0xdaa5457e0cfd3e61), + Felt::new(0xd481d45f71b56b77), + Felt::new(0xb10316f16afe4550), + Felt::new(0xc55ef6e4bb501483), + Felt::new(0x9494dbc8ff511634), ]), Word::new([ - Felt::new(0x9fceba739503cda0), - Felt::new(0xb9daf271ac42c8ba), - Felt::new(0x10fe3e8de8680d83), - Felt::new(0xd7e1dc73ced7730b), + Felt::new(0xce6ab27c75267570), + Felt::new(0xa841b38a81153b84), + Felt::new(0x2a982e69815eb95), + Felt::new(0x3a781dbedba07fff), ]), Word::new([ - Felt::new(0x93ec6c422d4271ea), - Felt::new(0x73923813232b0e70), - Felt::new(0xbbe6a4441a900b65), - Felt::new(0x36b2164f37c9319b), + Felt::new(0xb9986f52024a4070), + Felt::new(0xd7b04dacfb578a4c), + Felt::new(0x84335c4107ff51b4), + Felt::new(0x57d34e97a7f5bc14), ]), Word::new([ - Felt::new(0xce3ecb2eed624694), - Felt::new(0xb7e1d75fff7a454c), - Felt::new(0x86c24aa3a8d92d2b), - Felt::new(0xb1ba74cafa9ce649), + Felt::new(0xcaf39a0799c252), + Felt::new(0xc59e04527c15e01d), + Felt::new(0xc3ee742f12d01965), + Felt::new(0x6c363303a5379a6), ]), Word::new([ - Felt::new(0xb5fae724eb357479), - Felt::new(0x359532ddc4840cb9), - Felt::new(0x4b111251e037e9fa), - Felt::new(0xfcdab1cdd314c1d9), + Felt::new(0x85a9cc8568e7530e), + Felt::new(0x2cec56ea82497437), + Felt::new(0x405667275bb5fe7e), + Felt::new(0x7ec253c378286890), ]), Word::new([ - Felt::new(0xb3a89464d21c9ff1), - Felt::new(0x8136e1b457a59ca8), - Felt::new(0x88b0fa606b53c4d5), - Felt::new(0x89645f8a9dfe97a2), + Felt::new(0xd718ae4b8686d41e), + Felt::new(0xadc41b281bdd380f), + Felt::new(0x568fa3dcb8be911a), + Felt::new(0xfcce156b4bbf07ca), ]), Word::new([ - Felt::new(0xfe115ef35b814cbf), - Felt::new(0x63de467fb93b6851), - Felt::new(0x17c73b03c9f44ad8), - Felt::new(0x53742721f568b5be), + Felt::new(0x284a977ee6ab740), + Felt::new(0x52da1ee52275d41b), + Felt::new(0x44c940a446c5038d), + Felt::new(0x74332541d3a3eb4a), ]), Word::new([ - Felt::new(0xd8110ea6e905cc2), - Felt::new(0xd67b3c7cea25100), - Felt::new(0x9e49b38ed51d1c60), - Felt::new(0xe9e24f9b597c9bfd), + Felt::new(0xe14f55528c248fcb), + Felt::new(0x3b14809519bb04f5), + Felt::new(0xa2ebb8accb489605), + Felt::new(0xa2f4a2890f3f7743), ]), Word::new([ - Felt::new(0xefe9086b5bb5a504), - Felt::new(0x991f92a90c9346a3), - Felt::new(0xe4fab215a20f453b), - Felt::new(0x4e4d4dde9146d61a), + Felt::new(0x9b2bc7e94b75374e), + Felt::new(0xc2879b31f4035c4f), + Felt::new(0xbd600bdc34732d3c), + Felt::new(0x6dfa6d51fef2721c), ]), Word::new([ - Felt::new(0xaa998c3b26497ffa), - Felt::new(0x985bd5cf4ccefb3c), - Felt::new(0xce44e80aa02424bb), - Felt::new(0x75158a37503aed75), + Felt::new(0x902331d94df3505a), + Felt::new(0xfda6cde5628308f9), + Felt::new(0x34b439c067c7b4d3), + Felt::new(0x2b7878660dc8efd9), ]), Word::new([ - Felt::new(0xdb61760c917116f1), - Felt::new(0xf378c9645174a832), - Felt::new(0x1216aa71b73e7fac), - Felt::new(0x8a4e7f0591a129fd), + Felt::new(0xdd66f8d7698e4eb8), + Felt::new(0x1d03969c9d146092), + Felt::new(0xf8eac97f83c08043), + Felt::new(0x9b59e29ef763e459), ]), Word::new([ - Felt::new(0xaf11a04daaf4ed67), - Felt::new(0xd3e59f0d7dad9064), - Felt::new(0x30c206089a2c294d), - Felt::new(0xe104db59761e8a99), + Felt::new(0x6c309112a472dcc8), + Felt::new(0xe089a50410c3b074), + Felt::new(0xff23afa304ff16f), + Felt::new(0xbe1bd4aa7a393695), ]), Word::new([ - Felt::new(0x70b545ba7a6d447), - Felt::new(0x6ac0e423ddf68913), - Felt::new(0xf9b50997257bb033), - Felt::new(0xdac37c7b1c18b48a), + Felt::new(0x303f9384cec5e0c9), + Felt::new(0xfbee0ab1ff8dadcb), + Felt::new(0xcaf6a4571a59e33c), + Felt::new(0x85516327705ec9a2), ]), Word::new([ - Felt::new(0xd182b9dff0fcd5c0), - Felt::new(0xf87619ae86b6eb02), - Felt::new(0x6838c1b612b17cb5), - Felt::new(0x9b705d5b6bcf92c), + Felt::new(0xb3f872265da8c2c), + Felt::new(0xb47964162479bebc), + Felt::new(0x86e8e5b1b092fa71), + Felt::new(0x32266efeb618442c), ]), Word::new([ - Felt::new(0xfba622b3026c6193), - Felt::new(0xdacde486f8129b96), - Felt::new(0xd5acd22a7c2cf6aa), - Felt::new(0xf5beb40535e6c0f2), + Felt::new(0xa0ee5b88ffa1bf17), + Felt::new(0x2c7981644c2c8f06), + Felt::new(0xd9b896e51031b30d), + Felt::new(0x3c369de24985490d), ]), Word::new([ - Felt::new(0x59bde17b2d501969), - Felt::new(0xb4abe1389123d3b9), - Felt::new(0x683d8dd8635d9a67), - Felt::new(0x347e01da4c07833), + Felt::new(0x151881390e803f09), + Felt::new(0x76629fefc37ec9a3), + Felt::new(0xdc30ef977a01cffc), + Felt::new(0x8a1b767747d677de), ]), Word::new([ - Felt::new(0x4e28956ab7162a06), - Felt::new(0xccfcc7358f48c727), - Felt::new(0x7b3485f20c979144), - Felt::new(0xeeb27fa694f1c8fd), + Felt::new(0x98f81ad1dc5d3f53), + Felt::new(0x6bf3729004afe0d3), + Felt::new(0xbf194c54a20e0299), + Felt::new(0x1431836bedeb6093), ]), Word::new([ - Felt::new(0x275b2c0ee883807b), - Felt::new(0x8f68f2016c1391cd), - Felt::new(0xb59fdccb20322765), - Felt::new(0xeb9b902c5351d5d4), + Felt::new(0xd9a4f944c886c196), + Felt::new(0x43bc25f7df3201b6), + Felt::new(0xdb2d5dad33db4893), + Felt::new(0xe476f132869558fe), ]), Word::new([ - Felt::new(0xb767d8cb8816cc8e), - Felt::new(0xbd29bb02cdcbc9af), - Felt::new(0xeb1dca9bfebee6f), - Felt::new(0x57597da8109c0354), + Felt::new(0xdd8d86a42c418610), + Felt::new(0xb9a2476435d3a058), + Felt::new(0x1cae777275604da6), + Felt::new(0x21e886725e0c18f9), ]), Word::new([ - Felt::new(0xeb32a8db8cf216), - Felt::new(0xeb5532ac68f304c1), - Felt::new(0x9bca72ffccb957ee), - Felt::new(0x33d4b152ebedb841), + Felt::new(0xa46f20d055f7216f), + Felt::new(0x4900ac3f4370a63), + Felt::new(0x99ca62f79ef5f8b9), + Felt::new(0xc2de59ce20691978), ]), Word::new([ - Felt::new(0x439b20dce9810169), - Felt::new(0x2b693e2530a1b88c), - Felt::new(0x36b8898f4e900c7a), - Felt::new(0x7bf5064dde3a0da1), + Felt::new(0x7a48631a0bfff3eb), + Felt::new(0x3b94c6e453004591), + Felt::new(0x5fab1652ae822b3), + Felt::new(0x400b37cc56fa1d22), ]), Word::new([ - Felt::new(0x8794201ce6158fe0), - Felt::new(0xfcc53644557471f3), - Felt::new(0xa66d87f6ae6f64d0), - Felt::new(0x4e876d9d933b2ad0), + Felt::new(0x40d0b3847cc462ec), + Felt::new(0x2c5e05c7e87962e3), + Felt::new(0x840520a1bc365f88), + Felt::new(0x2ab54e2273ad9f81), ]), Word::new([ - Felt::new(0x6ff8f4900e43bab6), - Felt::new(0x40014f298cb7b9a3), - Felt::new(0x9d6b252ff946ee3d), - Felt::new(0xb014d99ab8508072), + Felt::new(0xcdf5f717928c8a89), + Felt::new(0xb612eb0a97227ef8), + Felt::new(0xc71f4387804459fd), + Felt::new(0x8bdc0619d4e1659e), ]), Word::new([ - Felt::new(0x9cdd5a4a37511cae), - Felt::new(0x684444122d770c18), - Felt::new(0x8982944b22a22577), - Felt::new(0x50a58d944629de54), + Felt::new(0x52fde7c40e364968), + Felt::new(0xed8b8c233f32a499), + Felt::new(0xdb69dc89c4a1cf9e), + Felt::new(0x8a602b6c7125b3ce), ]), Word::new([ - Felt::new(0x853f5b8ad557fac3), - Felt::new(0xdab1743c03b8da56), - Felt::new(0xc70d6683d4f4c086), - Felt::new(0x2f1d0f67a5dfae4c), + Felt::new(0xde95dfa36608bc73), + Felt::new(0x36893df69ef5c7b6), + Felt::new(0x296e79075c4a7d0f), + Felt::new(0xad9dc949d6b30d78), ]), Word::new([ - Felt::new(0xf3b6fe76eb11284), - Felt::new(0xbeb9e98b146c63a8), - Felt::new(0xc7e8824fce7777ad), - Felt::new(0x5229918b04410d6a), + Felt::new(0xfdd13861d5320017), + Felt::new(0x175043526474a728), + Felt::new(0x9ec8cdd1448b992d), + Felt::new(0x7cf5a38e86cb4f6c), ]), Word::new([ - Felt::new(0xc170c46601ffc4f3), - Felt::new(0x1258e8e47103c39b), - Felt::new(0x612e99da984aac99), - Felt::new(0xc82fcfcf56d6dd94), + Felt::new(0xfda651ec0660e8cd), + Felt::new(0xf04e4f664a56f41a), + Felt::new(0x292a9003e6767a0a), + Felt::new(0xe50ccfe917bd456), ]), Word::new([ - Felt::new(0xf793819d04d5679d), - Felt::new(0xb738b97ec0a52dd3), - Felt::new(0x4df897389119a098), - Felt::new(0xa5af45eb0d007785), + Felt::new(0xc688eadb9f01e59c), + Felt::new(0x47b9cd7023903603), + Felt::new(0x318bfae1e06fe3da), + Felt::new(0xc83189d37992f87f), ]), Word::new([ - Felt::new(0xfcf59c6c9d7280e7), - Felt::new(0x662b993b320f3345), - Felt::new(0xeb8e04ba28f156fa), - Felt::new(0xe72233ee6a444749), + Felt::new(0xe4fb5c90773f9ea1), + Felt::new(0x13b1582bad1f031b), + Felt::new(0x9ee222546a5b1217), + Felt::new(0xcaf5cdadfaf7d9b4), ]), Word::new([ - Felt::new(0x7ad6b7badfc9e757), - Felt::new(0x3332f340184af6f5), - Felt::new(0xe92a736dcdf52022), - Felt::new(0xf1759f8041119245), + Felt::new(0xaf90f25be574025e), + Felt::new(0xa6a79bae7183edb2), + Felt::new(0xfd3f30f11cb7d962), + Felt::new(0xa80fb4b3cb60debb), ]), Word::new([ - Felt::new(0x166382d3c8ca3a95), - Felt::new(0x36c8c25f971d771a), - Felt::new(0xe82cc977ee1402cc), - Felt::new(0xe13f6dc2ab919177), + Felt::new(0x4da76d203efa5600), + Felt::new(0xa8690c93cf9d295a), + Felt::new(0x2f24955130442d09), + Felt::new(0x4ee238c83e47b9aa), ]), Word::new([ - Felt::new(0x41fb8b9d5a9ab5e8), - Felt::new(0xb2a608f5d6fbc37d), - Felt::new(0xe81719d9778c54b4), - Felt::new(0xcc0d4373ef2bb8e1), + Felt::new(0x9d867192ce6c7de8), + Felt::new(0xa5e41d4bc73618e8), + Felt::new(0xb5c83dd5d51d84b0), + Felt::new(0xb18e472768a75d96), ]), Word::new([ - Felt::new(0x60b3788c45c1bc29), - Felt::new(0xcb38969a428c5423), - Felt::new(0xca11f6b9d957f57c), - Felt::new(0x7a166881648514cb), + Felt::new(0x59d6601a9b015b25), + Felt::new(0xc843c1aa885ae236), + Felt::new(0x9a158183e08b5dc6), + Felt::new(0x1145844b22b49390), ]), Word::new([ - Felt::new(0x3548d7dcbe4af37f), - Felt::new(0xb199194f25a6f55a), - Felt::new(0x751017bda8f0d816), - Felt::new(0x80117260c6525c4a), + Felt::new(0x6524ab5a3897e1fb), + Felt::new(0x4734b8ef3529204f), + Felt::new(0x13d39127b429dfab), + Felt::new(0x20b6b93f96712a75), ]), Word::new([ - Felt::new(0x1d1d34d842e95671), - Felt::new(0x38ab2833c4a2bded), - Felt::new(0x3a53caf5f6e20f5f), - Felt::new(0xfee946c5ebce837d), + Felt::new(0x331d2c37945eb6a3), + Felt::new(0xe22e4d90443e4cd3), + Felt::new(0xa6f7c6e30b0aa924), + Felt::new(0x60291d1d37a29298), ]), Word::new([ - Felt::new(0x65dbb5d849e46f92), - Felt::new(0x753679d0d09d1250), - Felt::new(0xc539adf9062e7698), - Felt::new(0xd76f4b13d2635af3), + Felt::new(0x38a14a1754501dbd), + Felt::new(0x4167f7d92b1c2ce2), + Felt::new(0x13ab7681212a624), + Felt::new(0xbaaf8e1ac64cd97d), ]), Word::new([ - Felt::new(0x9eae446581f7076d), - Felt::new(0x237138e3a1c55ee8), - Felt::new(0xfb54da0b58969484), - Felt::new(0x9a899375c4f10483), + Felt::new(0x5fbbcd61ca726726), + Felt::new(0xd2582d23ba908b74), + Felt::new(0x330e3dbb3e9eb2d5), + Felt::new(0x951652929797868e), ]), Word::new([ - Felt::new(0x88b12578d3c36cc4), - Felt::new(0x9cd868d6a29df146), - Felt::new(0x5443d21524637821), - Felt::new(0xceb3692fad9f76b9), + Felt::new(0xec01babca08bd895), + Felt::new(0xba9a7626ddd6668f), + Felt::new(0x9179b39124aedfc4), + Felt::new(0x5affa3c62c4b3a65), ]), Word::new([ - Felt::new(0x4e45d3e34ebfcb0d), - Felt::new(0xedca6a0f8d90eb2e), - Felt::new(0x1a182dad4a914731), - Felt::new(0xd89946f41f53f106), + Felt::new(0x4f8bea84ae68c86b), + Felt::new(0xcef547bf6c813156), + Felt::new(0x57463124b1dc489), + Felt::new(0xb4ea4cc2377e6fe1), ]), Word::new([ - Felt::new(0x42a89e1abee8b12d), - Felt::new(0x3af360678475225a), - Felt::new(0x3e28a1cdede0c7a3), - Felt::new(0x25cc76ad1c8788e), + Felt::new(0x1e5cc70d1dc1841e), + Felt::new(0x321ad7eab2f70418), + Felt::new(0x806dd438381a20e7), + Felt::new(0xa25b9f1a88890e06), ]), Word::new([ - Felt::new(0xa6e955e3b0721737), - Felt::new(0xbbd721b51cbc605f), - Felt::new(0xfa537854669f20a5), - Felt::new(0x2a1ab83bb24d43f7), + Felt::new(0x17dd1a39290a6672), + Felt::new(0xa76b6687282df90a), + Felt::new(0x5549085194b9310), + Felt::new(0x47468128e17ff977), ]), Word::new([ - Felt::new(0x1c289dc62c4e7907), - Felt::new(0x186437cf1f8b3abf), - Felt::new(0x929244addee54abf), - Felt::new(0x42ec87a9141b58fb), + Felt::new(0x8951be2c71b6aa25), + Felt::new(0xc8849cb2f3d17d55), + Felt::new(0x45006c090e491d86), + Felt::new(0x6437fa6577394ca6), ]), Word::new([ - Felt::new(0xf5ea743b81e59fee), - Felt::new(0x32e2c826d0fc57d4), - Felt::new(0xbc411bca1b745388), - Felt::new(0xc9af8de5bc3b8692), + Felt::new(0xa89d3f1ac7597df9), + Felt::new(0xc14f31ba91c1bd97), + Felt::new(0x487e19d8fdfb574), + Felt::new(0x6af87b44b56ed3f6), ]), Word::new([ - Felt::new(0xf75129028d95ebab), - Felt::new(0xf9d02d7713784ff2), - Felt::new(0x944b5fea457c946b), - Felt::new(0xa6645d6f5389a91f), + Felt::new(0xa5eaadab93f885fd), + Felt::new(0xc42867bf591206f4), + Felt::new(0xdfc5efa38a456769), + Felt::new(0x9f6ce48420cdf929), ]), Word::new([ - Felt::new(0x236ce9e32fb0131c), - Felt::new(0x92cac3a2b67600b8), - Felt::new(0x437914d1d9e409bb), - Felt::new(0x6bff102a4ea81e38), + Felt::new(0x1f52d69390fd9980), + Felt::new(0xa06a4c951d9e332), + Felt::new(0xddefc965eb12ccc7), + Felt::new(0x544db122c8ec2200), ]), Word::new([ - Felt::new(0x9906825ec6d45f25), - Felt::new(0x1245ccf4121950a2), - Felt::new(0xff82337f357949bb), - Felt::new(0xe1f59f6b824f97af), + Felt::new(0x9d331e003c39481f), + Felt::new(0xc29377c5b7262063), + Felt::new(0xd10c96634885ae64), + Felt::new(0x53b174d630f0a988), ]), Word::new([ - Felt::new(0xd6030ea8046f0d1e), - Felt::new(0x69fd3c732586f5db), - Felt::new(0x765cccb10c151f30), - Felt::new(0xd1bad2f1ba1bdc5b), + Felt::new(0x7e36a51c053b79c3), + Felt::new(0x1539bac1275df6dc), + Felt::new(0x35d946afc238a3f2), + Felt::new(0x5a32f67c1500f6aa), ]), Word::new([ - Felt::new(0xd4a0cff6578c123e), - Felt::new(0xf11a17948930b14a), - Felt::new(0xd128dd2a4213b53c), - Felt::new(0x2df8fe54f23f6b91), + Felt::new(0xb80fd935de9c2911), + Felt::new(0x15997eaf79765d40), + Felt::new(0xa600dcb22c577529), + Felt::new(0xe15004068a2d6a93), ]), Word::new([ - Felt::new(0xa891ab94552907b2), - Felt::new(0xc64b69708f99b16e), - Felt::new(0xb99d29be9af9df15), - Felt::new(0xa452647d61bd3d7b), + Felt::new(0x97d7528bd226911f), + Felt::new(0x636a7ee15ed43326), + Felt::new(0x25a943cbdbb77f80), + Felt::new(0x2eae2a071905d048), ]), Word::new([ - Felt::new(0xd339a7d5f35bdf7e), - Felt::new(0x296127097a98dbdd), - Felt::new(0x66d3eac876c1a984), - Felt::new(0x2714db555ccbdca4), + Felt::new(0x2cba8fe9fea65ee7), + Felt::new(0x6a41be71d2fc7028), + Felt::new(0x605212e2ee9bc5b), + Felt::new(0xc21c2908a5658935), ]), Word::new([ - Felt::new(0x6ca87920a067acd6), - Felt::new(0x55a102d742879de5), - Felt::new(0xa3f57a4b6a56d7f3), - Felt::new(0x18770879a832a177), + Felt::new(0xaac0de8bd44b1d9f), + Felt::new(0x7225852b69d88957), + Felt::new(0x2f7c909294f15753), + Felt::new(0x7affa1185c0381b7), ]), Word::new([ - Felt::new(0x1208793a0a77e984), - Felt::new(0x89ed5a1f45b34219), - Felt::new(0x8d74e6f75e77ef03), - Felt::new(0xa78cc9b7d29a1ee9), + Felt::new(0x34ca88a0e0d63460), + Felt::new(0x389fbe4ae11a59f6), + Felt::new(0x81e26cd8421a5db7), + Felt::new(0xdfac53bc294b2842), ]), Word::new([ - Felt::new(0x345fffecefd56a65), - Felt::new(0x23dec03b9f663006), - Felt::new(0x66f452be070b34d8), - Felt::new(0xe1c6aaaa54765dc), + Felt::new(0x46568c6daa1fffec), + Felt::new(0x2e2cda75ec40e041), + Felt::new(0x54fc7cb3b80bee28), + Felt::new(0xf0195f14b6ce7a91), ]), Word::new([ - Felt::new(0xb7b714ca60c77011), - Felt::new(0x5ffe9476baa10259), - Felt::new(0xbea15cd2b4150dda), - Felt::new(0x4467804ab919e3d8), + Felt::new(0x13593d27ded29298), + Felt::new(0xefe26b0c14bf1017), + Felt::new(0xfe921a040d8b3627), + Felt::new(0x79e4d45d9160c42), ]), Word::new([ - Felt::new(0x21c65ff7a4185640), - Felt::new(0x5d20fffcc7779035), - Felt::new(0x92b571249fa728d5), - Felt::new(0xe9e2eeca83b49342), + Felt::new(0x55235f121b2222a0), + Felt::new(0x5a3585db845aec02), + Felt::new(0x63530f0bfe01fd8e), + Felt::new(0xe8b1ca6f13d69fd6), ]), Word::new([ - Felt::new(0x2be46f9d0b431948), - Felt::new(0xa5c1a12c95a56f44), - Felt::new(0xc620adca5cd874), - Felt::new(0x51c64b969c1dc0c9), + Felt::new(0xe67cc603113d0cfa), + Felt::new(0xe6e6191b2f335634), + Felt::new(0x586d9798d40674ca), + Felt::new(0xcc05c4b81a3e9cf5), ]), Word::new([ - Felt::new(0xae9e7f421ab12b3f), - Felt::new(0x5f90e40cdb47bbec), - Felt::new(0x3d2a6be00ce19d31), - Felt::new(0x609632a98643a06c), + Felt::new(0x287625a6e0ac9570), + Felt::new(0xb3ca03f21da48806), + Felt::new(0x4d757e14a282da56), + Felt::new(0x62f916fb91c5ad44), ]), Word::new([ - Felt::new(0xde0f38c1d4cf5977), - Felt::new(0x3d6f0f44105b0fb2), - Felt::new(0x5f34005bed22db6f), - Felt::new(0x377009a91543c872), + Felt::new(0x6a3381ff8ac5eda8), + Felt::new(0x18bc36351ee69a6a), + Felt::new(0x96735310f4c0cf6), + Felt::new(0x82d29f8e231dbec6), ]), Word::new([ - Felt::new(0x8763f1c59420f662), - Felt::new(0xbe15cc9b37fdbdb3), - Felt::new(0xeed75857d5451487), - Felt::new(0x10b3507901fcfb8b), + Felt::new(0x22019fe65e285413), + Felt::new(0xf1ea9640113d7e79), + Felt::new(0x6eb829f8d3dac377), + Felt::new(0xa5a5390e2c61e707), ]), Word::new([ - Felt::new(0x2e40aeb53f7ba2f5), - Felt::new(0xed598b103221e46), - Felt::new(0xed943dae7f3be8f7), - Felt::new(0x7115a61b37a95c30), + Felt::new(0x94f3666f3ec0cad6), + Felt::new(0x41af521db5132b0f), + Felt::new(0x12f521b602f26bb7), + Felt::new(0x6fb0099fc03771d7), ]), Word::new([ - Felt::new(0xef9bdb6947abe190), - Felt::new(0xfe47c74c5ad62e2b), - Felt::new(0xf85ee41bc6960d64), - Felt::new(0x55e51b57d5990e2b), + Felt::new(0xb94b561402c5d3f5), + Felt::new(0x74676ed17243d4ab), + Felt::new(0x501728289cec61a7), + Felt::new(0xa261eeff641cf1f), ]), Word::new([ - Felt::new(0x81798808c02cffe0), - Felt::new(0x22df79cb29991e51), - Felt::new(0x965714f831e09f90), - Felt::new(0x67ac8aef55047894), + Felt::new(0x81638d8c4c380987), + Felt::new(0x301ade5b72dae9cf), + Felt::new(0xf83af45539a37292), + Felt::new(0x9c70b7c60b3cd79a), ]), Word::new([ - Felt::new(0x321d58dfe138e326), - Felt::new(0xee80b399b444c937), - Felt::new(0x6cbc565ad3d69ccf), - Felt::new(0x6ea9e44922bdc48e), + Felt::new(0x25e6179ad88ec2c8), + Felt::new(0xd9d92f7f8d44d084), + Felt::new(0x920cf9eac097e734), + Felt::new(0xbe57d8b3b732eee7), ]), Word::new([ - Felt::new(0xf2a11cd35120f6c3), - Felt::new(0x4e77378973b2ca8), - Felt::new(0x1adb26e6c1630440), - Felt::new(0x42e5495895baee92), + Felt::new(0xea4f289f9693ec8d), + Felt::new(0x588619a7df3e0fc6), + Felt::new(0x6389b9f3593c4147), + Felt::new(0x80eddd98a455f424), ]), Word::new([ - Felt::new(0xe07020daabcdebae), - Felt::new(0x2d5c6456b50a05c3), - Felt::new(0x3050bdec1715085b), - Felt::new(0xe4d05bce00c4f30d), + Felt::new(0xfdb071de197aa1fa), + Felt::new(0x9ebf28b5367b0b30), + Felt::new(0x903691bea5e02644), + Felt::new(0x1885c5b911033b78), ]), Word::new([ - Felt::new(0xe64d54038f19cbc3), - Felt::new(0xde70db9c37ffc1a6), - Felt::new(0xb4a54682fe025b15), - Felt::new(0xcb4e28247a791f29), + Felt::new(0x14c62abb80b3a5a), + Felt::new(0xddb6686bbebe29c5), + Felt::new(0xb81da11fbadf796c), + Felt::new(0xf10541bf63efaedc), ]), Word::new([ - Felt::new(0x9cd2815cef52b092), - Felt::new(0xd46fa4485d7aaf5c), - Felt::new(0x275777fd3b9637f7), - Felt::new(0x684317aab8bb48da), + Felt::new(0x53769de620b4d7d5), + Felt::new(0xc8357de15f14c907), + Felt::new(0x3a3430a09e6ab803), + Felt::new(0x349eb5b389339747), ]), Word::new([ - Felt::new(0xfef7ca5bc1a8531), - Felt::new(0x66da24a709f70f60), - Felt::new(0xc6d0048b0222dc8d), - Felt::new(0xd82cc216eed266b4), + Felt::new(0xd32edd92ee3597cd), + Felt::new(0xfed08f68579f5d19), + Felt::new(0x8080848b85559b70), + Felt::new(0x18ee5f8f74772555), ]), Word::new([ - Felt::new(0xc11007d4b20c68e8), - Felt::new(0xb88dea2c92f105d1), - Felt::new(0xfe885fceee959992), - Felt::new(0x4d8c71eeb47c8f90), + Felt::new(0x1eaecabc395eb3ca), + Felt::new(0xcee04708413469f3), + Felt::new(0x9e327fc63d557259), + Felt::new(0xb734b4fb745d8876), ]), Word::new([ - Felt::new(0x980fbe89dd050886), - Felt::new(0xdb7ad3a9a9f39be1), - Felt::new(0xf830b6439c7e9bc0), - Felt::new(0xdd76938b7c686c07), + Felt::new(0xd8334c7f3822e64d), + Felt::new(0xd6fac4cf1a8e9e9b), + Felt::new(0x889e88c42d27086d), + Felt::new(0x9fc028639782ac3e), ]), Word::new([ - Felt::new(0xb678629d183cf841), - Felt::new(0x664f19c21a6465bc), - Felt::new(0xba142578e79eb9d0), - Felt::new(0x1d57cbcdb2bbcb18), + Felt::new(0x2b31fcf3db65a0b8), + Felt::new(0x2335ec0c61381060), + Felt::new(0xf83740c4484f865d), + Felt::new(0xca78a031f895b478), ]), Word::new([ - Felt::new(0xc8822aae44990ed3), - Felt::new(0xf7edce1d2a235ab4), - Felt::new(0x8f35d229e981ca01), - Felt::new(0x50dc4c673c8fc58), + Felt::new(0xb024ed5aaab4e0e2), + Felt::new(0xb679e50459d10961), + Felt::new(0x2aa647595b95809a), + Felt::new(0x6cbb814d03cb1050), ]), Word::new([ - Felt::new(0x86edc215b92306e7), - Felt::new(0xe333ce17fd0f40df), - Felt::new(0xabcca6409bb48e29), - Felt::new(0xa110affea61c2951), + Felt::new(0x973d00d01b315e5c), + Felt::new(0xca40abc63e02d761), + Felt::new(0xffe9668b74f5d3e), + Felt::new(0x9e32938755c526d1), ]), Word::new([ - Felt::new(0x269d782ba380a2e4), - Felt::new(0x32bdc65a619310cd), - Felt::new(0x52895e02cb1df991), - Felt::new(0x1749f76388896455), + Felt::new(0x9d11baa0d7916712), + Felt::new(0xfb39766c72705f4e), + Felt::new(0xb4e2faa466611377), + Felt::new(0xe506307dd8d57b43), ]), Word::new([ - Felt::new(0x8f99ac4698f12102), - Felt::new(0x9983407c99f948c5), - Felt::new(0x7b89856f980cb407), - Felt::new(0x8acff0a017e68367), + Felt::new(0x95ffb9fc14efdecb), + Felt::new(0xbb2eadb4662b9da4), + Felt::new(0x5474a5291e5489f3), + Felt::new(0x95dc151ad0aaca6f), ]), Word::new([ - Felt::new(0xe7983973c759318a), - Felt::new(0xb389a6754d7976ca), - Felt::new(0x1d7ded5044c6ede6), - Felt::new(0x4290fc214bd001d7), + Felt::new(0xea122881fe161b45), + Felt::new(0x96489b253c89ae19), + Felt::new(0xc137c2a9fc3939f9), + Felt::new(0x28ef67075c763533), ]), Word::new([ - Felt::new(0x2a1607db398812eb), - Felt::new(0xbbaaed6d5de95899), - Felt::new(0x53699ff1add9cc48), - Felt::new(0x71c99c4c20a2cbe9), + Felt::new(0xdf9cc227338945fe), + Felt::new(0x4e009f2fa0d11d7a), + Felt::new(0x93d28360b41fea9a), + Felt::new(0x2e23dd18fb5e3430), ]), Word::new([ - Felt::new(0x9c211abaf37beba7), - Felt::new(0x44f91ee2c8f84830), - Felt::new(0xdaf1ca95546ff105), - Felt::new(0x4d15218eb972c855), + Felt::new(0xbbacee1eac480274), + Felt::new(0x5df2420822bbdb29), + Felt::new(0x273876d51ab28a7c), + Felt::new(0x5b2cce865277eb8f), ]), Word::new([ - Felt::new(0x47018b067647ff40), - Felt::new(0xabd25bc93e3cd280), - Felt::new(0x1e33fd9953262800), - Felt::new(0x9b8f68c64db159c3), + Felt::new(0x8eacb9ec570d0bfd), + Felt::new(0xd93cb13f32c59541), + Felt::new(0xa03a7780adeab0cb), + Felt::new(0x96bf26bf16a0c20a), ]), Word::new([ - Felt::new(0xa20fb2bcf2636418), - Felt::new(0xdc8996a5085ba820), - Felt::new(0x349b49cddfc936d4), - Felt::new(0x1d816c1d0bfef815), + Felt::new(0xd02199f182c7c44c), + Felt::new(0x2bcb02e4d9972e10), + Felt::new(0x1947aa10d093b782), + Felt::new(0xf8ce237f239a422e), ]), Word::new([ - Felt::new(0xc19364d95b916e9b), - Felt::new(0xdcb0cf99f9ca31b5), - Felt::new(0x1b5493d83dca1621), - Felt::new(0xf0758e5e3459a0f9), + Felt::new(0xc59e3b7237e49c5d), + Felt::new(0x99ef9f91bd859046), + Felt::new(0x1a045cfcbfc42d3c), + Felt::new(0x52f64e1f7e149d50), ]), Word::new([ - Felt::new(0xdf2cb895c1de5b3a), - Felt::new(0xb7347b591512353a), - Felt::new(0xfdbbce7837412eb7), - Felt::new(0x893fa2af604d446f), + Felt::new(0xd80c9ddbb8d33e12), + Felt::new(0xe2eba2ca287db211), + Felt::new(0xbf3b52bf58c3b6fa), + Felt::new(0xe45d1fcd58fae7c1), ]), Word::new([ - Felt::new(0x75d2b8fc6043d11e), - Felt::new(0xa6559eaebeed7d5f), - Felt::new(0xa9379773dad056b3), - Felt::new(0xf3bcf80d0748d3c1), + Felt::new(0x58b23f68e7c1181), + Felt::new(0xc1957d3818a0f35f), + Felt::new(0xc369c3257cdff85c), + Felt::new(0x4ecfb5f107df28ce), ]), Word::new([ - Felt::new(0x35b731f6c583214a), - Felt::new(0x26249962927dd05f), - Felt::new(0x75fc431a3e7c9e93), - Felt::new(0x170c1036d421083d), + Felt::new(0x4b8c985ba74188b6), + Felt::new(0x66ea95855e191b57), + Felt::new(0x151c0662fe97ef91), + Felt::new(0x517ffd6e5dd87316), ]), Word::new([ - Felt::new(0x1d6fbabfdf0d9096), - Felt::new(0x7d7c9687d9eca6cc), - Felt::new(0xd62974b75d27af1c), - Felt::new(0x93ed9a3825cc1c35), + Felt::new(0xaa310cfa768bc071), + Felt::new(0x35c683204f1f7a5b), + Felt::new(0xf1a3e64c7fd12971), + Felt::new(0xc071d798762a5f59), ]), Word::new([ - Felt::new(0xd30946664518ff6e), - Felt::new(0xd3e54aedfb7ba7e5), - Felt::new(0xb91107b2bfadfa1d), - Felt::new(0x2f7b0b2cb06cc43b), + Felt::new(0xe163a0558e4ada6a), + Felt::new(0x16864ee667320dd0), + Felt::new(0x6a7ee65793d04854), + Felt::new(0x59b459e6c0cfd7e), ]), Word::new([ - Felt::new(0x47d1dc850260558e), - Felt::new(0x8e5dbde02c522c39), - Felt::new(0x3464d9fe9fd4a1e), - Felt::new(0xf9e5bcfe534437be), + Felt::new(0x22e4a0a001eb1015), + Felt::new(0xba711695a8865274), + Felt::new(0x586544b816143930), + Felt::new(0xd6e2bf9b531be327), ]), Word::new([ - Felt::new(0x83378fd3d8e8a235), - Felt::new(0x5c12793119b6dfdb), - Felt::new(0xfc6f110fe6acbc57), - Felt::new(0x3138336422622a48), + Felt::new(0x8c72517df821c34b), + Felt::new(0x1a9838a014fd288e), + Felt::new(0xe5411d1d40288388), + Felt::new(0x7d79eb302daa9e3b), ]), Word::new([ - Felt::new(0x239c939a0a9787a0), - Felt::new(0x6c0d389b75f1cfb4), - Felt::new(0xc1947a136998e2a1), - Felt::new(0x76c1779e38ffb573), + Felt::new(0xe43f22af98d4319c), + Felt::new(0x25e3871ca9f92728), + Felt::new(0x762bf3cdc93e6a14), + Felt::new(0x16f6e357adafee63), ]), Word::new([ - Felt::new(0x7a266f705e62cd23), - Felt::new(0x7246a2960a038c2), - Felt::new(0xc1f47087a22fa2cb), - Felt::new(0xf298d4651af0b8b2), + Felt::new(0xa99f1387a11c6c9d), + Felt::new(0x68a1c792ee1cda6), + Felt::new(0xb6af58d26d306e00), + Felt::new(0xfca5d691fd46cb33), ]), Word::new([ - Felt::new(0x623bcdb6cc15e3ad), - Felt::new(0x8a6e820adf4725c3), - Felt::new(0x48737f1587e13208), - Felt::new(0xbf747c141bf3564c), + Felt::new(0x46b19d9575d5a999), + Felt::new(0x4c85bbe9c316d745), + Felt::new(0xe75c3c0215fdc2a3), + Felt::new(0x627bcc2c05380f82), ]), Word::new([ - Felt::new(0x14842ec18fe2348c), - Felt::new(0x3701e593309841cf), - Felt::new(0x8e85c9f1884fdf7b), - Felt::new(0xa4cc06a6b8cae8e3), + Felt::new(0xe0d9f7bd745a6139), + Felt::new(0xecd57179df992475), + Felt::new(0x14adc18ec0ab9a32), + Felt::new(0x87e2e8c38e84d27d), ]), Word::new([ - Felt::new(0xcfebb64f4bf4f79), - Felt::new(0x54754d70569eeecd), - Felt::new(0x50405a6b79d1ae83), - Felt::new(0x9075f17ccebc056c), + Felt::new(0xd8ad07ca4b066157), + Felt::new(0x40cbe185468109c0), + Felt::new(0x8ff6e16bb6f0acf3), + Felt::new(0x3a8538ff68aeb4a0), ]), Word::new([ - Felt::new(0x9a93d03d0f9c825f), - Felt::new(0xf704fa1580fe53c7), - Felt::new(0xf95df7a7d06a22b7), - Felt::new(0x8462e18adf8b3607), + Felt::new(0x7f0c04ca40410bde), + Felt::new(0xc93b0fc7fe5a4be4), + Felt::new(0x541bd0fff26378a2), + Felt::new(0xf4583e69e45aa98b), ]), Word::new([ - Felt::new(0xb5210a5cbd9556b4), - Felt::new(0x6fd4218ebf3c7336), - Felt::new(0x19d661830be725ae), - Felt::new(0x28a2862b49428353), + Felt::new(0x16c3ad2b8fcbf6a6), + Felt::new(0x20f02cdbe3a422ca), + Felt::new(0xa0c82db5bb435141), + Felt::new(0xdf60ac327044e6dd), ]), Word::new([ - Felt::new(0x93ceddc8a18724fe), - Felt::new(0x4e3299172e9130ed), - Felt::new(0x80229e0e1808c860), - Felt::new(0x13f479347eb7fd78), + Felt::new(0x23f34324486cf69a), + Felt::new(0x56d25f6ea8774a2e), + Felt::new(0x778eb42f8e4203ee), + Felt::new(0xd9bfd51145f77a34), ]), Word::new([ - Felt::new(0x86e4db88d39d4203), - Felt::new(0x81c24ca37b068741), - Felt::new(0x12b7d21371ca58f5), - Felt::new(0xb6546d4722efcbb0), + Felt::new(0x964229279ccb8ee9), + Felt::new(0x21a4dce43c249fc3), + Felt::new(0xd74eb222e6e70016), + Felt::new(0xf1939377a0cd2d8), ]), Word::new([ - Felt::new(0x96ce1320155172c5), - Felt::new(0xbf622c3a5ab86fba), - Felt::new(0x5876a31266554836), - Felt::new(0x4f23fd3646963922), + Felt::new(0x6accdf4f970c96a2), + Felt::new(0x843589dfd6e770c0), + Felt::new(0xdcf316fec1f41538), + Felt::new(0xc679df0c52121fc9), ]), Word::new([ - Felt::new(0x48f59a613c9baa43), - Felt::new(0x761de5ad2fd2c806), - Felt::new(0x2db99e809e35728f), - Felt::new(0x38b05ac131a0edc0), + Felt::new(0xb84e5d32796aae8c), + Felt::new(0xe8059efb9fbdd0bd), + Felt::new(0xd2727b248292d2fa), + Felt::new(0xfe80340c738661b2), ]), Word::new([ - Felt::new(0x6e4fba2ef071cb8a), - Felt::new(0x77c415814dc5a0e5), - Felt::new(0xa0492c8344a64bb1), - Felt::new(0xc6dd5e8dbc841fe5), + Felt::new(0x767c7d96e0863dd9), + Felt::new(0xec2ee4294c5b4456), + Felt::new(0x4e1c55d9f00c292), + Felt::new(0x93d56e40ac873ea4), ]), Word::new([ - Felt::new(0xa31e2e634187fec), - Felt::new(0xa4c019c12457e4b1), - Felt::new(0xa7b9de27a75ec6b4), - Felt::new(0x990bacb76b218164), + Felt::new(0x9a9ef79a545675db), + Felt::new(0xa72b254d8276b0ed), + Felt::new(0x931881e4971e5385), + Felt::new(0x81c8e39b3f7dd52f), ]), Word::new([ - Felt::new(0x97c85363ab1c997e), - Felt::new(0xa95cd8ccab0b41b3), - Felt::new(0xb98b7ebc3680470b), - Felt::new(0x22fe1b13640f19a9), + Felt::new(0x935669bd1213fd04), + Felt::new(0x72c57f762c09437), + Felt::new(0x18217f624bc70556), + Felt::new(0xd64b394846c549af), ]), Word::new([ - Felt::new(0x113c9a5f0dfbf187), - Felt::new(0x77f2dc9c46f3d699), - Felt::new(0xc3d92d34c950f8f4), - Felt::new(0x9cc6aaf7ffdaf2b6), + Felt::new(0x9bbb4616365b2ea2), + Felt::new(0x19c8c5d9137e56ce), + Felt::new(0x9d1111260fecec13), + Felt::new(0x41a2b2e0f870b070), ]), Word::new([ - Felt::new(0xa5f51bc68b62924c), - Felt::new(0x54182d0b8b519f87), - Felt::new(0x15b42760e2ad50a5), - Felt::new(0x3b2d54cae046788), + Felt::new(0xc389cf2838e661df), + Felt::new(0x2b2390795679fc2d), + Felt::new(0x1a06f8f594a5779f), + Felt::new(0x6af322014f565c2f), ]), Word::new([ - Felt::new(0xefec815078a58b7d), - Felt::new(0x2d02bd88ae2b8a3d), - Felt::new(0x7b3dee115eaac60a), - Felt::new(0x8e659da09a00ffad), + Felt::new(0xa5c607667558109a), + Felt::new(0x7b9587a0fa3b82fe), + Felt::new(0x46514e189536926b), + Felt::new(0xf415f181043e7187), ]), Word::new([ - Felt::new(0x1987fb02253b4338), - Felt::new(0xa7c1e3a856b329be), - Felt::new(0x8d90defd305553e8), - Felt::new(0xb132ee2f7526c94d), + Felt::new(0xd116f0cec205c7f8), + Felt::new(0x32b47d268aee9985), + Felt::new(0x33c45a0dce9d411d), + Felt::new(0x1b8666d00071eb40), ]), Word::new([ - Felt::new(0x1556b5441e77734f), - Felt::new(0xa2ceaf4b2822a233), - Felt::new(0x41e5ee79d553e00c), - Felt::new(0xf2cdc04fd67a69f2), + Felt::new(0x763c2acc6485a335), + Felt::new(0x8898a0ecea149931), + Felt::new(0xf950dee87da2cfa0), + Felt::new(0xb67c8db4e1f76963), ]), Word::new([ - Felt::new(0xd4a8d6025cb6e3a), - Felt::new(0xd8d181277e183f21), - Felt::new(0x3bf9725618de11d3), - Felt::new(0xb26a0eb5a7d9c86e), + Felt::new(0xeab92f17cc5669e3), + Felt::new(0x1d7c7c77ec161b77), + Felt::new(0x51339cda396b9126), + Felt::new(0x136d9afecd1ce45d), ]), Word::new([ - Felt::new(0xcdc5ad1e45e351c2), - Felt::new(0xf00b06807244caa6), - Felt::new(0xbbb01495c9a7e17d), - Felt::new(0xd9614df7e656a73f), + Felt::new(0x638db6faffb9b065), + Felt::new(0x46b7c590267345a2), + Felt::new(0x3a83af8bdb9fa24a), + Felt::new(0x7c4b3be077e93d15), ]), Word::new([ - Felt::new(0xfc61931c05c4a55f), - Felt::new(0xcff6ce2372650a9), - Felt::new(0x6e6c32ff4dc92c6), - Felt::new(0x1b72d8c04b0e409d), + Felt::new(0x713ca5afe11f3e5b), + Felt::new(0xaafc34e968ae8792), + Felt::new(0x290b4cdef7a48a56), + Felt::new(0xabff471fe841e07f), ]), Word::new([ - Felt::new(0x77d6e2f32439dbf0), - Felt::new(0xa1f8ce9eb02419a5), - Felt::new(0xd3ad9fb0cea61624), - Felt::new(0x66ab5c684fbb8597), + Felt::new(0x46bbadc4fd475e8b), + Felt::new(0xf26792d3ac61c3ae), + Felt::new(0x3e57dafa1a6af337), + Felt::new(0xc6d722ae0d17a589), ]), - EMPTY_WORD, + Word::new([Felt::new(0x0), Felt::new(0x0), Felt::new(0x0), Felt::new(0x0)]), ]; #[test] @@ -1589,7 +1589,7 @@ fn all_depths_opens_to_zero() { assert_eq!(depth as usize + 1, subtree.len()); // assert the opening is zero - let initial = EMPTY_WORD; + let initial = crate::EMPTY_WORD; assert_eq!(initial, subtree.remove(0)); // compute every node of the path manually and compare with the output @@ -1607,7 +1607,7 @@ fn all_depths_opens_to_zero() { fn test_entry() { // check the leaf is always the empty work for depth in 0..255 { - assert_eq!(EmptySubtreeRoots::entry(depth, depth), &EMPTY_WORD); + assert_eq!(EmptySubtreeRoots::entry(depth, depth), &crate::EMPTY_WORD); } // check the root matches the first element of empty_hashes diff --git a/miden-crypto/src/merkle/mmr/partial.rs b/miden-crypto/src/merkle/mmr/partial.rs index 30d30146b..7e26253bd 100644 --- a/miden-crypto/src/merkle/mmr/partial.rs +++ b/miden-crypto/src/merkle/mmr/partial.rs @@ -640,20 +640,15 @@ mod tests { utils::{Deserializable, Serializable}, }; - // Note: This function works around the fact that P3 constructors are not const. - // Once upstream Plonky3 releases our const constructor changes, this should be - // reverted to `const LEAVES: [Word; 7] = [...]`. See issue #731. - fn leaves() -> [Word; 7] { - [ - int_to_node(0), - int_to_node(1), - int_to_node(2), - int_to_node(3), - int_to_node(4), - int_to_node(5), - int_to_node(6), - ] - } + const LEAVES: [Word; 7] = [ + int_to_node(0), + int_to_node(1), + int_to_node(2), + int_to_node(3), + int_to_node(4), + int_to_node(5), + int_to_node(6), + ]; #[test] fn test_partial_mmr_apply_delta() { @@ -733,7 +728,7 @@ mod tests { #[test] fn test_partial_mmr_inner_nodes_iterator() { // build the MMR - let mmr: Mmr = leaves().into(); + let mmr: Mmr = LEAVES.into(); let first_peak = mmr.peaks().peaks()[0]; // -- test single tree ---------------------------- @@ -892,7 +887,7 @@ mod tests { #[test] fn test_partial_mmr_untrack() { // build the MMR - let mmr: Mmr = leaves().into(); + let mmr: Mmr = LEAVES.into(); // get path and node for position 1 let node1 = mmr.get(1).unwrap(); @@ -920,7 +915,7 @@ mod tests { #[test] fn test_partial_mmr_untrack_returns_removed_nodes() { // build the MMR - let mmr: Mmr = leaves().into(); + let mmr: Mmr = LEAVES.into(); // get path and node for position 1 let node1 = mmr.get(1).unwrap(); @@ -950,7 +945,7 @@ mod tests { #[test] fn test_partial_mmr_untrack_shared_nodes() { // build the MMR - let mmr: Mmr = leaves().into(); + let mmr: Mmr = LEAVES.into(); // track two sibling leaves let node0 = mmr.get(0).unwrap(); diff --git a/miden-crypto/src/merkle/path.rs b/miden-crypto/src/merkle/path.rs index 59fccfc61..8effe8cf9 100644 --- a/miden-crypto/src/merkle/path.rs +++ b/miden-crypto/src/merkle/path.rs @@ -249,7 +249,7 @@ impl Serializable for MerklePath { impl Deserializable for MerklePath { fn read_from(source: &mut R) -> Result { let count = source.read_u8()?.into(); - let nodes = source.read_many::(count)?; + let nodes: Vec = source.read_many_iter(count)?.collect::>()?; Ok(Self { nodes }) } } diff --git a/miden-crypto/src/merkle/smt/forest/mod.rs b/miden-crypto/src/merkle/smt/forest/mod.rs index b80efe903..f9b38ce6e 100644 --- a/miden-crypto/src/merkle/smt/forest/mod.rs +++ b/miden-crypto/src/merkle/smt/forest/mod.rs @@ -126,7 +126,7 @@ impl SmtForest { /// Inserts the specified key-value pair into an SMT with the specified root. This will also /// add a new root to the forest. Returns the new root. /// - /// Returns an error if an SMT with the specified root is not in the forest, these is not + /// Returns an error if an SMT with the specified root is not in the forest, there is not /// enough data in the forest to perform the insert, or if the insert would create a leaf /// with too many entries. pub fn insert(&mut self, root: Word, key: Word, value: Word) -> Result { @@ -136,7 +136,7 @@ impl SmtForest { /// Inserts the specified key-value pairs into an SMT with the specified root. This will also /// add a single new root to the forest for the entire batch of inserts. Returns the new root. /// - /// Returns an error if an SMT with the specified root is not in the forest, these is not + /// Returns an error if an SMT with the specified root is not in the forest, there is not /// enough data in the forest to perform the insert, or if the insert would create a leaf /// with too many entries. pub fn batch_insert( diff --git a/miden-crypto/src/merkle/smt/forest/tests.rs b/miden-crypto/src/merkle/smt/forest/tests.rs index a6b663cf3..bfb2547ee 100644 --- a/miden-crypto/src/merkle/smt/forest/tests.rs +++ b/miden-crypto/src/merkle/smt/forest/tests.rs @@ -35,10 +35,10 @@ fn test_insert_root_empty() -> Result<(), MerkleError> { assert_eq!( forest.insert(empty_tree_root, key, value)?, Word::new([ - Felt::new(10376354645124572258), - Felt::new(13808228093617896354), - Felt::new(4835829334388921262), - Felt::new(2144113770050911180) + Felt::new(17566703186976592377), + Felt::new(10698964398442624200), + Felt::new(17156877103802520546), + Felt::new(4782456441201120034), ]), ); Ok(()) @@ -55,10 +55,10 @@ fn test_insert_multiple_values() -> Result<(), MerkleError> { assert_eq!( new_root, Word::new([ - Felt::new(10376354645124572258), - Felt::new(13808228093617896354), - Felt::new(4835829334388921262), - Felt::new(2144113770050911180) + Felt::new(17566703186976592377), + Felt::new(10698964398442624200), + Felt::new(17156877103802520546), + Felt::new(4782456441201120034), ]), ); @@ -66,10 +66,10 @@ fn test_insert_multiple_values() -> Result<(), MerkleError> { assert_eq!( new_root, Word::new([ - Felt::new(10376354645124572258), - Felt::new(13808228093617896354), - Felt::new(4835829334388921262), - Felt::new(2144113770050911180) + Felt::new(17566703186976592377), + Felt::new(10698964398442624200), + Felt::new(17156877103802520546), + Felt::new(4782456441201120034), ]), ); @@ -82,10 +82,10 @@ fn test_insert_multiple_values() -> Result<(), MerkleError> { assert_eq!( new_root, Word::new([ - Felt::new(1600265794710932756), - Felt::new(4102884415474859847), - Felt::new(7916203901318401823), - Felt::new(9187865964280213047) + Felt::new(838199083189246611), + Felt::new(16778657488739036237), + Felt::new(13828051603373627066), + Felt::new(2453801083600635673), ]) ); @@ -111,10 +111,10 @@ fn test_batch_insert() -> Result<(), MerkleError> { assert_eq!( new_root, Word::new([ - Felt::new(7086678883692273722), - Felt::new(12292668811816691012), - Felt::new(10126815404170194367), - Felt::new(1147037274136690014) + Felt::new(117604577632613381), + Felt::new(15058212884545544983), + Felt::new(5650876462024416752), + Felt::new(6460194250276012043), ]) ); diff --git a/miden-crypto/src/merkle/smt/full/concurrent/mod.rs b/miden-crypto/src/merkle/smt/full/concurrent/mod.rs index 591d0cd27..f4ed54f46 100644 --- a/miden-crypto/src/merkle/smt/full/concurrent/mod.rs +++ b/miden-crypto/src/merkle/smt/full/concurrent/mod.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem; use num::Integer; -use rayon::prelude::*; +use p3_maybe_rayon::prelude::*; use super::{ EmptySubtreeRoots, InnerNode, InnerNodes, Leaves, MerkleError, MutationSet, NodeIndex, diff --git a/miden-crypto/src/merkle/smt/full/concurrent/tests.rs b/miden-crypto/src/merkle/smt/full/concurrent/tests.rs index 56fcf07ac..81d613ac4 100644 --- a/miden-crypto/src/merkle/smt/full/concurrent/tests.rs +++ b/miden-crypto/src/merkle/smt/full/concurrent/tests.rs @@ -7,7 +7,8 @@ use alloc::{ use assert_matches::assert_matches; use proptest::prelude::*; -use rand::{Rng, prelude::IteratorRandom, rng}; +use rand::{Rng, SeedableRng, prelude::IteratorRandom}; +use rand_chacha::ChaCha20Rng; use super::{ COLS_PER_SUBTREE, InnerNode, Map, NodeIndex, NodeMutations, PairComputations, SMT_DEPTH, @@ -116,7 +117,7 @@ fn generate_entries(pair_count: u64) -> Vec<(Word, Word)> { fn generate_updates(entries: Vec<(Word, Word)>, updates: usize) -> Vec<(Word, Word)> { const REMOVAL_PROBABILITY: f64 = 0.2; - let mut rng = rng(); + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); // Assertion to ensure input keys are unique assert!( entries.iter().map(|(key, _)| key).collect::>().len() == entries.len(), @@ -301,7 +302,7 @@ fn test_singlethreaded_subtrees() { /// The parallel version of `test_singlethreaded_subtree()`. #[test] fn test_multithreaded_subtrees() { - use rayon::prelude::*; + use p3_maybe_rayon::prelude::*; const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64; let entries = generate_entries(PAIR_COUNT); let control = Smt::with_entries_sequential(entries.clone()).unwrap(); @@ -381,7 +382,7 @@ fn test_multithreaded_subtrees() { fn test_with_entries_concurrent() { const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64; let mut entries = generate_entries(PAIR_COUNT); - let mut rng = rand::rng(); + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); // Set 10% of the entries to have empty words as their values. for _ in 0..PAIR_COUNT / 10 { diff --git a/miden-crypto/src/merkle/smt/full/error.rs b/miden-crypto/src/merkle/smt/full/error.rs index 8e9e9c3da..93adcd7ad 100644 --- a/miden-crypto/src/merkle/smt/full/error.rs +++ b/miden-crypto/src/merkle/smt/full/error.rs @@ -1,3 +1,5 @@ +use alloc::string::String; + use thiserror::Error; use crate::{ @@ -44,6 +46,10 @@ pub enum SmtLeafError { "multiple leaf contains {actual} entries but the maximum allowed is {MAX_LEAF_ENTRIES}" )] TooManyLeafEntries { actual: usize }, + + /// Elements could not be decoded into a leaf. + #[error("decoding error: {0}")] + DecodingError(String), } // SMT PROOF ERROR diff --git a/miden-crypto/src/merkle/smt/full/leaf.rs b/miden-crypto/src/merkle/smt/full/leaf.rs index 69ef0f284..caef59d16 100644 --- a/miden-crypto/src/merkle/smt/full/leaf.rs +++ b/miden-crypto/src/merkle/smt/full/leaf.rs @@ -10,6 +10,9 @@ use crate::{ utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; +/// The number of field elements in a key-value pair (two Words, 4 Felts each). +const DOUBLE_WORD_LEN: usize = 8; + /// Represents a leaf node in the Sparse Merkle Tree. /// /// A leaf can be empty, hold a single key-value pair, or multiple key-value pairs. @@ -176,12 +179,12 @@ impl SmtLeaf { // CONVERSIONS // --------------------------------------------------------------------------------------------- - /// Converts a leaf to a list of field elements - pub fn to_elements(&self) -> Vec { - self.clone().into_elements() + /// Returns an iterator over the field elements representing this leaf. + pub fn to_elements(&self) -> impl Iterator + '_ { + self.entries().iter().copied().flat_map(kv_to_elements) } - /// Converts a leaf to a list of field elements + /// Converts a leaf to a list of field elements. pub fn into_elements(self) -> Vec { self.into_entries().into_iter().flat_map(kv_to_elements).collect() } @@ -195,6 +198,54 @@ impl SmtLeaf { } } + /// Converts a list of elements into a leaf + pub fn try_from_elements( + elements: &[Felt], + leaf_index: LeafIndex, + ) -> Result { + if elements.is_empty() { + return Ok(SmtLeaf::new_empty(leaf_index)); + } + + // Elements should be organized into a contiguous array of K/V Words (4 Felts each). + if !elements.len().is_multiple_of(DOUBLE_WORD_LEN) { + return Err(SmtLeafError::DecodingError( + "elements length is not a multiple of 8".into(), + )); + } + + let num_entries = elements.len() / DOUBLE_WORD_LEN; + + if num_entries == 1 { + // Single entry. + let key = Word::new([elements[0], elements[1], elements[2], elements[3]]); + let value = Word::new([elements[4], elements[5], elements[6], elements[7]]); + Ok(SmtLeaf::new_single(key, value)) + } else { + // Multiple entries. + let mut entries = Vec::with_capacity(num_entries); + // Read k/v pairs from each entry. + for i in 0..num_entries { + let base_idx = i * DOUBLE_WORD_LEN; + let key = Word::new([ + elements[base_idx], + elements[base_idx + 1], + elements[base_idx + 2], + elements[base_idx + 3], + ]); + let value = Word::new([ + elements[base_idx + 4], + elements[base_idx + 5], + elements[base_idx + 6], + elements[base_idx + 7], + ]); + entries.push((key, value)); + } + let leaf = SmtLeaf::new_multiple(entries)?; + Ok(leaf) + } + } + // HELPERS // --------------------------------------------------------------------------------------------- diff --git a/miden-crypto/src/merkle/smt/full/tests.rs b/miden-crypto/src/merkle/smt/full/tests.rs index 7c6534ba1..029c6c8cc 100644 --- a/miden-crypto/src/merkle/smt/full/tests.rs +++ b/miden-crypto/src/merkle/smt/full/tests.rs @@ -994,6 +994,58 @@ fn test_smt_proof_get_returns_empty_for_absent_key_same_leaf() { ); } +/// Tests that `try_from_elements()` round-trips correctly for an empty leaf. +#[test] +fn test_smt_leaf_try_from_elements_empty() { + let leaf_index = LeafIndex::new_max_depth(42); + let expected = SmtLeaf::new_empty(leaf_index); + let elements: Vec = expected.to_elements().collect(); + let actual = SmtLeaf::try_from_elements(&elements, leaf_index).unwrap(); + assert_eq!(actual, expected); +} + +/// Tests that `try_from_elements()` round-trips correctly for a single-entry leaf. +#[test] +fn test_smt_leaf_try_from_elements_single_entry() { + let expected = SmtLeaf::new_single( + Word::from([10_u32, 11_u32, 12_u32, 13_u32]), + Word::from([1_u32, 2_u32, 3_u32, 4_u32]), + ); + let elements: Vec = expected.to_elements().collect(); + let actual = SmtLeaf::try_from_elements(&elements, expected.index()).unwrap(); + assert_eq!(actual, expected); +} + +/// Tests that `try_from_elements()` round-trips correctly for a multiple-entry leaf. +#[test] +fn test_smt_leaf_try_from_elements_multiple_entries() { + // Keys must have the same value in position 3 to map to the same leaf index. + let expected = SmtLeaf::new_multiple(vec![ + ( + Word::from([10_u32, 11_u32, 12_u32, 13_u32]), + Word::from([1_u32, 2_u32, 3_u32, 4_u32]), + ), + ( + Word::from([100_u32, 101_u32, 102_u32, 13_u32]), + Word::from([11_u32, 12_u32, 13_u32, 14_u32]), + ), + ]) + .unwrap(); + let elements: Vec = expected.to_elements().collect(); + let actual = SmtLeaf::try_from_elements(&elements, expected.index()).unwrap(); + assert_eq!(actual, expected); +} + +/// Tests that `try_from_elements()` returns the expected error when the list of elements is +/// not a multiple of 8. +#[test] +fn test_smt_leaf_try_from_elements_invalid_length() { + let elements = vec![Felt::from_u32(1), Felt::from_u32(2), Felt::from_u32(3)]; + let leaf_index = LeafIndex::new_max_depth(42); + let result = SmtLeaf::try_from_elements(&elements, leaf_index); + assert_matches!(result, Err(SmtLeafError::DecodingError(_))); +} + // HELPERS // -------------------------------------------------------------------------------------------- diff --git a/miden-crypto/src/merkle/smt/large/batch_ops.rs b/miden-crypto/src/merkle/smt/large/batch_ops.rs index ea71f2e98..9c4b9da35 100644 --- a/miden-crypto/src/merkle/smt/large/batch_ops.rs +++ b/miden-crypto/src/merkle/smt/large/batch_ops.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem; use num::Integer; -use rayon::prelude::*; +use p3_maybe_rayon::prelude::*; use super::{ IN_MEMORY_DEPTH, LargeSmt, LargeSmtError, LoadedLeaves, MutatedLeaves, ROOT_MEMORY_INDEX, diff --git a/miden-crypto/src/merkle/smt/large/construction.rs b/miden-crypto/src/merkle/smt/large/construction.rs index 1fe8b99fa..7ce8c5123 100644 --- a/miden-crypto/src/merkle/smt/large/construction.rs +++ b/miden-crypto/src/merkle/smt/large/construction.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use core::mem; -use rayon::prelude::*; +use p3_maybe_rayon::prelude::*; use super::{ CONSTRUCTION_SUBTREE_BATCH_SIZE, IN_MEMORY_DEPTH, LargeSmt, LargeSmtError, NUM_IN_MEMORY_NODES, diff --git a/miden-crypto/src/merkle/smt/large/storage/rocksdb.rs b/miden-crypto/src/merkle/smt/large/storage/rocksdb.rs index d2f843fa1..e2c314340 100644 --- a/miden-crypto/src/merkle/smt/large/storage/rocksdb.rs +++ b/miden-crypto/src/merkle/smt/large/storage/rocksdb.rs @@ -552,7 +552,7 @@ impl SmtStorage for RocksDbStorage { /// - `Ok(...)` if all fetches succeed. /// - `Err(StorageError)` if any RocksDB access or deserialization fails. fn get_subtrees(&self, indices: &[NodeIndex]) -> Result>, StorageError> { - use rayon::prelude::*; + use p3_maybe_rayon::prelude::*; let mut depth_buckets: [Vec<(usize, NodeIndex)>; 5] = Default::default(); @@ -809,7 +809,7 @@ impl SmtStorage for RocksDbStorage { /// # Errors /// - `StorageError::Backend`: If any column family is missing or a RocksDB write error occurs. fn apply(&mut self, updates: StorageUpdates) -> Result<(), StorageError> { - use rayon::prelude::*; + use p3_maybe_rayon::prelude::*; let mut batch = WriteBatch::default(); diff --git a/miden-crypto/src/merkle/smt/large_forest/backend.rs b/miden-crypto/src/merkle/smt/large_forest/backend.rs new file mode 100644 index 000000000..5c33a0dc2 --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/backend.rs @@ -0,0 +1,123 @@ +//! This file contains the [`Backend`] trait for the [`LargeSmtForest`] implementation and the +//! supporting types it needs. + +use alloc::{boxed::Box, vec::Vec}; +use core::fmt::Debug; + +use thiserror::Error; + +use crate::{ + Word, + merkle::{ + MerkleError, + smt::{ + SmtProof, + full::SMT_DEPTH, + large_forest::{ + history::VersionId, + operation::{SmtForestUpdateBatch, SmtUpdateBatch}, + }, + }, + }, +}; +// TYPE ALIASES +// ================================================================================================ + +/// The mutation set used by the forest backends. +/// +/// At the moment this is used for _reverse_ mutations that "undo" the changes made to the tree(s), +/// but may be harmonised with [`SmtUpdateBatch`] in the future. For more information on its use for +/// reverse mutations, see [`crate::merkle::smt::SparseMerkleTree::apply_mutations_with_reversion`]. +pub type MutationSet = crate::merkle::smt::MutationSet; + +// BACKEND +// ================================================================================================ + +/// The backing storage for the SMT forest, providing the necessary high-level methods for +/// performing operations on the full trees that make up the forest, while allowing the forest +/// itself to be storage agnostic. +/// +/// # Backend Data Storage +/// +/// Having a generic [`Backend`] provides no guarantees to the user about how it stores data and +/// what patterns are used for data access under the hood. It is, however, guaranteed to store +/// _only_ the data necessary to describe the latest state of each tree in the forest. +pub trait Backend +where + Self: Debug, +{ + // QUERIES + // ============================================================================================ + + /// Returns an opening for the specified `key` in the SMT with the specified `root`. + fn open(&self, root: Word, key: Word) -> Result; + + /// Returns the value associated with the provided `key` in the SMT with the provided `root`, or + /// [`None`] if no such value exists. + fn get(&self, root: Word, key: Word) -> Result>; + + /// Returns the version of the tree with the provided `root`. + fn version(&self, root: Word) -> Result; + + /// Returns an iterator over all the tree roots and versions that the backend knows about. + /// + /// The iteration order is unspecified. + fn versions(&self) -> Result>; + + // SINGLE-TREE MODIFIERS + // ============================================================================================ + + /// Performs the provided `updates` on the tree with the provided `root`, returning the mutation + /// set that will revert the changes made to the tree. + /// + /// Implementations must guarantee the following behavior, with non-conforming implementations + /// considered to be a bug: + /// + /// - At most one new root must be added to the forest for the entire batch. + /// - If applying the provided `updates` results in no changes to the tree, no new tree must be + /// allocated. + fn update_tree( + &mut self, + root: Word, + new_version: VersionId, + updates: SmtUpdateBatch, + ) -> Result; + + // MULTI-TREE MODIFIERS + // ============================================================================================ + + /// Performs the provided `updates` on the forest, setting all new tree states to have the + /// provided `new_version` and returning a vector of the mutation sets that reverse the changes + /// to each changed tree. + /// + /// Implementations must guarantee the following behaviour, with non-conforming implementations + /// considered to be a bug: + /// + /// - At most one new root must be added to the forest for each target root in the provided + /// `updates`. + /// - If applying the provided `updates` results in no changes to a given lineage of trees in + /// the forest, then no new tree must be allocated in that lineage. + fn update_forest( + &mut self, + new_version: VersionId, + updates: SmtForestUpdateBatch, + ) -> Result>; +} + +// BACKEND ERROR +// ================================================================================================ + +/// The error type for use within Backends. +#[derive(Debug, Error)] +pub enum BackendError { + /// Raised when there is an error with the merkle tree semantics within the backend. + #[error(transparent)] + Merkle(#[from] MerkleError), + + /// Raised for arbitrary other errors within the backend. + #[error(transparent)] + Other(#[from] Box), +} + +/// The result type for use with backends. +pub type Result = core::result::Result; diff --git a/miden-crypto/src/merkle/smt/large_forest/error.rs b/miden-crypto/src/merkle/smt/large_forest/error.rs index 14ca4cf3e..8176215b4 100644 --- a/miden-crypto/src/merkle/smt/large_forest/error.rs +++ b/miden-crypto/src/merkle/smt/large_forest/error.rs @@ -1,21 +1,51 @@ //! This module contains the error types and helpers for working with errors from the large SMT //! forest. +use alloc::boxed::Box; + use thiserror::Error; -use crate::merkle::{MerkleError, smt::large_forest::history::error::HistoryError}; +use crate::{ + Word, + merkle::{ + MerkleError, + smt::large_forest::{backend::BackendError, history::error::HistoryError}, + }, +}; + +// LARGE SMT FOREST ERROR +// ================================================================================================ -/// The errors returned by operations on the large SMT forest. -/// -/// This type primarily serves to wrap more specific error types from various subsystems into a -/// generic interface type. +/// The type of errors returned by operations on the large SMT forest. #[derive(Debug, Error)] pub enum LargeSmtForestError { + /// Errors in the history subsystem of the forest. #[error(transparent)] HistoryError(#[from] HistoryError), + /// Raised when an attempt is made to modify a frozen tree. + #[error("Attempted to modify non-current tree with root {0}")] + InvalidModification(Word), + + /// Errors with the merkle tree operations of the forest. #[error(transparent)] MerkleError(#[from] MerkleError), + + /// Raised for arbitrary other errors. + #[error(transparent)] + Other(#[from] Box), +} + +/// We want to forward backend errors specifically when we can, so we manually implement the +/// conversion. +impl From for LargeSmtForestError { + fn from(value: BackendError) -> Self { + match value { + BackendError::Merkle(e) => LargeSmtForestError::from(e), + BackendError::Other(e) => LargeSmtForestError::from(e), + } + } } -pub mod history {} +/// The result type for use within the large SMT forest portion of the library. +pub type Result = core::result::Result; diff --git a/miden-crypto/src/merkle/smt/large_forest/history/mod.rs b/miden-crypto/src/merkle/smt/large_forest/history/mod.rs index 37ce4daf0..097f04741 100644 --- a/miden-crypto/src/merkle/smt/large_forest/history/mod.rs +++ b/miden-crypto/src/merkle/smt/large_forest/history/mod.rs @@ -39,7 +39,7 @@ use core::fmt::Debug; use error::{HistoryError, Result}; use crate::{ - Map, Set, Word, + Map, Word, merkle::{ NodeIndex, smt::{LeafIndex, SMT_DEPTH}, @@ -87,6 +87,7 @@ pub type VersionId = u64; /// The versions are _cumulative_, meaning that querying the history must account for changes from /// the current tree that take place in versions that are not the queried version or the current /// tree. +#[allow(dead_code)] // Temporary #[derive(Clone, Debug)] pub struct History { /// The maximum number of historical versions to be stored. @@ -110,6 +111,7 @@ pub struct History { deltas: VecDeque, } +#[allow(dead_code)] // Temporary impl History { /// Constructs a new history container, containing at most `max_count` historical versions for /// a tree. @@ -136,13 +138,23 @@ impl History { /// Returns all the roots that the history knows about. /// + /// The iteration order of the roots is guaranteed to move backward in time, with earlier items + /// being roots from versions closer to the present. + /// /// # Complexity /// - /// Calling this method requires a traversal of all the versions and is hence linear in the - /// number of history versions. - #[must_use] - pub fn roots(&self) -> Set { - self.deltas.iter().map(|d| d.root).collect() + /// Calling this method provides an iterator whose consumption requires a traversal of all the + /// versions. The method's complexity is thus `O(n)` in the number of versions. + pub fn roots(&self) -> impl Iterator { + self.deltas.iter().rev().map(|d| d.root) + } + + /// Gets the version corresponding to the provided `root`, or returns [`None`] if the provided + /// `root` is not found within this history. + pub fn version(&self, root: Word) -> Option { + self.deltas + .iter() + .find_map(|d| if d.root == root { Some(d.version_id) } else { None }) } /// Returns `true` if `root` is in the history and `false` otherwise. @@ -302,6 +314,7 @@ impl History { // ================================================================================================ /// A read-only view of the history overlay on the tree at a specified place in the history. +#[allow(dead_code)] // Temporary #[derive(Debug)] pub struct HistoryView<'history> { /// The index of the target version in the history. @@ -311,6 +324,7 @@ pub struct HistoryView<'history> { history: &'history History, } +#[allow(dead_code)] // Temporary impl<'history> HistoryView<'history> { /// Constructs a new history view that acts as a single overlay of the state represented by the /// oldest delta for which `f` returns true. @@ -413,6 +427,7 @@ struct Delta { pub leaves: LeafChanges, } +#[allow(dead_code)] // Temporary impl Delta { /// Creates a new delta with the provided `root`, and representing the provided /// changes to `nodes` and `leaves` in the merkle tree. diff --git a/miden-crypto/src/merkle/smt/large_forest/history/tests.rs b/miden-crypto/src/merkle/smt/large_forest/history/tests.rs index b3b763704..f559c9f24 100644 --- a/miden-crypto/src/merkle/smt/large_forest/history/tests.rs +++ b/miden-crypto/src/merkle/smt/large_forest/history/tests.rs @@ -1,6 +1,8 @@ #![cfg(feature = "std")] //! The functional tests for the history component. +use alloc::vec::Vec; + use p3_field::PrimeCharacteristicRing; use super::{CompactLeaf, History, LeafChanges, NodeChanges, error::Result}; @@ -32,7 +34,7 @@ fn roots() -> Result<()> { history.add_version(root_2, 1, nodes.clone(), leaves.clone())?; // We should be able to get all the roots. - let roots = history.roots(); + let roots = history.roots().collect::>(); assert_eq!(roots.len(), 2); assert!(roots.contains(&root_1)); assert!(roots.contains(&root_2)); diff --git a/miden-crypto/src/merkle/smt/large_forest/mod.rs b/miden-crypto/src/merkle/smt/large_forest/mod.rs index e67a5f27a..accffc95a 100644 --- a/miden-crypto/src/merkle/smt/large_forest/mod.rs +++ b/miden-crypto/src/merkle/smt/large_forest/mod.rs @@ -1,7 +1,382 @@ -//! A high-performance sparse merkle tree forest backed by pluggable storage. +//! A high-performance sparse merkle tree forest with pluggable backends. +//! +//! # Semantic Layout +//! +//! Much like `SparseMerkleTree`, the forest stores trees of depth 64 that use the compact leaf +//! optimization to uniquely store 256-bit elements. This reduces both the size of a merkle path, +//! and the computational work necessary to perform queries into the trees. +//! +//! # Storing Trees and Versions +//! +//! The usage of an SMT forest is conceptually split into two parts: a collection that is able to +//! store **multiple, unrelated trees**, and a container for **multiple versions of those trees**. +//! Both of these use-cases are supported by the forest, but have an explicit delineation between +//! them in both the API and the implementation. This has two impacts that a client of the forest +//! must understand. +//! +//! - While, when using a [`Backend`] that can persist data, **only the current full tree state is +//! persisted**, while **the historical data will not be**. This is designed into the structure of +//! the forest, and does not depend on the choice of storage backend. +//! - It is more expensive to query a given tree at an older point in its history than it is to +//! query it at a newer point, and querying at the current tree will always take the least time. +//! +//! # Data Storage +//! +//! The SMT forest is parametrised over the [`Backend`] implementation that it uses. These backends +//! may have significantly varied performance characteristics, and hence any performance analysis of +//! the forest should be done in conjunction with a specific backend. The forest itself takes pains +//! to not make any assumptions about properties of the backend in use. +//! +//! Take care to read the documentation of the specific [`Backend`] that you are planning to use in +//! order to understand its performance, gotchas, and other such details. +mod backend; mod error; mod history; +mod operation; +mod property_tests; +mod root; +mod tests; -pub use error::LargeSmtForestError; -pub use history::{History, HistoryView, error::HistoryError}; +pub use backend::{Backend, BackendError}; +pub use error::{LargeSmtForestError, Result}; +pub use operation::{ForestOperation, SmtForestUpdateBatch, SmtUpdateBatch}; +pub use root::RootInfo; + +use crate::{ + Map, Set, Word, + merkle::{ + EmptySubtreeRoots, MerkleError, + smt::{ + SMT_DEPTH, SmtProof, + large_forest::history::{History, VersionId}, + }, + }, +}; + +// SPARSE MERKLE TREE FOREST +// ================================================================================================ + +/// A high-performance forest of sparse merkle trees with pluggable storage. +/// +/// # Current and Frozen Trees +/// +/// Trees in the forest fall into two categories: +/// +/// 1. **Current:** These trees represent the latest version of their 'tree lineage' and can be +/// modified to generate a new tree version in the forest. +/// 2. **Frozen:** These are historical versions of trees that are no longer current, and are +/// considered 'frozen' and hence cannot be modified to generate a new tree version in the +/// forest. This is because being able to do so would effectively create a "fork" in the history, +/// and hence allow the forest to yield potentially invalid responses with regard to the +/// blockchain history. +/// +/// If an attempt is made to modify a frozen tree, the method in question will yield an +/// [`LargeSmtForestError::InvalidModification`] error as doing so represents a programmer bug. +/// +/// # Performance +/// +/// The performance characteristics of this forest depend heavily on the choice of underlying +/// [`Backend`] implementation. Where something more specific can be said about a particular method +/// call, the documentation for that method will state it. +#[allow(dead_code)] // Temporarily +#[derive(Debug)] +pub struct LargeSmtForest { + /// The backend for storing the full trees that exist as part of the forest. It makes no + /// guarantees as to where the tree data is stored, and **must not be exposed** in the API of + /// the forest for correctness. + backend: B, + + /// The container for the historical versions of each tree stored in the forest, identified by + /// the _current root_ of that tree. + /// + /// This should contain an entry for every tree lineage contained in the forest, under the root + /// of its current tree version. + histories: Map, + + /// A set tracking which lineage histories in `histories` contain actual deltas in order to + /// speed up querying. + /// + /// It must always be maintained as a strict subset of `histories.keys()`. + non_empty_histories: Set, +} + +// CONSTRUCTION AND BASIC QUERIES +// ================================================================================================ + +/// These functions deal with the creation of new forest instances, and hence rely on the ability to +/// query storage to do so. +/// +/// # Performance +/// +/// All the methods in this impl block require access to the underlying [`Backend`] instance to +/// return results. This means that their performance will depend heavily on the specific instance +/// with which the forest was constructed. +/// +/// Where anything more specific can be said about performance, the method documentation will +/// contain more detail. +impl LargeSmtForest { + /// Constructs a new forest backed by the provided `backend`. + /// + /// The constructor will treat whatever state is contained within the provided `backend` as the + /// starting state for the forest. This means that, if you pass a newly-initialized storage, the + /// forest will start in an empty state. Similarly, if you pass a `backend` that already + /// contains some data (loaded from disk, for example), then the forest will start in that state + /// instead. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::Other`] if the forest cannot be started up correctly using the + /// provided `backend`. + pub fn new(_backend: B) -> Result { + todo!("LargeSmtForest::new") + } +} + +/// These methods provide the ability to perform basic queries on the forest without the need to +/// access the underlying tree storage. +/// +/// # Performance +/// +/// All of these methods can be performed fully in-memory, and hence their performance is +/// predictable on a given machine regardless of the choice of [`Backend`] instance being used by +/// the forest. +impl LargeSmtForest { + /// Returns an iterator over all roots that the forest knows about, including those from all + /// historical versions. + /// + /// The iteration order of the roots is unspecified. + pub fn roots(&self) -> impl Iterator { + self.histories + .keys() + .cloned() + .chain(self.histories.values().flat_map(|h| h.roots())) + } + + /// Returns an iterator over the roots for the latest version of every tree in the forest. + /// + /// The iteration order is unspecified. + pub fn current_roots(&self) -> impl Iterator { + self.histories.keys().cloned() + } + + /// Returns an iterator over the historical roots in the forest belonging to the lineage with + /// the provided `current_root`. + /// + /// The iteration order of the roots is guaranteed to move backward in time, with earlier items + /// being roots from versions closer to the present. It does _not_ include the specified + /// `current_root`. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::MerkleError`] if no tree with the provided `root` exists in the + /// forest. + pub fn historical_roots(&self, current_root: Word) -> Result> { + self.histories + .get(¤t_root) + .map(|h| h.roots()) + .ok_or(MerkleError::RootNotInStore(current_root).into()) + } + + /// Returns the number of trees in the forest that have unique identity. + /// + /// This is **not** the number of unique tree lineages in the forest, as it includes all + /// historical trees as well. For that, see [`Self::lineage_count`]. + pub fn tree_count(&self) -> usize { + self.roots().count() + } + + /// Returns the number of unique tree lineages in the forest. + /// + /// This is **not** the number of unique trees in the forest, as it does not include all + /// versions in each lineage. For that, see [`Self::tree_count`]. + pub fn lineage_count(&self) -> usize { + self.histories.iter().len() + } + + /// Returns `true` if the provided `root` points to a tree that is the latest version, and + /// `false` otherwise. + /// + /// A tree being the latest version is one that can be modified to yield a new version. In other + /// words it does not represent a historical tree version. + pub fn is_latest_version(&self, root: Word) -> bool { + self.histories.contains_key(&root) || *EmptySubtreeRoots::entry(SMT_DEPTH, 0) == root + } +} + +// QUERIES +// ================================================================================================ + +/// These methods pertain to non-mutating queries about the data stored in the forest. They differ +/// from the simple queries in the previous block by requiring access to the backend to function. +/// +/// # Performance +/// +/// All the methods in this impl block require access to the underlying [`Backend`] instance to +/// return results. This means that their performance will depend heavily on the specific instance +/// with which the forest was constructed. +/// +/// Where anything more specific can be said about performance, the method documentation will +/// contain more detail. +impl LargeSmtForest { + /// Returns an opening for the specified `key` in the SMT with the specified `root`, or [`None`] + /// if there is no tree with the specified `root` in the forest. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::MerkleError`] if no tree with the provided `root` exists in the + /// forest, or if the forest does not contain sufficient data to provide an opening for `key`. + pub fn open(&self, _root: Word, _key: Word) -> Result> { + todo!("LargeSmtForest::open") + } + + /// Returns the value associated with the provided `key` in the SMT with the provided `root`, or + /// [`None`] if no such value exists. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::MerkleError`] if no tree with the provided `root` exists in the + /// forest, or if the forest does not contain sufficient data to get the value for `key`. + pub fn get(&self, _root: Word, _key: Word) -> Result> { + todo!("LargeSmtForest::get") + } + + /// Returns data describing what information the forest knows about the provided `root`. + pub fn knows_root(&self, root: Word) -> Result { + if self.histories.contains_key(&root) { + Ok(RootInfo::LatestVersion(self.backend.version(root)?)) + } else if let Some(v) = self.histories.values().find_map(|h| h.version(root)) { + Ok(RootInfo::HistoricalVersion(v)) + } else if root == *EmptySubtreeRoots::entry(SMT_DEPTH, 0) { + Ok(RootInfo::EmptyTree) + } else { + Ok(RootInfo::Missing) + } + } +} + +// SINGLE-TREE MODIFIERS +// ================================================================================================ + +/// These methods pertain to modifications that can be made to a single tree in the forest. They +/// exploit parallelism within the single target tree wherever possible. +/// +/// # Performance +/// +/// All the methods in this impl block require access to the underlying [`Backend`] instance to +/// return results. This means that their performance will depend heavily on the specific instance +/// with which the forest was constructed. +/// +/// Where anything more specific can be said about performance, the method documentation will +/// contain more detail. +#[allow(dead_code)] // Temporarily +impl LargeSmtForest { + /// Performs the provided `updates` on the tree with the provided `root`, adding a single new + /// root to the forest (corresponding to `new_version`) for the entire batch and returning that + /// root. + /// + /// If applying the provided `operations` results in no changes to the tree, then `root` will be + /// returned unchanged and no new tree will be allocated. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::InvalidModification`] if `root` corresponds to a tree that is not + /// the latest in its lineage. + /// - [`LargeSmtForestError::MerkleError`] if `root` is not a root known by the forest. + pub fn update_tree( + &mut self, + _root: Word, + _new_version: VersionId, + _updates: SmtUpdateBatch, + ) -> Result { + todo!("LargeSmtForest::modify_tree") + } +} + +// MULTI-TREE MODIFIERS +// ================================================================================================ + +/// These methods pertain to modifications that can be made to multiple trees in the forest at once. +/// They exploit parallelism both between trees and within trees wherever possible. +/// +/// # Performance +/// +/// All the methods in this impl block require access to the underlying [`Backend`] instance to +/// return results. This means that their performance will depend heavily on the specific instance +/// with which the forest was constructed. +/// +/// Where anything more specific can be said about performance, the method documentation will +/// contain more detail. +impl LargeSmtForest { + /// Performs the provided `updates` on the forest, adding at most one new root with version + /// `new_version` to the forest for each target root in `updates` and returning a mapping + /// from old root to new root. + /// + /// If applying the associated batch to any given lineage in the forest results in no changes to + /// that tree, the initial root for that lineage will be returned and no new tree will be + /// allocated. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::InvalidModification`] if any root in the batch corresponds to a + /// tree that is not the latest in its lineage. + /// - [`LargeSmtForestError::MerkleError`] if any root in the batch is not a root known by the + /// forest. + pub fn update_forest( + &mut self, + _new_version: VersionId, + _updates: SmtForestUpdateBatch, + ) -> Result> { + todo!("LargeSmtForest::modify_forest") + } + + /// Removes all tree versions in the forest that are older than the provided `version`. + /// + /// In the case that the current version of a given tree in the forest is older than `version`, + /// that current version is retained. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::Other`] if the backend cannot be accessed to get the full tree + /// versions. + /// + /// # Panics + /// + /// - If there is no history that corresponds to one of the trees that is fully stored. + pub fn truncate(&mut self, version: VersionId) -> Result<()> { + // Truncation in the history is defined such that it never removes a version that could + // possibly serve as the latest delta for a newer version. This is because it cannot safely + // know if a version `v` is between the latest delta `d` and the current version `c`, as it + // has no knowledge of the current version. + // + // Thus, if we have a version `v` such that `d <= v < c`, we need to retain the reversion + // delta `d` in the history to correctly service queries for `v`. If, however, we have `d < + // c <= v` we need to explicitly remove the last delta as well. + // + // To that end, we handle the latter case first, by explicitly calling `History::clear()`. + self.backend.versions()?.for_each(|(root, v)| { + if version >= v { + self.histories + .get_mut(&root) + .expect( + "A full tree did not have a corresponding history, but is required + to", + ) + .clear(); + self.non_empty_histories.remove(&root); + } + }); + + // The other case is `v < c`, which is handled simply by the truncation mechanism in the + // history as we want. In other words, it retains the necessary delta, and so we can just + // call it here. + self.non_empty_histories.iter().for_each(|h| { + self.histories + .get_mut(h) + .expect("Histories did not contain an entry corresponding to a tree") + .truncate(version); + }); + + Ok(()) + } +} diff --git a/miden-crypto/src/merkle/smt/large_forest/operation.rs b/miden-crypto/src/merkle/smt/large_forest/operation.rs new file mode 100644 index 000000000..fb9140c22 --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/operation.rs @@ -0,0 +1,265 @@ +//! This module contains the definition of the [`ForestOperation`] type that encapsulates the +//! possible modifications made to a tree, as well as the concept of a [`SmtUpdateBatch`] of +//! operations to be performed on a single tree in the forest. This is then extended to +//! [`SmtForestUpdateBatch`], which defines a batch of operations across multiple trees. + +use alloc::vec::Vec; + +use crate::{Map, Set, Word}; + +// FOREST OPERATION +// ================================================================================================ + +/// The operations that can be performed on an arbitrary leaf in a tree in a forest. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ForestOperation { + /// An insertion of `value` under `key` into the tree. + /// + /// If `key` already exists in the tree, the associated value will be replaced with `value` + /// instead. + Insert { key: Word, value: Word }, + + /// The removal of the `key` and its associated value from the tree. + Remove { key: Word }, +} +impl ForestOperation { + /// Insert the provided `value` into a tree under the provided `key`. + pub fn insert(key: Word, value: Word) -> Self { + Self::Insert { key, value } + } + + /// Remove the provided `key` and its associated value from a tree. + pub fn remove(key: Word) -> Self { + Self::Remove { key } + } + + /// Retrieves the key from the operation. + pub fn key(&self) -> Word { + match self { + ForestOperation::Insert { key, .. } => *key, + ForestOperation::Remove { key } => *key, + } + } +} + +// TREE BATCH +// ================================================================================================ + +/// A batch of operations that can be performed on an arbitrary tree in a forest. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SmtUpdateBatch { + /// The operations to be performed on a tree. + operations: Vec, +} +impl SmtUpdateBatch { + /// Creates an empty batch of operations that, when applied, will produce a tree with the + /// provided `version` when applied. + pub fn empty() -> Self { + Self { operations: vec![] } + } + + /// Creates a batch containing the provided `operations` that will produce a tree with the + /// provided `version` when applied. + pub fn new(operations: impl Iterator) -> Self { + Self { + operations: operations.collect::>(), + } + } + + /// Adds the provided `operations` to the batch. + pub fn add_operations(&mut self, operations: impl Iterator) { + self.operations.extend(operations); + } + + /// Adds the [`ForestOperation::Insert`] operation for the provided `key` and `value` pair to + /// the batch. + pub fn add_insert(&mut self, key: Word, value: Word) { + self.operations.push(ForestOperation::insert(key, value)); + } + + /// Adds the [`ForestOperation::Remove`] operation for the provided `key` to the batch. + pub fn add_remove(&mut self, key: Word) { + self.operations.push(ForestOperation::remove(key)); + } + + /// Consumes the batch as a vector of operations, containing the last operation for any given + /// `key` in the case that multiple operations per key are encountered. + /// + /// This vector is guaranteed to be sorted by the key on which an operation is performed. + pub fn consume(self) -> Vec { + // As we want to keep the LAST operation for each key, rather than the first, we filter in + // reverse. + let mut seen_keys: Set = Set::new(); + let mut ops = self + .operations + .into_iter() + .rev() + .filter(|o| seen_keys.insert(o.key())) + .collect::>(); + ops.sort_by_key(|o| o.key()); + ops + } +} + +impl From for SmtUpdateBatch +where + I: Iterator, +{ + fn from(value: I) -> Self { + Self::new(value) + } +} + +impl From for Vec { + /// The vector is guaranteed to be sorted by the key on which an operation is performed, and to + /// only contain the _last_ operation to be performed on any given key. + fn from(value: SmtUpdateBatch) -> Self { + value.consume() + } +} + +impl Default for SmtUpdateBatch { + fn default() -> Self { + Self::empty() + } +} + +// FOREST BATCH +// ================================================================================================ + +/// A batch of operations that can be performed on an arbitrary forest, consisting of operations +/// associated with specified trees in that forest. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SmtForestUpdateBatch { + /// The operations associated with each targeted tree in the forest. + operations: Map, +} + +impl SmtForestUpdateBatch { + /// Creates a new, empty, batch of operations. + pub fn empty() -> Self { + Self { operations: Map::new() } + } + + /// Adds the provided `operations` to be performed on the tree with the provided `root`. + pub fn add_operations( + &mut self, + root: Word, + operations: impl Iterator, + ) { + let batch = self.operations.entry(root).or_insert_with(SmtUpdateBatch::empty); + batch.add_operations(operations); + } + + /// Gets the batch of operations for the tree with the provided `root` for inspection and/or + /// modification. + /// + /// It is assumed that calling this means that the caller wants to insert operations into the + /// associated batch, so a batch will be created even if one was not previously present. + pub fn operations(&mut self, root: Word) -> &mut SmtUpdateBatch { + self.operations.entry(root).or_insert_with(SmtUpdateBatch::empty) + } + + /// Consumes the batch as a map of batches, with each individual batch guaranteed to be in + /// sorted order and contain only the last operation in the batch for any given key. + pub fn consume(self) -> Map> { + self.operations.into_iter().map(|(k, v)| (k, v.consume())).collect() + } +} + +// TESTS +// ================================================================================================ + +#[cfg(feature = "std")] +#[cfg(test)] +mod test { + use itertools::Itertools; + + use super::*; + use crate::rand::test_utils::rand_value; + + #[test] + fn tree_batch() { + // We start by creating an empty tree batch. + let mut batch = SmtUpdateBatch::empty(); + + // Let's make three operations on different keys... + let o1_key: Word = rand_value(); + let o1_value: Word = rand_value(); + let o2_key: Word = rand_value(); + let o3_key: Word = rand_value(); + let o3_value: Word = rand_value(); + + let o1 = ForestOperation::insert(o1_key, o1_value); + let o2 = ForestOperation::remove(o2_key); + let o3 = ForestOperation::insert(o3_key, o3_value); + + // ... and stick them in the batch in various ways + batch.add_operations(vec![o1.clone()].into_iter()); + batch.add_remove(o2_key); + batch.add_insert(o3_key, o3_value); + + // We save a copy of the batch for later as we have more testing to do. + let batch_tmp = batch.clone(); + + // If we then consume the batch, we should have the operations ordered by their key. + let ops = batch.consume(); + assert!(ops.is_sorted_by_key(|o| o.key())); + + // Let's now make two additional operations with keys that overlay with keys from the first + // three... + let o4_key = o2_key; + let o4_value: Word = rand_value(); + let o5_key = o1_key; + + let o4 = ForestOperation::insert(o4_key, o4_value); + let o5 = ForestOperation::remove(o5_key); + + // ... and also stick them into the batch. + let mut batch = batch_tmp; + batch.add_operations(vec![o4.clone(), o5.clone()].into_iter()); + + // Now if we consume the batch we should have three operations, and they should be the last + // operation for each key. + let ops = batch.consume(); + + assert_eq!(ops.len(), 3); + assert!(ops.is_sorted_by_key(|o| o.key())); + + assert!(ops.contains(&o3)); + assert!(ops.contains(&o4)); + assert!(!ops.contains(&o2)); + assert!(ops.contains(&o5)); + assert!(!ops.contains(&o1)); + } + + #[test] + fn forest_batch() { + // We can start by creating an empty forest batch. + let mut batch = SmtForestUpdateBatch::empty(); + + // Let's start by adding a few operations to a tree. + let t1_root: Word = rand_value(); + let t1_o1 = ForestOperation::insert(rand_value(), rand_value()); + let t1_o2 = ForestOperation::remove(rand_value()); + batch.add_operations(t1_root, vec![t1_o1, t1_o2].into_iter()); + + // We can also add them differently. + let t2_root: Word = rand_value(); + let t2_o1 = ForestOperation::remove(rand_value()); + let t2_o2 = ForestOperation::insert(rand_value(), rand_value()); + batch.operations(t2_root).add_operations(vec![t2_o1, t2_o2].into_iter()); + + // When we consume the batch, each per-tree batch should be unique by key and sorted. + let ops = batch.consume(); + assert_eq!(ops.len(), 2); + + let t1_ops = ops.get(&t1_root).unwrap(); + assert!(t1_ops.is_sorted_by_key(|o| o.key())); + assert_eq!(t1_ops.iter().unique_by(|o| o.key()).count(), 2); + + let t2_ops = ops.get(&t2_root).unwrap(); + assert!(t2_ops.is_sorted_by_key(|o| o.key())); + assert_eq!(t2_ops.iter().unique_by(|o| o.key()).count(), 2); + } +} diff --git a/miden-crypto/src/merkle/smt/large_forest/property_tests.rs b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs new file mode 100644 index 000000000..b59f0e435 --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/property_tests.rs @@ -0,0 +1 @@ +//! This module contains the property tests for the SMT forest. diff --git a/miden-crypto/src/merkle/smt/large_forest/root.rs b/miden-crypto/src/merkle/smt/large_forest/root.rs new file mode 100644 index 000000000..c007e0fb8 --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/root.rs @@ -0,0 +1,21 @@ +//! This module contains utility types for working with roots as part of the forest. + +use crate::merkle::smt::large_forest::history::VersionId; + +/// Information about the role that a queried root plays in the forest. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RootInfo { + /// The queried root corresponds to a tree that is the latest version of a given tree in the + /// forest. + LatestVersion(VersionId), + + /// The queried root corresponds to a tree that is _not_ the latest version of a given tree in + /// the forest. + HistoricalVersion(VersionId), + + /// The queried root corresponds to the empty tree. + EmptyTree, + + /// The queried root does not belong to any tree that the forest knows about. + Missing, +} diff --git a/miden-crypto/src/merkle/smt/large_forest/tests.rs b/miden-crypto/src/merkle/smt/large_forest/tests.rs new file mode 100644 index 000000000..49c284ebd --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/tests.rs @@ -0,0 +1 @@ +//! This module contains the handwritten tests for the SMT forest. diff --git a/miden-crypto/src/merkle/smt/mod.rs b/miden-crypto/src/merkle/smt/mod.rs index 6c57c95cb..ce912010e 100644 --- a/miden-crypto/src/merkle/smt/mod.rs +++ b/miden-crypto/src/merkle/smt/mod.rs @@ -29,7 +29,10 @@ pub use large::{ pub use large::{RocksDbConfig, RocksDbStorage}; mod large_forest; -pub use large_forest::{History, HistoryError, HistoryView, LargeSmtForestError}; +pub use large_forest::{ + Backend, BackendError, ForestOperation, LargeSmtForest, LargeSmtForestError, RootInfo, + SmtForestUpdateBatch, SmtUpdateBatch, +}; mod simple; pub use simple::{SimpleSmt, SimpleSmtProof}; @@ -758,8 +761,8 @@ impl De ); let num_new_pairs = source.read_usize()?; - let new_pairs = source.read_many(num_new_pairs)?; - let new_pairs = Map::from_iter(new_pairs); + let new_pairs: Map<_, _> = + source.read_many_iter(num_new_pairs)?.collect::>()?; Ok(Self { old_root, diff --git a/miden-crypto/src/merkle/smt/partial/mod.rs b/miden-crypto/src/merkle/smt/partial/mod.rs index bcbfb7289..7a0b02a78 100644 --- a/miden-crypto/src/merkle/smt/partial/mod.rs +++ b/miden-crypto/src/merkle/smt/partial/mod.rs @@ -1,3 +1,5 @@ +use p3_field::PrimeField64; + use super::{EmptySubtreeRoots, LeafIndex, SMT_DEPTH}; use crate::{ EMPTY_WORD, Word, @@ -8,7 +10,7 @@ use crate::{ utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests; /// A partial version of an [`super::Smt`]. @@ -398,7 +400,7 @@ impl PartialSmt { /// Converts a key to a leaf index. fn key_to_leaf_index(key: &Word) -> LeafIndex { let most_significant_felt = key[3]; - LeafIndex::new_max_depth(most_significant_felt.as_int()) + LeafIndex::new_max_depth(most_significant_felt.as_canonical_u64()) } /// Returns the inner node at the specified index, or `None` if not stored. diff --git a/miden-crypto/src/merkle/smt/partial/tests.rs b/miden-crypto/src/merkle/smt/partial/tests.rs index 602450f6f..e13f56249 100644 --- a/miden-crypto/src/merkle/smt/partial/tests.rs +++ b/miden-crypto/src/merkle/smt/partial/tests.rs @@ -1,10 +1,12 @@ use alloc::collections::{BTreeMap, BTreeSet}; use assert_matches::assert_matches; +use p3_field::PrimeField64; +use proptest::prelude::*; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; use super::{PartialSmt, SMT_DEPTH}; -#[cfg(any(test, feature = "std"))] -use crate::rand::test_utils::{rand_array, rand_value}; use crate::{ EMPTY_WORD, Felt, ONE, Word, ZERO, merkle::{ @@ -14,12 +16,27 @@ use crate::{ utils::{Deserializable, Serializable}, }; +// Note: Word's Arbitrary implementation is in word/mod.rs, gated by cfg(any(test, feature = +// "testing")) + +/// Helper to generate a random Word from a seeded RNG. +/// This is used for deterministic tests that need reproducible sequences of random values. +fn random_word(rng: &mut R) -> Word { + Word::new([ + Felt::new(rng.random::() % Felt::ORDER_U64), + Felt::new(rng.random::() % Felt::ORDER_U64), + Felt::new(rng.random::() % Felt::ORDER_U64), + Felt::new(rng.random::() % Felt::ORDER_U64), + ]) +} + /// Tests that a partial SMT constructed from a root is well behaved and returns expected /// values. #[test] fn partial_smt_new_with_no_entries() { - let key0 = Word::from(rand_array::()); - let value0 = Word::from(rand_array::()); + let mut rng = ChaCha20Rng::from_seed([1u8; 32]); + let key0 = random_word(&mut rng); + let value0 = random_word(&mut rng); let full = Smt::with_entries([(key0, value0)]).unwrap(); let partial_smt = PartialSmt::new(full.root()); @@ -35,8 +52,9 @@ fn partial_smt_new_with_no_entries() { /// Tests that a PartialSmt with a non-empty root but no proofs cannot track or update keys. #[test] fn partial_smt_non_empty_root_no_proofs() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([2u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let full = Smt::with_entries([(key, value)]).unwrap(); // Create partial with non-empty root but don't add any proofs @@ -49,7 +67,7 @@ fn partial_smt_non_empty_root_no_proofs() { assert!(partial.insert(key, value).is_err()); // Can't get value for empty key either - still not trackable - let empty_key: Word = rand_value(); + let empty_key = random_word(&mut rng); assert!(partial.get_value(&empty_key).is_err()); // Can't insert at empty key - not trackable @@ -61,23 +79,24 @@ fn partial_smt_non_empty_root_no_proofs() { /// equivalent update in the full tree. #[test] fn partial_smt_insert_and_remove() { - let key0 = Word::from(rand_array::()); - let key1 = Word::from(rand_array::()); - let key2 = Word::from(rand_array::()); + let mut rng = ChaCha20Rng::from_seed([3u8; 32]); + let key0 = random_word(&mut rng); + let key1 = random_word(&mut rng); + let key2 = random_word(&mut rng); // A key for which we won't add a value so it will be empty. - let key_empty = Word::from(rand_array::()); + let key_empty = random_word(&mut rng); - let value0 = Word::from(rand_array::()); - let value1 = Word::from(rand_array::()); - let value2 = Word::from(rand_array::()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); + let value2 = random_word(&mut rng); let mut kv_pairs = vec![(key0, value0), (key1, value1), (key2, value2)]; // Add more random leaves. kv_pairs.reserve(1000); for _ in 0..1000 { - let key = Word::from(rand_array::()); - let value = Word::from(rand_array::()); + let key = random_word(&mut rng); + let value = random_word(&mut rng); kv_pairs.push((key, value)); } @@ -103,10 +122,10 @@ fn partial_smt_insert_and_remove() { // Insert new values for added keys with empty and non-empty values. // ---------------------------------------------------------------------------------------- - let new_value0 = Word::from(rand_array::()); - let new_value2 = Word::from(rand_array::()); + let new_value0 = random_word(&mut rng); + let new_value2 = random_word(&mut rng); // A non-empty value for the key that was previously empty. - let new_value_empty_key = Word::from(rand_array::()); + let new_value_empty_key = random_word(&mut rng); full.insert(key0, new_value0).unwrap(); full.insert(key2, new_value2).unwrap(); @@ -142,7 +161,7 @@ fn partial_smt_insert_and_remove() { // Attempting to update a key whose merkle path was not added is an error. // ---------------------------------------------------------------------------------------- - let error = partial.clone().insert(key1, Word::from(rand_array::())).unwrap_err(); + let error = partial.clone().insert(key1, random_word(&mut rng)).unwrap_err(); assert_matches!(error, MerkleError::UntrackedKey(_)); let error = partial.insert(key1, EMPTY_WORD).unwrap_err(); @@ -152,14 +171,15 @@ fn partial_smt_insert_and_remove() { /// Test that we can add an SmtLeaf::Multiple variant to a partial SMT. #[test] fn partial_smt_multiple_leaf_success() { + let mut rng = ChaCha20Rng::from_seed([4u8; 32]); // key0 and key1 have the same felt at index 3 so they will be placed in the same leaf. let key0 = Word::from([ZERO, ZERO, ZERO, ONE]); let key1 = Word::from([ONE, ONE, ONE, ONE]); - let key2 = Word::from(rand_array::()); + let key2 = random_word(&mut rng); - let value0 = Word::from(rand_array::()); - let value1 = Word::from(rand_array::()); - let value2 = Word::from(rand_array::()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); + let value2 = random_word(&mut rng); let full = Smt::with_entries([(key0, value0), (key1, value1), (key2, value2)]).unwrap(); @@ -187,12 +207,13 @@ fn partial_smt_multiple_leaf_success() { /// This test uses only empty values in the partial SMT. #[test] fn partial_smt_root_mismatch_on_empty_values() { - let key0 = Word::from(rand_array::()); - let key1 = Word::from(rand_array::()); - let key2 = Word::from(rand_array::()); + let mut rng = ChaCha20Rng::from_seed([5u8; 32]); + let key0 = random_word(&mut rng); + let key1 = random_word(&mut rng); + let key2 = random_word(&mut rng); let value0 = EMPTY_WORD; - let value1 = Word::from(rand_array::()); + let value1 = random_word(&mut rng); let value2 = EMPTY_WORD; let kv_pairs = vec![(key0, value0)]; @@ -220,13 +241,14 @@ fn partial_smt_root_mismatch_on_empty_values() { /// This test uses only non-empty values in the partial SMT. #[test] fn partial_smt_root_mismatch_on_non_empty_values() { - let key0 = Word::new(rand_array()); - let key1 = Word::new(rand_array()); - let key2 = Word::new(rand_array()); + let mut rng = ChaCha20Rng::from_seed([6u8; 32]); + let key0 = random_word(&mut rng); + let key1 = random_word(&mut rng); + let key2 = random_word(&mut rng); - let value0 = Word::new(rand_array()); - let value1 = Word::new(rand_array()); - let value2 = Word::new(rand_array()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); + let value2 = random_word(&mut rng); let kv_pairs = vec![(key0, value0), (key1, value1)]; @@ -249,11 +271,12 @@ fn partial_smt_root_mismatch_on_non_empty_values() { /// Tests that from_proofs fails when the proofs roots do not match. #[test] fn partial_smt_from_proofs_fails_on_root_mismatch() { - let key0 = Word::new(rand_array()); - let key1 = Word::new(rand_array()); + let mut rng = ChaCha20Rng::from_seed([7u8; 32]); + let key0 = random_word(&mut rng); + let key1 = random_word(&mut rng); - let value0 = Word::new(rand_array()); - let value1 = Word::new(rand_array()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); let mut full = Smt::with_entries([(key0, value0)]).unwrap(); @@ -271,23 +294,24 @@ fn partial_smt_from_proofs_fails_on_root_mismatch() { /// Tests that a basic PartialSmt's iterator APIs return the expected values. #[test] fn partial_smt_iterator_apis() { - let key0 = Word::new(rand_array()); - let key1 = Word::new(rand_array()); - let key2 = Word::new(rand_array()); + let mut rng = ChaCha20Rng::from_seed([8u8; 32]); + let key0 = random_word(&mut rng); + let key1 = random_word(&mut rng); + let key2 = random_word(&mut rng); // A key for which we won't add a value so it will be empty. - let key_empty = Word::new(rand_array()); + let key_empty = random_word(&mut rng); - let value0 = Word::new(rand_array()); - let value1 = Word::new(rand_array()); - let value2 = Word::new(rand_array()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); + let value2 = random_word(&mut rng); let mut kv_pairs = vec![(key0, value0), (key1, value1), (key2, value2)]; // Add more random leaves. kv_pairs.reserve(1000); for _ in 0..1000 { - let key = Word::new(rand_array()); - let value = Word::new(rand_array()); + let key = random_word(&mut rng); + let value = random_word(&mut rng); kv_pairs.push((key, value)); } @@ -365,21 +389,23 @@ fn partial_smt_tracks_leaves() { /// `PartialSmt` serde round-trip when constructed from just a root. #[test] fn partial_smt_with_empty_leaves_serialization_roundtrip() { - let partial_smt = PartialSmt::new(rand_value()); + let mut rng = ChaCha20Rng::from_seed([9u8; 32]); + let partial_smt = PartialSmt::new(random_word(&mut rng)); assert_eq!(partial_smt, PartialSmt::read_from_bytes(&partial_smt.to_bytes()).unwrap()); } /// `PartialSmt` serde round-trip. Also tests conversion from SMT. #[test] fn partial_smt_serialization_roundtrip() { - let key = rand_value(); - let val = rand_value(); + let mut rng = ChaCha20Rng::from_seed([10u8; 32]); + let key = random_word(&mut rng); + let val = random_word(&mut rng); - let key_1 = rand_value(); - let val_1 = rand_value(); + let key_1 = random_word(&mut rng); + let val_1 = random_word(&mut rng); - let key_2 = rand_value(); - let val_2 = rand_value(); + let key_2 = random_word(&mut rng); + let val_2 = random_word(&mut rng); let smt: Smt = Smt::with_entries([(key, val), (key_1, val_1), (key_2, val_2)]).unwrap(); @@ -400,13 +426,14 @@ fn partial_smt_serialization_roundtrip() { /// Note that decreasing counts are not possible with the current API. #[test] fn partial_smt_add_proof_num_entries() { + let mut rng = ChaCha20Rng::from_seed([11u8; 32]); // key0 and key1 have the same felt at index 3 so they will be placed in the same leaf. let key0 = Word::from([ZERO, ZERO, ZERO, ONE]); let key1 = Word::from([ONE, ONE, ONE, ONE]); let key2 = Word::from([ONE, ONE, ONE, Felt::new(5)]); - let value0 = Word::from(rand_array::()); - let value1 = Word::from(rand_array::()); - let value2 = Word::from(rand_array::()); + let value0 = random_word(&mut rng); + let value1 = random_word(&mut rng); + let value2 = random_word(&mut rng); let full = Smt::with_entries([(key0, value0), (key1, value1), (key2, value2)]).unwrap(); let mut partial = PartialSmt::new(full.root()); @@ -468,8 +495,12 @@ fn partial_smt_tracking_visualization() { let key_6 = Word::from([ZERO, ZERO, ZERO, Felt::new(LEAF_6)]); let key_7 = Word::from([ZERO, ZERO, ZERO, Felt::new(LEAF_7)]); + let mut rng = ChaCha20Rng::from_seed([12u8; 32]); + // Create full SMT with keys 1 and 3 (key_3 makes node b non-empty) - let mut full = Smt::with_entries([(key_1, rand_value()), (key_3, rand_value())]).unwrap(); + let mut full = + Smt::with_entries([(key_1, random_word(&mut rng)), (key_3, random_word(&mut rng))]) + .unwrap(); // Create partial SMT with ONLY the proof for key 1 let proof_1 = full.open(&key_1); @@ -477,19 +508,19 @@ fn partial_smt_tracking_visualization() { assert_eq!(full.root(), partial.root()); // Key 1: CAN update (explicitly tracked via proof) - let new_value_1: Word = rand_value(); + let new_value_1 = random_word(&mut rng); full.insert(key_1, new_value_1).unwrap(); partial.insert(key_1, new_value_1).unwrap(); assert_eq!(full.root(), partial.root()); // Key 0: CAN update (under same parent 'a' as key 1, empty) - let value_0: Word = rand_value(); + let value_0 = random_word(&mut rng); full.insert(key_0, value_0).unwrap(); partial.insert(key_0, value_0).unwrap(); assert_eq!(full.root(), partial.root()); // Key 4: CAN update (in empty subtree f) - let value_4: Word = rand_value(); + let value_4 = random_word(&mut rng); full.insert(key_4, value_4).unwrap(); partial.insert(key_4, value_4).unwrap(); assert_eq!(full.root(), partial.root()); @@ -498,29 +529,29 @@ fn partial_smt_tracking_visualization() { // remain trackable through the inner nodes created by previous inserts. // Key 5: CAN update - let value_5: Word = rand_value(); + let value_5 = random_word(&mut rng); full.insert(key_5, value_5).unwrap(); partial.insert(key_5, value_5).unwrap(); assert_eq!(full.root(), partial.root()); // Key 6: CAN update - let value_6: Word = rand_value(); + let value_6 = random_word(&mut rng); full.insert(key_6, value_6).unwrap(); partial.insert(key_6, value_6).unwrap(); assert_eq!(full.root(), partial.root()); // Key 7: CAN update - let value_7: Word = rand_value(); + let value_7 = random_word(&mut rng); full.insert(key_7, value_7).unwrap(); partial.insert(key_7, value_7).unwrap(); assert_eq!(full.root(), partial.root()); // Key 2: CANNOT update (under non-empty node b, only have its hash) - let result = partial.insert(key_2, rand_value()); + let result = partial.insert(key_2, random_word(&mut rng)); assert_matches!(result, Err(MerkleError::UntrackedKey(_))); // Key 3: CANNOT update (has data but no proof in partial SMT) - let result = partial.insert(key_3, rand_value()); + let result = partial.insert(key_3, random_word(&mut rng)); assert_matches!(result, Err(MerkleError::UntrackedKey(_))); // Verify roots still match (failed inserts should not modify partial SMT) @@ -529,11 +560,12 @@ fn partial_smt_tracking_visualization() { #[test] fn partial_smt_implicit_empty_tree() { + let mut rng = ChaCha20Rng::from_seed([13u8; 32]); let mut full = Smt::new(); let mut partial = PartialSmt::new(full.root()); - let key: Word = rand_value(); - let value: Word = rand_value(); + let key = random_word(&mut rng); + let value = random_word(&mut rng); full.insert(key, value).unwrap(); // Can insert into empty partial SMT (implicitly tracked) @@ -545,11 +577,12 @@ fn partial_smt_implicit_empty_tree() { #[test] fn partial_smt_implicit_insert_and_remove() { + let mut rng = ChaCha20Rng::from_seed([14u8; 32]); let mut full = Smt::new(); let mut partial = PartialSmt::new(full.root()); - let key: Word = rand_value(); - let value: Word = rand_value(); + let key = random_word(&mut rng); + let value = random_word(&mut rng); // Insert into implicitly tracked leaf full.insert(key, value).unwrap(); @@ -569,8 +602,9 @@ fn partial_smt_implicit_insert_and_remove() { /// Tests that deserialization fails when an inner node hash is inconsistent with its parent. #[test] fn partial_smt_deserialize_invalid_inner_node() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([15u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let smt = Smt::with_entries([(key, value)]).unwrap(); let proof = smt.open(&key); @@ -593,8 +627,9 @@ fn partial_smt_deserialize_invalid_inner_node() { /// node. #[test] fn partial_smt_deserialize_invalid_leaf() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([16u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let smt = Smt::with_entries([(key, value)]).unwrap(); let proof = smt.open(&key); @@ -620,8 +655,9 @@ fn partial_smt_deserialize_invalid_leaf() { /// Tests that deserialization fails when the root is inconsistent with the inner nodes. #[test] fn partial_smt_deserialize_invalid_root() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([17u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let smt = Smt::with_entries([(key, value)]).unwrap(); let proof = smt.open(&key); @@ -639,8 +675,9 @@ fn partial_smt_deserialize_invalid_root() { /// Tests that deserialization fails when leaves count is tampered to be smaller. #[test] fn partial_smt_deserialize_leaves_count_smaller() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([18u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let smt = Smt::with_entries([(key, value)]).unwrap(); let proof = smt.open(&key); @@ -664,8 +701,9 @@ fn partial_smt_deserialize_leaves_count_smaller() { /// Tests that deserialization fails when leaves count is tampered to be larger. #[test] fn partial_smt_deserialize_leaves_count_larger() { - let key: Word = rand_value(); - let value: Word = rand_value(); + let mut rng = ChaCha20Rng::from_seed([19u8; 32]); + let key = random_word(&mut rng); + let value = random_word(&mut rng); let smt = Smt::with_entries([(key, value)]).unwrap(); let proof = smt.open(&key); @@ -685,3 +723,35 @@ fn partial_smt_deserialize_leaves_count_larger() { let result = PartialSmt::read_from_bytes(&bytes); assert!(result.is_err()); } + +// PROPTEST-BASED TESTS +// ================================================================================================ +// These tests use proptest's Arbitrary trait, which is no_std compatible with the `alloc` feature. + +proptest! { + /// Property test: inserting a value into an empty partial SMT and then reading it back + /// should return the same value. + #[test] + fn prop_partial_smt_insert_roundtrip(key: Word, value: Word) { + // Skip empty values as they have special semantics + prop_assume!(value != EMPTY_WORD); + + let mut full = Smt::new(); + let mut partial = PartialSmt::new(full.root()); + + full.insert(key, value).unwrap(); + partial.insert(key, value).unwrap(); + + prop_assert_eq!(full.root(), partial.root()); + prop_assert_eq!(partial.get_value(&key).unwrap(), value); + } + + /// Property test: serialization roundtrip for partial SMT constructed from an empty tree. + #[test] + fn prop_partial_smt_empty_serialization_roundtrip(root: Word) { + let partial_smt = PartialSmt::new(root); + let bytes = partial_smt.to_bytes(); + let decoded = PartialSmt::read_from_bytes(&bytes).unwrap(); + prop_assert_eq!(partial_smt, decoded); + } +} diff --git a/miden-crypto/src/merkle/sparse_path.rs b/miden-crypto/src/merkle/sparse_path.rs index 915f92801..7dc60fdcb 100644 --- a/miden-crypto/src/merkle/sparse_path.rs +++ b/miden-crypto/src/merkle/sparse_path.rs @@ -272,7 +272,7 @@ impl Deserializable for SparseMerklePath { ))); } let count = depth as u32 - empty_nodes_count; - let nodes = source.read_many::(count as usize)?; + let nodes: Vec = source.read_many_iter(count as usize)?.collect::>()?; Ok(Self { empty_nodes_mask, nodes }) } } @@ -637,26 +637,6 @@ mod tests { use proptest::prelude::*; - // Arbitrary instance for Word - impl Arbitrary for Word { - type Parameters = (); - type Strategy = BoxedStrategy; - - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - prop::collection::vec(any::(), 4) - .prop_map(|vals| { - Word::new([ - Felt::new(vals[0]), - Felt::new(vals[1]), - Felt::new(vals[2]), - Felt::new(vals[3]), - ]) - }) - .no_shrink() - .boxed() - } - } - // Arbitrary instance for MerklePath impl Arbitrary for MerklePath { type Parameters = (); diff --git a/miden-crypto/src/rand/test_utils.rs b/miden-crypto/src/rand/test_utils.rs index 998f46bb6..137e1f002 100644 --- a/miden-crypto/src/rand/test_utils.rs +++ b/miden-crypto/src/rand/test_utils.rs @@ -3,14 +3,48 @@ //! This module provides helper functions for tests and benchmarks that need //! random data generation. These functions replace the functionality previously //! provided by winter-rand-utils. +//! +//! # no_std Compatibility +//! +//! This module provides both `std`-dependent and `no_std`-compatible functions: +//! +//! - **`std` required**: [`rand_value`], [`rand_array`], [`rand_vector`] use the thread-local RNG +//! and require the `std` feature. +//! - **`no_std` compatible**: [`seeded_rng`], [`prng_array`], [`prng_vector`] use deterministic +//! seeded PRNGs and work in `no_std` environments. +//! +//! For tests that should run in `no_std` mode, prefer using [`seeded_rng`] to obtain +//! a deterministic RNG instead of `rand::rng()`. -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; use crate::rand::Randomizable; +/// Creates a deterministic seeded RNG suitable for tests. +/// +/// This function returns a ChaCha20 PRNG seeded with the provided seed, providing +/// deterministic random number generation that works in `no_std` environments. +/// +/// # Examples +/// ``` +/// # use miden_crypto::rand::test_utils::seeded_rng; +/// let mut rng = seeded_rng([0u8; 32]); +/// // Use rng with any function that accepts impl RngCore +/// ``` +pub fn seeded_rng(seed: [u8; 32]) -> ChaCha20Rng { + ChaCha20Rng::from_seed(seed) +} + +/// Generates a random value of type T from an RNG. +fn rng_value(rng: &mut impl Rng) -> T { + let mut bytes = vec![0u8; T::VALUE_SIZE]; + rng.fill(&mut bytes[..]); + T::from_random_bytes(&bytes).expect("failed to generate random value") +} + /// Generates a random value of type T using the thread-local random number generator. /// /// # Examples @@ -21,10 +55,7 @@ use crate::rand::Randomizable; /// ``` #[cfg(feature = "std")] pub fn rand_value() -> T { - let mut rng = rand::rng(); - let mut bytes = vec![0u8; T::VALUE_SIZE]; - rng.fill(&mut bytes[..]); - T::from_random_bytes(&bytes).expect("failed to generate random value") + rng_value(&mut rand::rng()) } /// Generates a random array of type T with N elements. @@ -36,7 +67,8 @@ pub fn rand_value() -> T { /// ``` #[cfg(feature = "std")] pub fn rand_array() -> [T; N] { - core::array::from_fn(|_| rand_value()) + let mut rng = rand::rng(); + core::array::from_fn(|_| rng_value(&mut rng)) } /// Generates a random vector of type T with the specified length. @@ -48,27 +80,36 @@ pub fn rand_array() -> [T; N] { /// ``` #[cfg(feature = "std")] pub fn rand_vector(length: usize) -> Vec { - (0..length).map(|_| rand_value()).collect() + let mut rng = rand::rng(); + (0..length).map(|_| rng_value(&mut rng)).collect() } -/// Generates a deterministic array using a PRNG seeded with the provided seed. +/// Generates a deterministic value using a PRNG seeded with the provided seed. /// /// This function uses ChaCha20 PRNG for deterministic random generation, which is /// useful for reproducible tests and benchmarks. /// /// # Examples /// ``` +/// # use miden_crypto::rand::test_utils::prng_value; +/// let seed = [0u8; 32]; +/// let val: u64 = prng_value(seed); +/// ``` +pub fn prng_value(seed: [u8; 32]) -> T { + rng_value(&mut seeded_rng(seed)) +} + +/// Generates a deterministic array using a PRNG seeded with the provided seed. +/// +/// # Examples +/// ``` /// # use miden_crypto::rand::test_utils::prng_array; /// let seed = [0u8; 32]; /// let arr: [u64; 4] = prng_array(seed); /// ``` pub fn prng_array(seed: [u8; 32]) -> [T; N] { - let mut rng = ChaCha20Rng::from_seed(seed); - core::array::from_fn(|_| { - let mut bytes = vec![0u8; T::VALUE_SIZE]; - rng.fill(&mut bytes[..]); - T::from_random_bytes(&bytes).expect("failed to generate random value") - }) + let mut rng = seeded_rng(seed); + core::array::from_fn(|_| rng_value(&mut rng)) } /// Generates a deterministic vector using a PRNG seeded with the provided seed. @@ -80,12 +121,6 @@ pub fn prng_array(seed: [u8; 32]) -> [T; N] { /// let vec: Vec = prng_vector(seed, 100); /// ``` pub fn prng_vector(seed: [u8; 32], length: usize) -> Vec { - let mut rng = ChaCha20Rng::from_seed(seed); - (0..length) - .map(|_| { - let mut bytes = vec![0u8; T::VALUE_SIZE]; - rng.fill(&mut bytes[..]); - T::from_random_bytes(&bytes).expect("failed to generate random value") - }) - .collect() + let mut rng = seeded_rng(seed); + (0..length).map(|_| rng_value(&mut rng)).collect() } diff --git a/miden-crypto/src/utils/iterators.rs b/miden-crypto/src/utils/iterators.rs deleted file mode 100644 index 8ba3c5ed2..000000000 --- a/miden-crypto/src/utils/iterators.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree. - -//! Components needed for parallel iterators. -//! -//! When `concurrent` feature is enabled, this module re-exports `rayon::prelude`. Otherwise, -//! this is an empty module. - -#[cfg(feature = "concurrent")] -pub use rayon::prelude::*; - -/// Returns either a regular or a parallel iterator depending on whether `concurrent` feature -/// is enabled. -/// -/// When `concurrent` feature is enabled, creates a parallel iterator; otherwise, creates a -/// regular iterator. Optionally, `min_length` can be used to specify the minimum length of -/// iterator to be processed in each thread. -/// -/// Adapted from: -#[macro_export] -macro_rules! iter { - ($e:expr) => {{ - #[cfg(feature = "concurrent")] - let result = $e.par_iter(); - - #[cfg(not(feature = "concurrent"))] - let result = $e.iter(); - - result - }}; - ($e:expr, $min_len:expr) => {{ - #[cfg(feature = "concurrent")] - let result = $e.par_iter().with_min_len($min_len); - - #[cfg(not(feature = "concurrent"))] - let result = $e.iter(); - - result - }}; -} - -/// Returns either a regular or a parallel mutable iterator depending on whether `concurrent` -/// feature is enabled. -/// -/// When `concurrent` feature is enabled, creates a mutable parallel iterator; otherwise, -/// creates a regular mutable iterator. Optionally, `min_length` can be used to specify the -/// minimum length of iterator to be processed in each thread. -/// -/// Adapted from: -#[macro_export] -macro_rules! iter_mut { - ($e:expr) => {{ - #[cfg(feature = "concurrent")] - let result = $e.par_iter_mut(); - - #[cfg(not(feature = "concurrent"))] - let result = $e.iter_mut(); - - result - }}; - ($e:expr, $min_len:expr) => {{ - #[cfg(feature = "concurrent")] - let result = $e.par_iter_mut().with_min_len($min_len); - - #[cfg(not(feature = "concurrent"))] - let result = $e.iter_mut(); - - result - }}; -} - -/// Applies a procedure to the provided slice either in a single thread or multiple threads -/// based on whether `concurrent` feature is enabled. -/// -/// When `concurrent` feature is enabled, breaks the slice into batches and processes each -/// batch in a separate thread; otherwise, the entire slice is processed as a single batch -/// in one thread. Optionally, `min_batch_size` can be used to specify the minimum size of -/// the resulting batches. -#[macro_export] -macro_rules! batch_iter_mut { - ($e: expr, $c: expr) => { - #[cfg(feature = "concurrent")] - { - let batch_size = $e.len() / rayon::current_num_threads().next_power_of_two(); - if batch_size < 1 { - $c($e, 0); - } - else { - $e.par_chunks_mut(batch_size).enumerate().for_each(|(i, batch)| { - $c(batch, i * batch_size); - }); - } - } - - #[cfg(not(feature = "concurrent"))] - $c($e, 0); - }; - ($e: expr, $min_batch_size: expr, $c: expr) => { - #[cfg(feature = "concurrent")] - { - let batch_size = $e.len() / rayon::current_num_threads().next_power_of_two(); - if batch_size < $min_batch_size { - $c($e, 0); - } - else { - $e.par_chunks_mut(batch_size).enumerate().for_each(|(i, batch)| { - $c(batch, i * batch_size); - }); - } - } - - #[cfg(not(feature = "concurrent"))] - $c($e, 0); - }; -} diff --git a/miden-crypto/src/utils/mod.rs b/miden-crypto/src/utils/mod.rs index 45b8d3c01..410d060ee 100644 --- a/miden-crypto/src/utils/mod.rs +++ b/miden-crypto/src/utils/mod.rs @@ -1,10 +1,7 @@ //! Utilities used in this crate which can also be generally useful downstream. use alloc::{string::String, vec::Vec}; -use core::{ - fmt::{self, Write}, - mem, slice, -}; +use core::fmt::{self, Write}; // Re-export serialization traits from miden-serde-utils #[cfg(feature = "std")] @@ -13,12 +10,9 @@ pub use miden_serde_utils::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, }; use p3_field::{PrimeCharacteristicRing, RawDataSerializable, integers::QuotientMap}; +use p3_maybe_rayon::prelude::*; use thiserror::Error; -mod iterators; -#[cfg(feature = "concurrent")] -use iterators::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; - use crate::{Felt, Word, field::PrimeField64}; // CONSTANTS @@ -306,30 +300,27 @@ pub unsafe fn uninit_vector(length: usize) -> Vec { /// # Panics /// Panics if `n` is not divisible by `N`. pub fn group_slice_elements(source: &[T]) -> &[[T; N]] { - assert_eq!(source.len() % N, 0, "source length must be divisible by {N}"); - let p = source.as_ptr(); - let len = source.len() / N; - unsafe { slice::from_raw_parts(p as *const [T; N], len) } + let (chunks, remainder) = source.as_chunks::(); + assert!(remainder.is_empty(), "source length must be divisible by {N}"); + chunks } /// Transmutes a slice of `n` arrays each of length `N`, into a slice of `N` * `n` elements. /// /// This function just re-interprets the underlying memory and is thus zero-copy. pub fn flatten_slice_elements(source: &[[T; N]]) -> &[T] { - let p = source.as_ptr(); - let len = source.len() * N; - unsafe { slice::from_raw_parts(p as *const T, len) } + // SAFETY: [T; N] has the same alignment and memory layout as an array of T. + // p3-util's as_base_slice handles the conversion safely. + unsafe { p3_util::as_base_slice(source) } } /// Transmutes a vector of `n` arrays each of length `N`, into a vector of `N` * `n` elements. /// /// This function just re-interprets the underlying memory and is thus zero-copy. pub fn flatten_vector_elements(source: Vec<[T; N]>) -> Vec { - let v = mem::ManuallyDrop::new(source); - let p = v.as_ptr(); - let len = v.len() * N; - let cap = v.capacity() * N; - unsafe { Vec::from_raw_parts(p as *mut T, len, cap) } + // SAFETY: [T; N] has the same alignment and memory layout as an array of T. + // p3-util's flatten_to_base handles the conversion without reallocations. + unsafe { p3_util::flatten_to_base(source) } } // TRANSPOSING (ported from Winterfell's winter-utils) @@ -352,7 +343,7 @@ pub fn transpose_slice(source: &[T]) -> V ); let mut result: Vec<[T; N]> = unsafe { uninit_vector(row_count) }; - crate::iter_mut!(result, 1024).enumerate().for_each(|(i, element)| { + result.par_iter_mut().enumerate().for_each(|(i, element)| { for j in 0..N { element[j] = source[i + j * row_count] } diff --git a/miden-crypto/src/word/mod.rs b/miden-crypto/src/word/mod.rs index b802e409d..e6fc2f270 100644 --- a/miden-crypto/src/word/mod.rs +++ b/miden-crypto/src/word/mod.rs @@ -718,3 +718,32 @@ macro_rules! word { word }}; } + +// ARBITRARY (proptest) +// ================================================================================================ + +#[cfg(any(test, feature = "testing"))] +mod arbitrary { + use proptest::prelude::*; + + use super::{Felt, Word}; + + impl Arbitrary for Word { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop::collection::vec(any::(), 4) + .prop_map(|vals| { + Word::new([ + Felt::new(vals[0]), + Felt::new(vals[1]), + Felt::new(vals[2]), + Felt::new(vals[3]), + ]) + }) + .no_shrink() + .boxed() + } + } +} diff --git a/miden-serde-utils/Cargo.toml b/miden-serde-utils/Cargo.toml index 1ba1812e0..2c6d43750 100644 --- a/miden-serde-utils/Cargo.toml +++ b/miden-serde-utils/Cargo.toml @@ -13,19 +13,12 @@ rust-version.workspace = true version.workspace = true [features] -default = ["std"] -p3-compat = ["dep:p3-field", "dep:p3-miden-goldilocks"] -std = ["winter-math?/std", "winter-utils?/std"] -winter-compat = ["dep:winter-math", "dep:winter-utils"] +default = ["std"] +std = [] [dependencies] -p3-field = { default-features = false, optional = true, version = "0.4" } -p3-miden-goldilocks = { default-features = false, optional = true, version = "0.4" } -winter-math = { default-features = false, optional = true, version = "0.13" } -winter-utils = { default-features = false, optional = true, version = "0.13" } +p3-field = { default-features = false, version = "0.4.2" } +p3-goldilocks = { default-features = false, version = "0.4.2" } [lints] workspace = true - -[package.metadata.cargo-machete] -ignored = ["winter-math", "winter-utils"] diff --git a/miden-serde-utils/fuzz/Cargo.lock b/miden-serde-utils/fuzz/Cargo.lock new file mode 100644 index 000000000..a6e59a05e --- /dev/null +++ b/miden-serde-utils/fuzz/Cargo.lock @@ -0,0 +1,480 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "cc" +version = "1.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "miden-serde-utils" +version = "0.21.0" +dependencies = [ + "p3-field", + "p3-miden-goldilocks", +] + +[[package]] +name = "miden-serde-utils-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "miden-serde-utils", + "p3-miden-goldilocks", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "p3-challenger" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e42ba74a49c08c6e99f74cd9b343bfa31aa5721fea55079b18e3fd65f1dcbc" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-monty-31", + "p3-symmetric", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63fa5eb1bd12a240089e72ae3fe10350944d9c166d00a3bfd2a1794db65cf5c" +dependencies = [ + "itertools", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "spin", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebfdb6ef992ae64e9e8f449ac46516ffa584f11afbdf9ee244288c2a633cdf4" +dependencies = [ + "itertools", + "num-bigint", + "p3-maybe-rayon", + "p3-util", + "paste", + "rand", + "serde", + "tracing", +] + +[[package]] +name = "p3-matrix" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5542f96504dae8100c91398fb1e3f5ec669eb9c73d9e0b018a93b5fe32bad230" +dependencies = [ + "itertools", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e5669ca75645f99cd001e9d0289a4eeff2bc2cd9dc3c6c3aaf22643966e83df" + +[[package]] +name = "p3-mds" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038763af23df9da653065867fd85b38626079031576c86fd537097e5be6a0da0" +dependencies = [ + "p3-dft", + "p3-field", + "p3-symmetric", + "p3-util", + "rand", +] + +[[package]] +name = "p3-miden-goldilocks" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a883bc797df1c43b06f2d467340625ee736f748928d1ceb402d27eb05e694394" +dependencies = [ + "num-bigint", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand", + "serde", +] + +[[package]] +name = "p3-monty-31" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a981d60da3d8cbf8561014e2c186068578405fd69098fa75b43d4afb364a47" +dependencies = [ + "itertools", + "num-bigint", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand", + "serde", + "spin", + "tracing", + "transpose", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903b73e4f9a7781a18561c74dc169cf03333497b57a8dd02aaeb130c0f386599" +dependencies = [ + "p3-field", + "p3-mds", + "p3-symmetric", + "p3-util", + "rand", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd788f04e86dd5c35dd87cad29eefdb6371d2fd5f7664451382eeacae3c3ed0" +dependencies = [ + "itertools", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663b16021930bc600ecada915c6c3965730a3b9d6a6c23434ccf70bfc29d6881" +dependencies = [ + "serde", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/miden-serde-utils/fuzz/Cargo.toml b/miden-serde-utils/fuzz/Cargo.toml new file mode 100644 index 000000000..0214e841b --- /dev/null +++ b/miden-serde-utils/fuzz/Cargo.toml @@ -0,0 +1,59 @@ +[package] +edition = "2024" +name = "miden-serde-utils-fuzz" +publish = false +version = "0.0.0" + +[workspace] + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +p3-miden-goldilocks = "0.4" + +[dependencies.miden-serde-utils] +path = ".." + +[[bin]] +bench = false +doc = false +name = "primitives" +path = "fuzz_targets/primitives.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "collections" +path = "fuzz_targets/collections.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "string" +path = "fuzz_targets/string.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "vint64" +path = "fuzz_targets/vint64.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "goldilocks" +path = "fuzz_targets/goldilocks.rs" +test = false + +[[bin]] +bench = false +doc = false +name = "budgeted" +path = "fuzz_targets/budgeted.rs" +test = false diff --git a/miden-serde-utils/fuzz/fuzz_targets/budgeted.rs b/miden-serde-utils/fuzz/fuzz_targets/budgeted.rs new file mode 100644 index 000000000..5ec3a7ab8 --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/budgeted.rs @@ -0,0 +1,39 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::{BudgetedReader, ByteReader, Deserializable, SliceReader}; +use std::collections::{BTreeMap, BTreeSet}; + +fuzz_target!(|data: &[u8]| { + // Use a tight budget to stress-test budget enforcement. + // The budget is the min of data length and 1KB to keep things fast. + let budget = data.len().min(1024); + + // Test primitives with budget + let inner = SliceReader::new(data); + let mut reader = BudgetedReader::new(inner, budget); + let _ = reader.read_u8(); + let _ = reader.read_u16(); + let _ = reader.read_u32(); + let _ = reader.read_u64(); + + // Test collections with budget via the convenience method + let _ = Vec::::read_from_bytes_with_budget(data, budget); + let _ = Vec::::read_from_bytes_with_budget(data, budget); + let _ = BTreeMap::::read_from_bytes_with_budget(data, budget); + let _ = BTreeSet::::read_from_bytes_with_budget(data, budget); + let _ = String::read_from_bytes_with_budget(data, budget); + + // Test with zero budget (should fail immediately on any read) + let _ = Vec::::read_from_bytes_with_budget(data, 0); + let _ = u64::read_from_bytes_with_budget(data, 0); + + // Test with exact budget for known-size types + let _ = u32::read_from_bytes_with_budget(data, 4); + let _ = u64::read_from_bytes_with_budget(data, 8); + let _ = <[u8; 16]>::read_from_bytes_with_budget(data, 16); + + // Test nested structures + let _ = Option::>::read_from_bytes_with_budget(data, budget); + let _ = Vec::>::read_from_bytes_with_budget(data, budget); +}); diff --git a/miden-serde-utils/fuzz/fuzz_targets/collections.rs b/miden-serde-utils/fuzz/fuzz_targets/collections.rs new file mode 100644 index 000000000..87c7f3fd1 --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/collections.rs @@ -0,0 +1,37 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::Deserializable; +use std::collections::{BTreeMap, BTreeSet}; + +fuzz_target!(|data: &[u8]| { + // Test Vec with various element types - focus on length prefix handling + let _ = Vec::::read_from_bytes(data); + let _ = Vec::::read_from_bytes(data); + let _ = Vec::::read_from_bytes(data); + + // Test Option which has a bool discriminator + let _ = Option::::read_from_bytes(data); + let _ = Option::>::read_from_bytes(data); + + // Test BTreeMap and BTreeSet - these validate ordering and handle allocations + let _ = BTreeMap::::read_from_bytes(data); + let _ = BTreeMap::::read_from_bytes(data); + let _ = BTreeSet::::read_from_bytes(data); + + // Test fixed-size arrays - these should handle exact count requirements + let _ = <[u8; 1]>::read_from_bytes(data); + let _ = <[u8; 4]>::read_from_bytes(data); + let _ = <[u8; 16]>::read_from_bytes(data); + let _ = <[u8; 32]>::read_from_bytes(data); + let _ = <[u64; 2]>::read_from_bytes(data); + let _ = <[u64; 4]>::read_from_bytes(data); + + // Test tuples of various sizes + let _ = <(u8,)>::read_from_bytes(data); + let _ = <(u8, u16)>::read_from_bytes(data); + let _ = <(u8, u16, u32)>::read_from_bytes(data); + let _ = <(u8, u16, u32, u64)>::read_from_bytes(data); + let _ = <(u8, u16, u32, u64, u128)>::read_from_bytes(data); + let _ = <(u8, u16, u32, u64, u128, usize)>::read_from_bytes(data); +}); diff --git a/miden-serde-utils/fuzz/fuzz_targets/goldilocks.rs b/miden-serde-utils/fuzz/fuzz_targets/goldilocks.rs new file mode 100644 index 000000000..8a410835d --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/goldilocks.rs @@ -0,0 +1,26 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::Deserializable; + +fuzz_target!(|data: &[u8]| { + // Test Goldilocks field element deserialization + // The key validation: value must be < modulus (2^64 - 2^32 + 1) + // This is defined in p3_miden_goldilocks::Goldilocks::MODULUS + use p3_miden_goldilocks::Goldilocks; + + let _ = Goldilocks::read_from_bytes(data); + + // Also test via Vec of Goldilocks elements to test multiple reads + let _ = Vec::::read_from_bytes(data); + + // Test arrays of Goldilocks + let _ = <[Goldilocks; 1]>::read_from_bytes(data); + let _ = <[Goldilocks; 2]>::read_from_bytes(data); + let _ = <[Goldilocks; 4]>::read_from_bytes(data); + + // Test tuples containing Goldilocks + let _ = <(Goldilocks,)>::read_from_bytes(data); + let _ = <(Goldilocks, Goldilocks)>::read_from_bytes(data); + let _ = <(Goldilocks, u64, Goldilocks)>::read_from_bytes(data); +}); diff --git a/miden-serde-utils/fuzz/fuzz_targets/primitives.rs b/miden-serde-utils/fuzz/fuzz_targets/primitives.rs new file mode 100644 index 000000000..a9ccff88f --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/primitives.rs @@ -0,0 +1,29 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::{ByteReader, Deserializable, SliceReader}; + +fuzz_target!(|data: &[u8]| { + // Test all primitive deserializations with raw fuzz input + // Goal: ensure none of these panic, only return Ok or Err + let _ = u8::read_from_bytes(data); + let _ = u16::read_from_bytes(data); + let _ = u32::read_from_bytes(data); + let _ = u64::read_from_bytes(data); + let _ = u128::read_from_bytes(data); + let _ = usize::read_from_bytes(data); + + // Test read_bool which validates input is 0 or 1 + let mut reader = SliceReader::new(data); + let _ = reader.read_bool(); + + // Test peek_u8 which should handle empty input gracefully + let reader2 = SliceReader::new(data); + let _ = reader2.peek_u8(); + + // Test check_eor with various lengths + for len in 0..=16 { + let reader3 = SliceReader::new(data); + let _ = reader3.check_eor(len); + } +}); diff --git a/miden-serde-utils/fuzz/fuzz_targets/string.rs b/miden-serde-utils/fuzz/fuzz_targets/string.rs new file mode 100644 index 000000000..1e5a8eba3 --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/string.rs @@ -0,0 +1,8 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::Deserializable; + +fuzz_target!(|data: &[u8]| { + let _ = String::read_from_bytes(data); +}); diff --git a/miden-serde-utils/fuzz/fuzz_targets/vint64.rs b/miden-serde-utils/fuzz/fuzz_targets/vint64.rs new file mode 100644 index 000000000..732ac4f6b --- /dev/null +++ b/miden-serde-utils/fuzz/fuzz_targets/vint64.rs @@ -0,0 +1,30 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use miden_serde_utils::{ByteReader, Deserializable, SliceReader}; + +fuzz_target!(|data: &[u8]| { + // Test usize deserialization (vint64 encoding) + // The vint64 format has complex decoding: + // - Length is determined by trailing zeros in first byte + // - 9-byte special case when first byte is 0xFF + // - Bit shifting to extract value + let _ = usize::read_from_bytes(data); + + // Also test via SliceReader to ensure reader state is consistent + let mut reader = SliceReader::new(data); + let _ = reader.read_usize(); + + // Test peek + read combination - peek should not consume bytes + if !data.is_empty() { + let mut reader2 = SliceReader::new(data); + let _ = reader2.peek_u8(); + let _ = reader2.read_usize(); + } + + // Test multiple sequential reads to ensure state management + let mut reader3 = SliceReader::new(data); + let _ = reader3.read_usize(); + let _ = reader3.read_usize(); + let _ = reader3.read_usize(); +}); diff --git a/miden-serde-utils/src/byte_reader.rs b/miden-serde-utils/src/byte_reader.rs index f494271ca..fecb608cb 100644 --- a/miden-serde-utils/src/byte_reader.rs +++ b/miden-serde-utils/src/byte_reader.rs @@ -61,6 +61,25 @@ pub trait ByteReader { /// Returns true if there are more bytes left to be read from `self`. fn has_more_bytes(&self) -> bool; + /// Returns the maximum number of elements that can be safely allocated, given each + /// element occupies `element_size` bytes when serialized. + /// + /// This can be used by callers to pre-validate collection lengths before iterating, + /// preventing denial-of-service attacks from malicious length prefixes that claim + /// billions of elements. + /// + /// The default implementation returns `usize::MAX`, meaning no limit is enforced. + /// [`BudgetedReader`] overrides this to return `remaining_budget / element_size`, + /// providing tight, adaptive limits based on the caller's budget. + /// + /// # Arguments + /// * `element_size` - The serialized size of one element, from + /// [`Deserializable::min_serialized_size`]. Defaults to `size_of::()` but can be + /// overridden for types where serialized size differs from in-memory size. + fn max_alloc(&self, _element_size: usize) -> usize { + usize::MAX + } + // PROVIDED METHODS // -------------------------------------------------------------------------------------------- @@ -180,26 +199,85 @@ pub trait ByteReader { D::read_from(self) } - /// Reads a sequence of bytes from `self`, attempts to deserialize these bytes into a vector - /// with the specified number of `D` elements, and returns the result. + /// Returns an iterator that deserializes `num_elements` instances of `D` from this reader. + /// + /// This method validates the requested count against the reader's capacity before returning + /// the iterator, rejecting implausible lengths early. Each element is then deserialized + /// lazily as the iterator is consumed. /// /// # Errors - /// Returns a [DeserializationError] if the specified number elements could not be read from - /// `self`. - fn read_many(&mut self, num_elements: usize) -> Result, DeserializationError> + /// + /// Returns an error if `num_elements` exceeds `self.max_alloc(D::min_serialized_size())`, + /// indicating the reader cannot allocate that many elements. + /// + /// # Example + /// + /// ```ignore + /// // Collect into a Vec + /// let items: Vec = reader + /// .read_many_iter::(count)? + /// .collect::>()?; + /// + /// // Collect directly into a BTreeMap (no intermediate Vec) + /// let map: BTreeMap = reader + /// .read_many_iter::<(K, V)>(count)? + /// .collect::>()?; + /// ``` + fn read_many_iter( + &mut self, + num_elements: usize, + ) -> Result, DeserializationError> where Self: Sized, D: Deserializable, { - let mut result = Vec::with_capacity(num_elements); - for _ in 0..num_elements { - let element = D::read_from(self)?; - result.push(element) + let max_elements = self.max_alloc(D::min_serialized_size()); + if num_elements > max_elements { + return Err(DeserializationError::InvalidValue(format!( + "requested {num_elements} elements but reader can provide at most {max_elements}" + ))); } - Ok(result) + Ok(ReadManyIter { + reader: self, + remaining: num_elements, + _item: core::marker::PhantomData, + }) } } +// READ MANY ITERATOR +// ================================================================================================ + +/// Iterator that lazily deserializes elements from a [`ByteReader`]. +/// +/// Created by [`ByteReader::read_many_iter`]. Each call to `next()` deserializes one element. +/// This avoids upfront allocation and naturally integrates with [`BudgetedReader`] for +/// protection against malicious inputs. +pub struct ReadManyIter<'reader, R: ByteReader, D: Deserializable> { + reader: &'reader mut R, + remaining: usize, + _item: core::marker::PhantomData, +} + +impl<'reader, R: ByteReader, D: Deserializable> Iterator for ReadManyIter<'reader, R, D> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.remaining > 0 { + self.remaining -= 1; + Some(D::read_from(self.reader)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } +} + +impl<'reader, R: ByteReader, D: Deserializable> ExactSizeIterator for ReadManyIter<'reader, R, D> {} + // STANDARD LIBRARY ADAPTER // ================================================================================================ @@ -687,6 +765,152 @@ impl ByteReader for SliceReader<'_> { } } +// BUDGETED READER +// ================================================================================================ + +/// A reader wrapper that enforces a byte budget during deserialization. +/// +/// # Threat Model +/// +/// Malicious input can attack deserialization in two ways: +/// +/// 1. **Fake length prefix**: Input claims `len = 2^60` elements, causing allocation of a huge +/// `Vec` before any data is read. +/// 2. **Oversized input**: Attacker sends gigabytes of valid-looking data to exhaust memory over +/// time. +/// +/// # Defense Strategy +/// +/// Use `BudgetedReader` to limit total bytes consumed. Its [`max_alloc`](ByteReader::max_alloc) +/// method derives a bound from the remaining budget, which +/// [`read_many_iter`](ByteReader::read_many_iter) checks before iterating. +/// +/// ## Problem: SliceReader alone doesn't bound allocations +/// +/// ``` +/// use miden_serde_utils::{ByteReader, Deserializable, SliceReader}; +/// +/// // Malicious input: length prefix says 1 billion u64s, but only 16 bytes of data +/// let mut data = Vec::new(); +/// data.push(0u8); // vint64 9-byte marker +/// data.extend_from_slice(&1_000_000_000u64.to_le_bytes()); +/// data.extend_from_slice(&[0u8; 16]); +/// +/// // SliceReader returns usize::MAX from max_alloc, so read_many_iter accepts +/// // any length. This would try to iterate 1 billion times (slow, not OOM, +/// // but still a DoS vector). +/// let reader = SliceReader::new(&data); +/// assert_eq!(reader.max_alloc(8), usize::MAX); +/// ``` +/// +/// ## Solution: BudgetedReader bounds allocations via max_alloc +/// +/// ``` +/// use miden_serde_utils::{BudgetedReader, ByteReader, Deserializable, SliceReader}; +/// +/// // Same malicious input +/// let mut data = Vec::new(); +/// data.push(0u8); +/// data.extend_from_slice(&1_000_000_000u64.to_le_bytes()); +/// data.extend_from_slice(&[0u8; 16]); +/// +/// // BudgetedReader with 64-byte budget: max_alloc(8) = 64/8 = 8 elements +/// let inner = SliceReader::new(&data); +/// let reader = BudgetedReader::new(inner, 64); +/// assert_eq!(reader.max_alloc(8), 8); +/// +/// // read_many_iter rejects the 1B length since 1B > 8 +/// let result = Vec::::read_from_bytes_with_budget(&data, 64); +/// assert!(result.is_err()); +/// ``` +/// +/// ## Best practice: Set budget to expected input size +/// +/// ``` +/// use miden_serde_utils::{ByteWriter, Deserializable, Serializable}; +/// +/// // Legitimate input: 3 u64s, properly serialized +/// let original = vec![1u64, 2, 3]; +/// let mut data = Vec::new(); +/// original.write_into(&mut data); +/// +/// // Budget = data.len() bounds both fake lengths and total consumption +/// let result = Vec::::read_from_bytes_with_budget(&data, data.len()); +/// assert_eq!(result.unwrap(), vec![1, 2, 3]); +/// ``` +pub struct BudgetedReader { + inner: R, + remaining: usize, +} + +impl BudgetedReader { + /// Wraps a reader with the specified byte budget. + pub fn new(inner: R, budget: usize) -> Self { + Self { inner, remaining: budget } + } + + /// Returns remaining budget in bytes. + pub fn remaining(&self) -> usize { + self.remaining + } + + /// Consumes budget, returning an error if insufficient. + fn consume_budget(&mut self, n: usize) -> Result<(), DeserializationError> { + if n > self.remaining { + return Err(DeserializationError::InvalidValue(format!( + "budget exhausted: requested {n} bytes, {} remaining", + self.remaining + ))); + } + self.remaining -= n; + Ok(()) + } +} + +impl ByteReader for BudgetedReader { + fn read_u8(&mut self) -> Result { + self.consume_budget(1)?; + self.inner.read_u8() + } + + fn peek_u8(&self) -> Result { + // peek doesn't consume budget since it doesn't advance the reader + self.inner.peek_u8() + } + + fn read_slice(&mut self, len: usize) -> Result<&[u8], DeserializationError> { + self.consume_budget(len)?; + self.inner.read_slice(len) + } + + fn read_array(&mut self) -> Result<[u8; N], DeserializationError> { + self.consume_budget(N)?; + self.inner.read_array() + } + + fn check_eor(&self, num_bytes: usize) -> Result<(), DeserializationError> { + // check budget first, then delegate + if num_bytes > self.remaining { + return Err(DeserializationError::InvalidValue(format!( + "budget exhausted: requested {num_bytes} bytes, {} remaining", + self.remaining + ))); + } + self.inner.check_eor(num_bytes) + } + + fn has_more_bytes(&self) -> bool { + self.remaining > 0 && self.inner.has_more_bytes() + } + + fn max_alloc(&self, element_size: usize) -> usize { + if element_size == 0 { + return usize::MAX; // ZSTs don't consume budget + } + self.remaining / element_size + } +} + #[cfg(all(test, feature = "std"))] mod tests { use std::io::Cursor; @@ -831,4 +1055,300 @@ mod tests { assert_eq!(reader.pos, 513); assert!(!reader.has_more_bytes(), "expected there to be no more data in the input"); } + + #[test] + fn budgeted_reader_basic() { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 4); + + assert_eq!(reader.remaining(), 4); + assert!(reader.has_more_bytes()); + + // read 4 bytes (within budget) + assert_eq!(reader.read_u32().unwrap(), 0x04030201); + assert_eq!(reader.remaining(), 0); + + // budget exhausted + assert!(!reader.has_more_bytes()); + assert!(reader.read_u8().is_err()); + } + + #[test] + fn budgeted_reader_peek_does_not_consume() { + let data = [42u8]; + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 1); + + // peek multiple times, budget unchanged + assert_eq!(reader.peek_u8().unwrap(), 42); + assert_eq!(reader.peek_u8().unwrap(), 42); + assert_eq!(reader.remaining(), 1); + + // actual read consumes budget + assert_eq!(reader.read_u8().unwrap(), 42); + assert_eq!(reader.remaining(), 0); + } + + #[test] + fn budgeted_reader_check_eor_respects_budget() { + let data = [0u8; 100]; + let inner = SliceReader::new(&data); + let reader = BudgetedReader::new(inner, 10); + + // within budget + assert!(reader.check_eor(10).is_ok()); + + // exceeds budget (even though inner has enough bytes) + assert!(reader.check_eor(11).is_err()); + } + + #[test] + fn budgeted_reader_read_slice() { + let data = [1u8, 2, 3, 4, 5]; + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 3); + + // read 3 bytes (exactly budget) + assert_eq!(reader.read_slice(3).unwrap(), &[1, 2, 3]); + assert_eq!(reader.remaining(), 0); + + // can't read more + assert!(reader.read_slice(1).is_err()); + } + + #[test] + fn budgeted_reader_read_array() { + let data = [0xaau8, 0xbb, 0xcc, 0xdd]; + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 2); + + // read 2-byte array + assert_eq!(reader.read_array::<2>().unwrap(), [0xaa, 0xbb]); + assert_eq!(reader.remaining(), 0); + + // budget exhausted + assert!(reader.read_array::<2>().is_err()); + } + + #[test] + fn budgeted_reader_zero_budget() { + let data = [1u8]; + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 0); + + assert!(!reader.has_more_bytes()); + assert!(reader.read_u8().is_err()); + // peek still works (doesn't consume budget) + assert_eq!(reader.peek_u8().unwrap(), 1); + } + + #[test] + fn budgeted_reader_max_alloc() { + let data = [0u8; 100]; + let inner = SliceReader::new(&data); + let reader = BudgetedReader::new(inner, 64); + + // 64 bytes budget / 8 bytes per u64 = 8 elements max + assert_eq!(reader.max_alloc(8), 8); + + // 64 bytes budget / 1 byte per u8 = 64 elements max + assert_eq!(reader.max_alloc(1), 64); + + // 64 bytes budget / 16 bytes per u128 = 4 elements max + assert_eq!(reader.max_alloc(16), 4); + + // ZSTs (0 bytes) return usize::MAX + assert_eq!(reader.max_alloc(0), usize::MAX); + } + + #[test] + fn unbounded_reader_max_alloc_returns_max() { + let data = [0u8; 100]; + let reader = SliceReader::new(&data); + + // Unbounded readers return usize::MAX + assert_eq!(reader.max_alloc(1), usize::MAX); + assert_eq!(reader.max_alloc(8), usize::MAX); + } + + // ============================================================================================ + // The following tests document the threat model and defense layers. + // ============================================================================================ + + /// SliceReader alone does NOT reject fake length prefixes. + /// + /// A malicious input claiming 1000 elements will be accepted by read_many_iter + /// because SliceReader.max_alloc() returns usize::MAX. The deserialization will + /// eventually fail with UnexpectedEOF, but only after attempting to iterate + /// (which could be slow for huge counts, though not OOM since we don't pre-allocate). + #[test] + fn slice_reader_accepts_fake_length_prefix() { + let mut data = Vec::new(); + // Write length = 1000 (vint64 encoding: 0x07D0 << 2 | 0b10 = 0x1F42) + // For simplicity, use the 9-byte form + data.push(0); // 9-byte marker + data.extend_from_slice(&1000u64.to_le_bytes()); + // Only 8 bytes of actual u64 data (1 element, not 1000) + data.extend_from_slice(&42u64.to_le_bytes()); + + // read_many_iter passes the max_alloc check (usize::MAX >= 1000) + let mut reader = SliceReader::new(&data); + let _len = reader.read_usize().unwrap(); + let iter_result = reader.read_many_iter::(1000); + + // The iterator is created successfully + assert!(iter_result.is_ok()); + + // But collecting fails on the 2nd element (EOF) + let collect_result: Result, _> = iter_result.unwrap().collect(); + assert!(collect_result.is_err()); + assert!(matches!(collect_result.unwrap_err(), DeserializationError::UnexpectedEOF)); + } + + /// BudgetedReader rejects fake length prefixes BEFORE iteration begins. + /// + /// With a 64-byte budget, max_alloc(8) = 8, so a claim of 1000 elements + /// is rejected immediately by read_many_iter. + #[test] + fn budgeted_reader_rejects_fake_length_upfront() { + let mut data = Vec::new(); + data.push(0); // 9-byte vint64 marker + data.extend_from_slice(&1000u64.to_le_bytes()); + data.extend_from_slice(&42u64.to_le_bytes()); + + let inner = SliceReader::new(&data); + let mut reader = BudgetedReader::new(inner, 64); + + let _len = reader.read_usize().unwrap(); // consumes 9 bytes, 55 remaining + // 55 / 8 = 6 elements max + let iter_result = reader.read_many_iter::(1000); + + // Rejected immediately: 1000 > 6 + match iter_result { + Err(DeserializationError::InvalidValue(_)) => {}, // expected + other => panic!("expected InvalidValue error, got {:?}", other.map(|_| "Ok")), + } + } + + /// Best practice: budget = input length provides both protections. + /// + /// 1. Fake length prefixes are bounded by max_alloc (remaining_bytes / element_size) + /// 2. Total consumption is bounded by the budget + #[test] + fn budget_equals_input_length_is_safe() { + // Valid input: 2 u64s + let original = vec![100u64, 200]; + let mut data = Vec::new(); + crate::Serializable::write_into(&original, &mut data); + + // Budget = exact input size + let result = Vec::::read_from_bytes_with_budget(&data, data.len()); + assert_eq!(result.unwrap(), vec![100, 200]); + + // Malicious input claiming 1000 elements (same serialized prefix manipulation) + let mut evil_data = Vec::new(); + evil_data.push(0); // 9-byte vint64 + evil_data.extend_from_slice(&1000u64.to_le_bytes()); + evil_data.extend_from_slice(&42u64.to_le_bytes()); // only 1 actual element + + // Budget = input length (17 bytes). After reading length (9 bytes), 8 remain. + // max_alloc(8) = 8/8 = 1, so 1000 > 1 fails. + let result = Vec::::read_from_bytes_with_budget(&evil_data, evil_data.len()); + assert!(result.is_err()); + } + + // ============================================================================================ + // Tests documenting min_serialized_size()-based allocation bounds (defaults to size_of) + // ============================================================================================ + + /// The max_alloc check uses D::min_serialized_size() to bound memory allocation. + /// By default, min_serialized_size() returns size_of::(). + /// + /// For flat collections like Vec, this works well: we check that + /// budget / min_serialized_size() >= requested_count before allocating. + #[test] + fn min_serialized_size_bounds_flat_collections() { + let mut data = Vec::new(); + data.push(0); // 9-byte vint64 marker + data.extend_from_slice(&1000u64.to_le_bytes()); // claim 1000 u64s + data.extend_from_slice(&[0u8; 16]); // only 2 u64s of actual data + + let inner = SliceReader::new(&data); + // Budget of 80 bytes: after reading 9-byte length, 71 remain. + // max_alloc(u64::min_serialized_size()) = 71 / 8 = 8 elements max + let mut reader = BudgetedReader::new(inner, 80); + + let _len = reader.read_usize().unwrap(); + let result = reader.read_many_iter::(1000); + + // Rejected: 1000 > 8 + assert!(result.is_err()); + } + + /// For nested collections like Vec>, min_serialized_size() returns 1 (the minimum + /// vint length prefix), not size_of. This is more permissive but accurate: a + /// serialized Vec can be as small as 1 byte (empty vec). + /// + /// The early-abort check uses this minimum, and budget enforcement during actual + /// reads provides the real protection against malicious input. + #[test] + fn min_serialized_size_override_for_nested_collections() { + // Vec::min_serialized_size() returns 1 (minimum vint prefix), not size_of + assert_eq!(>::min_serialized_size(), 1); + + let mut data = Vec::new(); + data.push(0); // 9-byte vint64 marker + data.extend_from_slice(&100u64.to_le_bytes()); // claim 100 inner Vecs + // Only provide enough data for 1 empty inner Vec + data.push(0b10); // vint64 for 0 (empty inner vec) + + let inner = SliceReader::new(&data); + // With min_serialized_size() = 1, we need budget >= 100 to pass the early check. + // After reading 9-byte length, 101 - 9 = 92 remaining, 92 / 1 = 92 < 100. + // So with budget = 110, we get 110 - 9 = 101 remaining, 101 >= 100. + let mut reader = BudgetedReader::new(inner, 110); + + let _len = reader.read_usize().unwrap(); + let result = reader.read_many_iter::>(100); + + // The early check passes (100 <= 101) + assert!(result.is_ok()); + + // But deserialization fails when we try to read 100 inner Vecs with only 1 + let collect_result: Result>, _> = result.unwrap().collect(); + assert!(collect_result.is_err()); + } + + /// Demonstrates that min_serialized_size() approach still provides security for nested + /// collections, just with later detection. The budget is enforced during reads. + #[test] + fn nested_collections_still_protected_by_budget() { + // With Vec::min_serialized_size() = 1, the early check is permissive. + // Security comes from budget enforcement during actual reads. + let mut data = Vec::new(); + data.push(0); // 9-byte vint64 marker + data.extend_from_slice(&10u64.to_le_bytes()); // claim 10 inner Vecs + // Each inner vec claims 1000 u64s but provides none + for _ in 0..10 { + data.push(0); // 9-byte vint64 marker + data.extend_from_slice(&1000u64.to_le_bytes()); + } + + let inner = SliceReader::new(&data); + // Small budget: will run out during inner deserialization + let mut reader = BudgetedReader::new(inner, 100); + + // Outer length read succeeds (consumes 9 bytes, 91 remaining) + let _len = reader.read_usize().unwrap(); + + // With Vec::min_serialized_size() = 1, early check passes: 91 / 1 = 91 >= 10 + let result = reader.read_many_iter::>(10); + assert!(result.is_ok()); + + // But collecting fails because the inner vecs claim 1000 u64s each, + // exhausting the budget during inner deserialization + let collect_result: Result>, _> = result.unwrap().collect(); + assert!(collect_result.is_err()); + } } diff --git a/miden-serde-utils/src/lib.rs b/miden-serde-utils/src/lib.rs index a04c28add..ef9470f19 100644 --- a/miden-serde-utils/src/lib.rs +++ b/miden-serde-utils/src/lib.rs @@ -44,17 +44,11 @@ impl core::fmt::Display for DeserializationError { mod byte_reader; #[cfg(feature = "std")] pub use byte_reader::ReadAdapter; -pub use byte_reader::{ByteReader, SliceReader}; +pub use byte_reader::{BudgetedReader, ByteReader, ReadManyIter, SliceReader}; mod byte_writer; pub use byte_writer::ByteWriter; -#[cfg(feature = "winter-compat")] -mod winter_compat; - -#[cfg(feature = "p3-compat")] -mod p3_compat; - // SERIALIZABLE TRAIT // ================================================================================================ @@ -412,6 +406,22 @@ pub trait Deserializable: Sized { /// * Bytes read from the `source` do not represent a valid value for `Self`. fn read_from(source: &mut R) -> Result; + /// Returns the minimum serialized size for one instance of this type. + /// + /// This is used by [`ByteReader::max_alloc`] to estimate how many elements can be + /// deserialized from the remaining budget, preventing denial-of-service attacks from + /// malicious length prefixes. + /// + /// The default implementation returns `size_of::()`, which is conservative: it may + /// reject valid input for types where the serialized size is smaller than the in-memory + /// size (e.g., structs with computed/cached fields that aren't serialized). + /// + /// Override this method for types where the serialized representation is smaller than + /// the in-memory representation to allow more elements to be deserialized. + fn min_serialized_size() -> usize { + core::mem::size_of::() + } + // PROVIDED METHODS // -------------------------------------------------------------------------------------------- @@ -427,6 +437,24 @@ pub trait Deserializable: Sized { fn read_from_bytes(bytes: &[u8]) -> Result { Self::read_from(&mut SliceReader::new(bytes)) } + + /// Deserializes `Self` from bytes with a byte budget limit. + /// + /// This is the recommended method for deserializing untrusted input. The budget limits + /// how many bytes can be consumed during deserialization, preventing denial-of-service + /// attacks that exploit length fields to cause huge allocations. + /// + /// # Errors + /// Returns an error if: + /// * The budget is exhausted before deserialization completes. + /// * The `bytes` do not contain enough information to deserialize `Self`. + /// * The `bytes` do not represent a valid value for `Self`. + fn read_from_bytes_with_budget( + bytes: &[u8], + budget: usize, + ) -> Result { + Self::read_from(&mut BudgetedReader::new(SliceReader::new(bytes), budget)) + } } impl Deserializable for () { @@ -563,57 +591,112 @@ impl Deserializable for usize { impl Deserializable for Option { fn read_from(source: &mut R) -> Result { - let contains = source.read_bool()?; - - match contains { - true => Ok(Some(T::read_from(source)?)), - false => Ok(None), + if source.read_bool()? { + Ok(Some(T::read_from(source)?)) + } else { + Ok(None) } } + + /// Returns 1 (just the bool discriminator). + /// + /// The `Some` variant would be `1 + T::min_serialized_size()`, but we use the minimum + /// to allow more elements through the early check. + fn min_serialized_size() -> usize { + 1 + } } impl Deserializable for [T; C] { fn read_from(source: &mut R) -> Result { - let data: Vec = source.read_many(C)?; + let data: Vec = source.read_many_iter(C)?.collect::>()?; - // SAFETY: the call above only returns a Vec if there are `C` elements, this conversion - // always succeeds - let res = data.try_into().unwrap_or_else(|v: Vec| { + // The iterator yields exactly C elements (or fails early), so this always succeeds + Ok(data.try_into().unwrap_or_else(|v: Vec| { panic!("Expected a Vec of length {} but it was {}", C, v.len()) - }); + })) + } - Ok(res) + fn min_serialized_size() -> usize { + C.saturating_mul(T::min_serialized_size()) } } impl Deserializable for Vec { fn read_from(source: &mut R) -> Result { let len = source.read_usize()?; - source.read_many(len) + source.read_many_iter(len)?.collect() + } + + /// Returns 1 (the minimum vint length prefix size). + /// + /// The actual serialized size depends on the number of elements, which we don't know + /// at the point this is called. Using the minimum allows more elements through the + /// early check; budget enforcement during actual reads provides the real protection. + fn min_serialized_size() -> usize { + 1 } } impl Deserializable for BTreeMap { fn read_from(source: &mut R) -> Result { let len = source.read_usize()?; - let data = source.read_many(len)?; - Ok(BTreeMap::from_iter(data)) + source.read_many_iter(len)?.collect() + } + + fn min_serialized_size() -> usize { + 1 // minimum vint length prefix } } impl Deserializable for BTreeSet { fn read_from(source: &mut R) -> Result { let len = source.read_usize()?; - let data = source.read_many(len)?; - Ok(BTreeSet::from_iter(data)) + source.read_many_iter(len)?.collect() + } + + fn min_serialized_size() -> usize { + 1 // minimum vint length prefix } } impl Deserializable for String { fn read_from(source: &mut R) -> Result { let len = source.read_usize()?; - let data = source.read_many(len)?; + let data: Vec = source.read_many_iter(len)?.collect::>()?; String::from_utf8(data).map_err(|err| DeserializationError::InvalidValue(format!("{err}"))) } + + fn min_serialized_size() -> usize { + 1 // minimum vint length prefix + } +} + +// GOLDILOCKS FIELD ELEMENT IMPLEMENTATIONS +// ================================================================================================ + +impl Serializable for p3_goldilocks::Goldilocks { + fn write_into(&self, target: &mut W) { + use p3_field::PrimeField64; + target.write_u64(self.as_canonical_u64()); + } + + fn get_size_hint(&self) -> usize { + core::mem::size_of::() + } +} + +impl Deserializable for p3_goldilocks::Goldilocks { + fn read_from(source: &mut R) -> Result { + use p3_field::integers::QuotientMap; + + let value = source.read_u64()?; + Self::from_canonical_checked(value).ok_or_else(|| { + DeserializationError::InvalidValue(format!( + "value {} is not a valid Goldilocks field element", + value + )) + }) + } } diff --git a/miden-serde-utils/src/p3_compat.rs b/miden-serde-utils/src/p3_compat.rs deleted file mode 100644 index f30c76a0b..000000000 --- a/miden-serde-utils/src/p3_compat.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Compatibility layer implementing miden-serde-utils traits for Plonky3 types. - -use alloc::format; - -use p3_field::PrimeField64; - -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; - -// P3_MIDEN_GOLDILOCKS FIELD ELEMENT IMPLEMENTATIONS -// ================================================================================================ - -impl Serializable for p3_miden_goldilocks::Goldilocks { - fn write_into(&self, target: &mut W) { - target.write_u64(self.as_canonical_u64()); - } - - fn get_size_hint(&self) -> usize { - core::mem::size_of::() - } -} - -impl Deserializable for p3_miden_goldilocks::Goldilocks { - fn read_from(source: &mut R) -> Result { - use p3_field::integers::QuotientMap; - - let value = source.read_u64()?; - Self::from_canonical_checked(value).ok_or_else(|| { - DeserializationError::InvalidValue(format!( - "value {} is not a valid Goldilocks field element", - value - )) - }) - } -} diff --git a/miden-serde-utils/src/winter_compat.rs b/miden-serde-utils/src/winter_compat.rs deleted file mode 100644 index a66aed54e..000000000 --- a/miden-serde-utils/src/winter_compat.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Compatibility layer implementing miden-serde-utils traits for winter ecosystem types. - -use crate::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; - -// WINTER_MATH FIELD ELEMENT IMPLEMENTATIONS -// ================================================================================================ - -impl Serializable for winter_math::fields::f64::BaseElement { - fn write_into(&self, target: &mut W) { - target.write_u64(self.as_int()); - } - - fn get_size_hint(&self) -> usize { - core::mem::size_of::() - } -} - -impl Deserializable for winter_math::fields::f64::BaseElement { - fn read_from(source: &mut R) -> Result { - use winter_math::StarkField; - let value = source.read_u64()?; - if value >= Self::MODULUS { - return Err(DeserializationError::InvalidValue( - "field element value exceeds modulus".into(), - )); - } - Ok(Self::new(value)) - } -}