From 58b39e59a5425074124fa64918629a617a671663 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 4 Aug 2026 16:32:03 -0700 Subject: [PATCH 01/17] chore: increment crate versions to v0.30.0 --- CHANGELOG.md | 1 + Cargo.lock | 82 ++++++++++----------- Cargo.toml | 62 ++++++++-------- air/Cargo.toml | 2 +- core/Cargo.toml | 2 +- crates/ace-codegen/Cargo.toml | 2 +- crates/assembly-syntax-cst/Cargo.toml | 2 +- crates/assembly-syntax/Cargo.toml | 2 +- crates/assembly/Cargo.toml | 2 +- crates/crypto-derive/Cargo.toml | 2 +- crates/crypto/Cargo.toml | 2 +- crates/debug-types/Cargo.toml | 2 +- crates/field/Cargo.toml | 2 +- crates/lib/core/Cargo.toml | 2 +- crates/lib/core/asm/miden-project.toml | 4 +- crates/lifted-air/Cargo.toml | 2 +- crates/lifted-stark/Cargo.toml | 2 +- crates/mast-package/Cargo.toml | 2 +- crates/miden-constraint-compiler/Cargo.toml | 2 +- crates/miden-format/Cargo.toml | 2 +- crates/package-registry-local/Cargo.toml | 2 +- crates/package-registry/Cargo.toml | 2 +- crates/project/Cargo.toml | 2 +- crates/serde-utils/Cargo.toml | 2 +- crates/stark-transcript/Cargo.toml | 2 +- crates/stateful-hasher/Cargo.toml | 2 +- crates/test-serde-macros/Cargo.toml | 2 +- crates/test-utils/Cargo.toml | 2 +- crates/utils-core-derive/Cargo.toml | 2 +- crates/utils-diagnostics/Cargo.toml | 2 +- crates/utils-indexing/Cargo.toml | 2 +- crates/utils-sync/Cargo.toml | 2 +- miden-vm/Cargo.toml | 2 +- processor/Cargo.toml | 2 +- prover/Cargo.toml | 2 +- tools/miden-core-fuzz/Cargo.lock | 40 +++++----- tools/miden-crypto-fuzz/Cargo.lock | 16 ++-- tools/miden-serde-utils-fuzz/Cargo.lock | 2 +- verifier/Cargo.toml | 2 +- 39 files changed, 136 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b69bb3f1a2..925d7f32fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog +## v0.30.0 (Unreleased) ## v0.29.0 (2026-08-04) diff --git a/Cargo.lock b/Cargo.lock index e925b839c4..b004c1ccf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2053,7 +2053,7 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miden-ace-codegen" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-constraint-compiler", "miden-core", @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.29.0" +version = "0.30.0" dependencies = [ "insta", "miden-ace-codegen", @@ -2080,7 +2080,7 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.29.0" +version = "0.30.0" dependencies = [ "env_logger", "insta", @@ -2100,7 +2100,7 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.29.0" +version = "0.30.0" dependencies = [ "env_logger", "log", @@ -2124,7 +2124,7 @@ dependencies = [ [[package]] name = "miden-assembly-syntax-cst" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-debug-types", "miden-rowan", @@ -2135,7 +2135,7 @@ dependencies = [ [[package]] name = "miden-bench" -version = "0.29.0" +version = "0.30.0" dependencies = [ "clap", "miden-lifted-stark", @@ -2165,7 +2165,7 @@ dependencies = [ [[package]] name = "miden-constraint-compiler" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-core", "miden-crypto", @@ -2173,7 +2173,7 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.29.0" +version = "0.30.0" dependencies = [ "criterion", "derive_more", @@ -2197,7 +2197,7 @@ dependencies = [ [[package]] name = "miden-core-lib" -version = "0.29.0" +version = "0.30.0" dependencies = [ "criterion", "env_logger", @@ -2225,7 +2225,7 @@ dependencies = [ [[package]] name = "miden-core-lib-codegen" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-core", "miden-precompiles", @@ -2233,7 +2233,7 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.29.0" +version = "0.30.0" dependencies = [ "assert_matches", "blake3", @@ -2283,7 +2283,7 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-field", "quote", @@ -2292,7 +2292,7 @@ dependencies = [ [[package]] name = "miden-crypto-smt-codspeed-bench" -version = "0.29.0" +version = "0.30.0" dependencies = [ "codspeed-criterion-compat", "miden-crypto", @@ -2301,7 +2301,7 @@ dependencies = [ [[package]] name = "miden-crypto-wycheproof-tests" -version = "0.29.0" +version = "0.30.0" dependencies = [ "ed25519-dalek", "hex", @@ -2315,7 +2315,7 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.29.0" +version = "0.30.0" dependencies = [ "memchr", "miden-crypto", @@ -2335,7 +2335,7 @@ dependencies = [ [[package]] name = "miden-field" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-serde-utils", "num-bigint 0.5.1", @@ -2354,7 +2354,7 @@ dependencies = [ [[package]] name = "miden-format" -version = "0.29.0" +version = "0.30.0" dependencies = [ "clap", "miden-assembly-syntax-cst", @@ -2378,7 +2378,7 @@ dependencies = [ [[package]] name = "miden-lifted-air" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-field", "p3-air", @@ -2391,7 +2391,7 @@ dependencies = [ [[package]] name = "miden-lifted-stark" -version = "0.29.0" +version = "0.30.0" dependencies = [ "criterion", "miden-lifted-air", @@ -2422,7 +2422,7 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.29.0" +version = "0.30.0" dependencies = [ "hashbrown 0.17.1", "log", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "miden-package-registry" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "miden-package-registry-local" -version = "0.29.0" +version = "0.30.0" dependencies = [ "clap", "miden-assembly-syntax", @@ -2511,7 +2511,7 @@ dependencies = [ [[package]] name = "miden-precompiles" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-core", "miden-crypto", @@ -2519,7 +2519,7 @@ dependencies = [ [[package]] name = "miden-precompiles-prover" -version = "0.29.0" +version = "0.30.0" dependencies = [ "insta", "k256", @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.29.0" +version = "0.30.0" dependencies = [ "hashbrown 0.17.1", "insta", @@ -2569,7 +2569,7 @@ dependencies = [ [[package]] name = "miden-project" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -2588,7 +2588,7 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-air", "miden-assembly", @@ -2615,7 +2615,7 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-goldilocks", @@ -2625,7 +2625,7 @@ dependencies = [ [[package]] name = "miden-stark-transcript" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-challenger", "p3-field", @@ -2635,7 +2635,7 @@ dependencies = [ [[package]] name = "miden-stateful-hasher" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-bn254", "p3-field", @@ -2646,7 +2646,7 @@ dependencies = [ [[package]] name = "miden-test-serde-macros" -version = "0.29.0" +version = "0.30.0" dependencies = [ "proc-macro2", "proptest", @@ -2658,7 +2658,7 @@ dependencies = [ [[package]] name = "miden-test-utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "env_logger", "miden-air", @@ -2677,7 +2677,7 @@ dependencies = [ [[package]] name = "miden-utils-core-derive" -version = "0.29.0" +version = "0.30.0" dependencies = [ "proc-macro2", "quote", @@ -2686,7 +2686,7 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-debug-types", "miden-miette", @@ -2695,7 +2695,7 @@ dependencies = [ [[package]] name = "miden-utils-indexing" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-serde-utils", "miden-test-serde-macros", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "miden-utils-sync" -version = "0.29.0" +version = "0.30.0" dependencies = [ "lock_api", "loom", @@ -2717,7 +2717,7 @@ dependencies = [ [[package]] name = "miden-verifier" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-air", "miden-core", @@ -2732,7 +2732,7 @@ dependencies = [ [[package]] name = "miden-vm" -version = "0.29.0" +version = "0.30.0" dependencies = [ "assert_cmd", "clap", @@ -2761,7 +2761,7 @@ dependencies = [ [[package]] name = "miden-vm-blake3-bench" -version = "0.29.0" +version = "0.30.0" dependencies = [ "clap", "codspeed-criterion-compat", @@ -2776,7 +2776,7 @@ dependencies = [ [[package]] name = "miden-vm-precompiles-bench" -version = "0.29.0" +version = "0.30.0" dependencies = [ "codspeed-criterion-compat", "miden-core", @@ -2788,7 +2788,7 @@ dependencies = [ [[package]] name = "miden-vm-synthetic-bench" -version = "0.29.0" +version = "0.30.0" dependencies = [ "codspeed-criterion-compat", "miden-assembly", diff --git a/Cargo.toml b/Cargo.toml index 95ede4f893..5eff425cce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ rust-version = "1.96.1" # Private workspace members inherit this version. Publishable crates keep their # own package versions so release tooling can publish only the crates selected # for a release. -version = "0.29.0" +version = "0.30.0" [profile.optimized] inherits = "release" @@ -81,39 +81,39 @@ opt-level = 0 [workspace.dependencies] # Workspace crates -miden-ace-codegen = { path = "./crates/ace-codegen", version = "0.29", default-features = false } -miden-air = { path = "./air", version = "0.29", default-features = false } -miden-assembly = { path = "./crates/assembly", version = "0.29", default-features = false } -miden-assembly-syntax = { path = "./crates/assembly-syntax", version = "0.29", default-features = false } -miden-assembly-syntax-cst = { path = "./crates/assembly-syntax-cst", version = "0.29", default-features = false } -miden-constraint-compiler = { path = "./crates/miden-constraint-compiler", version = "0.29", default-features = false } -miden-core = { path = "./core", version = "0.29", default-features = false } -miden-crypto = { path = "./crates/crypto", version = "0.29", default-features = false } -miden-crypto-derive = { path = "./crates/crypto-derive", version = "0.29", default-features = false } -miden-debug-types = { path = "./crates/debug-types", version = "0.29", default-features = false } -miden-field = { path = "./crates/field", version = "0.29", default-features = false } -miden-lifted-air = { path = "./crates/lifted-air", version = "0.29", default-features = false } -miden-lifted-stark = { path = "./crates/lifted-stark", version = "0.29", default-features = false } -miden-mast-package = { path = "./crates/mast-package", version = "0.29", default-features = false } -miden-package-registry = { path = "./crates/package-registry", version = "0.29", default-features = false } -miden-precompiles = { path = "./crates/precompiles", version = "0.29", default-features = false } -miden-precompiles-prover = { path = "./crates/precompiles-prover", version = "0.29", default-features = false } -miden-core-lib-codegen = { path = "./crates/lib/core/codegen", version = "0.29", default-features = false } -miden-processor = { path = "./processor", version = "0.29", default-features = false } +miden-ace-codegen = { path = "./crates/ace-codegen", version = "0.30", default-features = false } +miden-air = { path = "./air", version = "0.30", default-features = false } +miden-assembly = { path = "./crates/assembly", version = "0.30", default-features = false } +miden-assembly-syntax = { path = "./crates/assembly-syntax", version = "0.30", default-features = false } +miden-assembly-syntax-cst = { path = "./crates/assembly-syntax-cst", version = "0.30", default-features = false } +miden-constraint-compiler = { path = "./crates/miden-constraint-compiler", version = "0.30", default-features = false } +miden-core = { path = "./core", version = "0.30", default-features = false } +miden-crypto = { path = "./crates/crypto", version = "0.30", default-features = false } +miden-crypto-derive = { path = "./crates/crypto-derive", version = "0.30", default-features = false } +miden-debug-types = { path = "./crates/debug-types", version = "0.30", default-features = false } +miden-field = { path = "./crates/field", version = "0.30", default-features = false } +miden-lifted-air = { path = "./crates/lifted-air", version = "0.30", default-features = false } +miden-lifted-stark = { path = "./crates/lifted-stark", version = "0.30", default-features = false } +miden-mast-package = { path = "./crates/mast-package", version = "0.30", default-features = false } +miden-package-registry = { path = "./crates/package-registry", version = "0.30", default-features = false } +miden-precompiles = { path = "./crates/precompiles", version = "0.30", default-features = false } +miden-precompiles-prover = { path = "./crates/precompiles-prover", version = "0.30", default-features = false } +miden-core-lib-codegen = { path = "./crates/lib/core/codegen", version = "0.30", default-features = false } +miden-processor = { path = "./processor", version = "0.30", default-features = false } miden-test-serde-macros = { path = "./crates/test-serde-macros", default-features = false } -miden-project = { path = "./crates/project", version = "0.29", default-features = false } -miden-prover = { path = "./prover", version = "0.29", default-features = false } -miden-core-lib = { path = "./crates/lib/core", version = "0.29", default-features = false } -miden-serde-utils = { path = "./crates/serde-utils", version = "0.29", default-features = false } -miden-stark-transcript = { path = "./crates/stark-transcript", version = "0.29", default-features = false } -miden-stateful-hasher = { path = "./crates/stateful-hasher", version = "0.29", default-features = false } +miden-project = { path = "./crates/project", version = "0.30", default-features = false } +miden-prover = { path = "./prover", version = "0.30", default-features = false } +miden-core-lib = { path = "./crates/lib/core", version = "0.30", default-features = false } +miden-serde-utils = { path = "./crates/serde-utils", version = "0.30", default-features = false } +miden-stark-transcript = { path = "./crates/stark-transcript", version = "0.30", default-features = false } +miden-stateful-hasher = { path = "./crates/stateful-hasher", version = "0.30", default-features = false } midenc-hir-type = { path = "./crates/midenc-hir-type", version = "0.10", default-features = false } -miden-utils-core-derive = { path = "./crates/utils-core-derive", version = "0.29", default-features = false } -miden-utils-diagnostics = { path = "./crates/utils-diagnostics", version = "0.29", default-features = false } -miden-utils-indexing = { path = "./crates/utils-indexing", version = "0.29", default-features = false } -miden-utils-sync = { path = "./crates/utils-sync", version = "0.29", default-features = false } +miden-utils-core-derive = { path = "./crates/utils-core-derive", version = "0.30", default-features = false } +miden-utils-diagnostics = { path = "./crates/utils-diagnostics", version = "0.30", default-features = false } +miden-utils-indexing = { path = "./crates/utils-indexing", version = "0.30", default-features = false } +miden-utils-sync = { path = "./crates/utils-sync", version = "0.30", default-features = false } miden-utils-testing = { path = "./crates/test-utils", package = "miden-test-utils" } -miden-verifier = { path = "./verifier", version = "0.29", default-features = false } +miden-verifier = { path = "./verifier", version = "0.30", default-features = false } # Miden crates miden-formatting = { version = "0.1", default-features = false } diff --git a/air/Cargo.toml b/air/Cargo.toml index 6c1dc42a53..356cc4c2d5 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-air" -version = "0.29.0" +version = "0.30.0" description = "Algebraic intermediate representation of Miden VM processor" documentation = "https://docs.rs/miden-air" readme = "README.md" diff --git a/core/Cargo.toml b/core/Cargo.toml index 3e89f9cd29..8db1161d9c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-core" -version = "0.29.0" +version = "0.30.0" description = "Miden VM core components" documentation = "https://docs.rs/miden-core" readme = "README.md" diff --git a/crates/ace-codegen/Cargo.toml b/crates/ace-codegen/Cargo.toml index ae8134556f..0c8d316684 100644 --- a/crates/ace-codegen/Cargo.toml +++ b/crates/ace-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-ace-codegen" -version = "0.29.0" +version = "0.30.0" description = "ACE circuit codegen for Plonky3-based Miden AIRs." documentation = "https://docs.rs/miden-ace-codegen" readme = "README.md" diff --git a/crates/assembly-syntax-cst/Cargo.toml b/crates/assembly-syntax-cst/Cargo.toml index c53c189891..f93081fc64 100644 --- a/crates/assembly-syntax-cst/Cargo.toml +++ b/crates/assembly-syntax-cst/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-assembly-syntax-cst" -version = "0.29.0" +version = "0.30.0" description = "Lossless concrete syntax tree support for the Miden Assembly language" documentation = "https://docs.rs/miden-assembly-syntax-cst" readme = "README.md" diff --git a/crates/assembly-syntax/Cargo.toml b/crates/assembly-syntax/Cargo.toml index b33e950109..875a94b57a 100644 --- a/crates/assembly-syntax/Cargo.toml +++ b/crates/assembly-syntax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-assembly-syntax" -version = "0.29.0" +version = "0.30.0" description = "Parsing and semantic analysis of the Miden Assembly language" documentation = "https://docs.rs/miden-assembly-syntax" readme = "README.md" diff --git a/crates/assembly/Cargo.toml b/crates/assembly/Cargo.toml index fb28258e3e..7c467608c4 100644 --- a/crates/assembly/Cargo.toml +++ b/crates/assembly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-assembly" -version = "0.29.0" +version = "0.30.0" description = "Miden VM assembly language" documentation = "https://docs.rs/miden-assembly" readme = "README.md" diff --git a/crates/crypto-derive/Cargo.toml b/crates/crypto-derive/Cargo.toml index 1f2e562ed8..a66d86194c 100644 --- a/crates/crypto-derive/Cargo.toml +++ b/crates/crypto-derive/Cargo.toml @@ -9,7 +9,7 @@ name = "miden-crypto-derive" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] doctest = false diff --git a/crates/crypto/Cargo.toml b/crates/crypto/Cargo.toml index 5200732c52..fd5ffa18c8 100644 --- a/crates/crypto/Cargo.toml +++ b/crates/crypto/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-crypto" readme.workspace = true repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [[bin]] bench = false diff --git a/crates/debug-types/Cargo.toml b/crates/debug-types/Cargo.toml index f633adecf8..606e5a1d53 100644 --- a/crates/debug-types/Cargo.toml +++ b/crates/debug-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-debug-types" -version = "0.29.0" +version = "0.30.0" description = "Core source-level debugging information types used throughout the Miden toolchain" documentation = "https://docs.rs/miden-debug-types" readme = "README.md" diff --git a/crates/field/Cargo.toml b/crates/field/Cargo.toml index 6ee9062cda..e2c1511f5a 100644 --- a/crates/field/Cargo.toml +++ b/crates/field/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-field" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] crate-type = ["rlib"] diff --git a/crates/lib/core/Cargo.toml b/crates/lib/core/Cargo.toml index 340e39151b..bd78fcb122 100644 --- a/crates/lib/core/Cargo.toml +++ b/crates/lib/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-core-lib" -version = "0.29.0" +version = "0.30.0" description = "Miden VM core library" documentation = "https://docs.rs/miden-core-lib" readme = "README.md" diff --git a/crates/lib/core/asm/miden-project.toml b/crates/lib/core/asm/miden-project.toml index bec8bbb71f..4bf7e5c036 100644 --- a/crates/lib/core/asm/miden-project.toml +++ b/crates/lib/core/asm/miden-project.toml @@ -1,13 +1,13 @@ [package] name = "miden-core" -version = "0.29.0" +version = "0.30.0" [lib] namespace = "miden::core" path = "mod.masm" [dependencies] -miden-precompiles = { version = "0.29", linkage = "dynamic" } +miden-precompiles = { version = "0.30", linkage = "dynamic" } [profile.release] # Always produce debug information, as it can be stripped later by the VM diff --git a/crates/lifted-air/Cargo.toml b/crates/lifted-air/Cargo.toml index f66151035b..f3f74e7edc 100644 --- a/crates/lifted-air/Cargo.toml +++ b/crates/lifted-air/Cargo.toml @@ -7,7 +7,7 @@ name = "miden-lifted-air" readme.workspace = true repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] doctest = true diff --git a/crates/lifted-stark/Cargo.toml b/crates/lifted-stark/Cargo.toml index 207a8a8bee..3a4ec84af8 100644 --- a/crates/lifted-stark/Cargo.toml +++ b/crates/lifted-stark/Cargo.toml @@ -7,7 +7,7 @@ name = "miden-lifted-stark" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] doctest = false diff --git a/crates/mast-package/Cargo.toml b/crates/mast-package/Cargo.toml index 376b3efbee..943cbc80e7 100644 --- a/crates/mast-package/Cargo.toml +++ b/crates/mast-package/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-mast-package" -version = "0.29.0" +version = "0.30.0" description = "Package containing a compiled Miden MAST artifact with declared dependencies and exports" documentation = "https://docs.rs/miden-mast-package" readme = "README.md" diff --git a/crates/miden-constraint-compiler/Cargo.toml b/crates/miden-constraint-compiler/Cargo.toml index e186a2c8bb..de723d03f0 100644 --- a/crates/miden-constraint-compiler/Cargo.toml +++ b/crates/miden-constraint-compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-constraint-compiler" -version = "0.29.0" +version = "0.30.0" description = "Constraint compiler for Plonky3-based Miden AIRs." documentation = "https://docs.rs/miden-constraint-compiler" readme = "README.md" diff --git a/crates/miden-format/Cargo.toml b/crates/miden-format/Cargo.toml index 1bb31a4f83..42f90c63d9 100644 --- a/crates/miden-format/Cargo.toml +++ b/crates/miden-format/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-format" -version = "0.29.0" +version = "0.30.0" description = "Formatter for the Miden Assembly language" documentation = "https://docs.rs/miden-format" readme = "README.md" diff --git a/crates/package-registry-local/Cargo.toml b/crates/package-registry-local/Cargo.toml index 4a53cb0c5a..82c20a0803 100644 --- a/crates/package-registry-local/Cargo.toml +++ b/crates/package-registry-local/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-package-registry-local" -version = "0.29.0" +version = "0.30.0" description = "Filesystem-backed local package registry for Miden packages" documentation = "https://docs.rs/miden-package-registry-local" readme = "README.md" diff --git a/crates/package-registry/Cargo.toml b/crates/package-registry/Cargo.toml index 82cd29f715..ba21c9eefe 100644 --- a/crates/package-registry/Cargo.toml +++ b/crates/package-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-package-registry" -version = "0.29.0" +version = "0.30.0" description = "Package registry interfaces and dependency resolution for Miden packages" documentation = "https://docs.rs/miden-package-registry" readme = "README.md" diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index a9ab5b8bdf..4d239688ba 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-project" -version = "0.29.0" +version = "0.30.0" description = "Interface for working with Miden projects" documentation = "https://docs.rs/miden-project" readme = "README.md" diff --git a/crates/serde-utils/Cargo.toml b/crates/serde-utils/Cargo.toml index 6049d8c3cc..60d9237dae 100644 --- a/crates/serde-utils/Cargo.toml +++ b/crates/serde-utils/Cargo.toml @@ -10,7 +10,7 @@ name = "miden-serde-utils" readme = "README.md" repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [features] default = ["std"] diff --git a/crates/stark-transcript/Cargo.toml b/crates/stark-transcript/Cargo.toml index d4060b8523..91821a673b 100644 --- a/crates/stark-transcript/Cargo.toml +++ b/crates/stark-transcript/Cargo.toml @@ -7,7 +7,7 @@ name = "miden-stark-transcript" readme.workspace = true repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] doctest = false diff --git a/crates/stateful-hasher/Cargo.toml b/crates/stateful-hasher/Cargo.toml index b33e059c6c..3447db2433 100644 --- a/crates/stateful-hasher/Cargo.toml +++ b/crates/stateful-hasher/Cargo.toml @@ -7,7 +7,7 @@ name = "miden-stateful-hasher" readme.workspace = true repository.workspace = true rust-version.workspace = true -version = "0.29.0" +version = "0.30.0" [lib] doctest = false diff --git a/crates/test-serde-macros/Cargo.toml b/crates/test-serde-macros/Cargo.toml index 400a263170..11f758044d 100644 --- a/crates/test-serde-macros/Cargo.toml +++ b/crates/test-serde-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-test-serde-macros" -version = "0.29.0" +version = "0.30.0" description = "Proc macros for serde roundtrip testing in Miden VM" readme = "README.md" categories = ["development-tools::testing", "no-std"] diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index a8176e59a3..d2f06e2e52 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-test-utils" -version = "0.29.0" +version = "0.30.0" description = "Test utilities for Miden VM programs" readme = "README.md" categories = ["development-tools::testing", "no-std"] diff --git a/crates/utils-core-derive/Cargo.toml b/crates/utils-core-derive/Cargo.toml index 1b566c4291..773c32d045 100644 --- a/crates/utils-core-derive/Cargo.toml +++ b/crates/utils-core-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-utils-core-derive" -version = "0.29.0" +version = "0.30.0" description = "Proc macro to derive enum dispatch trait implementations on miden-core structs" readme = "README.md" categories = ["development-tools::procedural-macro-helpers", "no-std"] diff --git a/crates/utils-diagnostics/Cargo.toml b/crates/utils-diagnostics/Cargo.toml index d5000e481d..af48676ff7 100644 --- a/crates/utils-diagnostics/Cargo.toml +++ b/crates/utils-diagnostics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-utils-diagnostics" -version = "0.29.0" +version = "0.30.0" description = "Diagnostic infrastructure used in the Miden assembler and VM" documentation = "https://docs.rs/miden-utils-diagnostics" readme = "README.md" diff --git a/crates/utils-indexing/Cargo.toml b/crates/utils-indexing/Cargo.toml index 5e48c7fc6e..4ab193a31b 100644 --- a/crates/utils-indexing/Cargo.toml +++ b/crates/utils-indexing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-utils-indexing" -version = "0.29.0" +version = "0.30.0" description = "Type-safe u32-indexed vector utilities for Miden" readme = "README.md" categories = ["development-tools", "no-std"] diff --git a/crates/utils-sync/Cargo.toml b/crates/utils-sync/Cargo.toml index 16fef49d72..24b21f34cc 100644 --- a/crates/utils-sync/Cargo.toml +++ b/crates/utils-sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-utils-sync" -version = "0.29.0" +version = "0.30.0" description = "no-std compatible locking primitives for the Miden project" documentation = "https://docs.rs/miden-utils-sync" readme = "README.md" diff --git a/miden-vm/Cargo.toml b/miden-vm/Cargo.toml index 77f16fa8bd..a271afca25 100644 --- a/miden-vm/Cargo.toml +++ b/miden-vm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-vm" -version = "0.29.0" +version = "0.30.0" description = "Miden virtual machine" documentation = "https://docs.rs/miden-vm" readme = "README.md" diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 5458cbfa2c..118bf2da9e 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-processor" -version = "0.29.0" +version = "0.30.0" description = "Miden VM processor" documentation = "https://docs.rs/miden-processor" readme = "README.md" diff --git a/prover/Cargo.toml b/prover/Cargo.toml index b61c2eb765..fa70089b49 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-prover" -version = "0.29.0" +version = "0.30.0" description = "Miden VM prover" documentation = "https://docs.rs/miden-prover" readme = "README.md" diff --git a/tools/miden-core-fuzz/Cargo.lock b/tools/miden-core-fuzz/Cargo.lock index 89120cafec..7d4afb1fa8 100644 --- a/tools/miden-core-fuzz/Cargo.lock +++ b/tools/miden-core-fuzz/Cargo.lock @@ -717,7 +717,7 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miden-assembly" -version = "0.29.0" +version = "0.30.0" dependencies = [ "log", "miden-assembly-syntax", @@ -732,7 +732,7 @@ dependencies = [ [[package]] name = "miden-assembly-syntax" -version = "0.29.0" +version = "0.30.0" dependencies = [ "log", "miden-assembly-syntax-cst", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "miden-assembly-syntax-cst" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-debug-types", "miden-rowan", @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.29.0" +version = "0.30.0" dependencies = [ "derive_more", "log", @@ -792,7 +792,7 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.29.0" +version = "0.30.0" dependencies = [ "blake3", "cc", @@ -831,7 +831,7 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.29.0" +version = "0.30.0" dependencies = [ "quote", "syn 2.0.119", @@ -839,7 +839,7 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.29.0" +version = "0.30.0" dependencies = [ "memchr", "miden-crypto", @@ -857,7 +857,7 @@ dependencies = [ [[package]] name = "miden-field" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-serde-utils", "num-bigint 0.5.1", @@ -883,7 +883,7 @@ dependencies = [ [[package]] name = "miden-lifted-air" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-air", "p3-challenger", @@ -895,7 +895,7 @@ dependencies = [ [[package]] name = "miden-lifted-stark" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-lifted-air", "miden-stark-transcript", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "miden-mast-package" -version = "0.29.0" +version = "0.30.0" dependencies = [ "hashbrown", "log", @@ -968,7 +968,7 @@ dependencies = [ [[package]] name = "miden-package-registry" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -982,7 +982,7 @@ dependencies = [ [[package]] name = "miden-project" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-assembly-syntax", "miden-core", @@ -1007,7 +1007,7 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-goldilocks", @@ -1016,7 +1016,7 @@ dependencies = [ [[package]] name = "miden-stark-transcript" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-challenger", "p3-field", @@ -1026,7 +1026,7 @@ dependencies = [ [[package]] name = "miden-stateful-hasher" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-symmetric", @@ -1034,7 +1034,7 @@ dependencies = [ [[package]] name = "miden-utils-core-derive" -version = "0.29.0" +version = "0.30.0" dependencies = [ "proc-macro2", "quote", @@ -1043,7 +1043,7 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-debug-types", "miden-miette", @@ -1052,7 +1052,7 @@ dependencies = [ [[package]] name = "miden-utils-indexing" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-serde-utils", "proptest", @@ -1062,7 +1062,7 @@ dependencies = [ [[package]] name = "miden-utils-sync" -version = "0.29.0" +version = "0.30.0" dependencies = [ "lock_api", "loom", diff --git a/tools/miden-crypto-fuzz/Cargo.lock b/tools/miden-crypto-fuzz/Cargo.lock index ce60c399dd..ecf4867284 100644 --- a/tools/miden-crypto-fuzz/Cargo.lock +++ b/tools/miden-crypto-fuzz/Cargo.lock @@ -513,7 +513,7 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.29.0" +version = "0.30.0" dependencies = [ "blake3", "cc", @@ -553,7 +553,7 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.29.0" +version = "0.30.0" dependencies = [ "quote", "syn 2.0.119", @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "miden-field" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-serde-utils", "num-bigint 0.5.1", @@ -586,7 +586,7 @@ dependencies = [ [[package]] name = "miden-lifted-air" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-air", "p3-challenger", @@ -598,7 +598,7 @@ dependencies = [ [[package]] name = "miden-lifted-stark" -version = "0.29.0" +version = "0.30.0" dependencies = [ "miden-lifted-air", "miden-stark-transcript", @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-goldilocks", @@ -628,7 +628,7 @@ dependencies = [ [[package]] name = "miden-stark-transcript" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-challenger", "p3-field", @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "miden-stateful-hasher" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-symmetric", diff --git a/tools/miden-serde-utils-fuzz/Cargo.lock b/tools/miden-serde-utils-fuzz/Cargo.lock index 71950bca25..ac86503707 100644 --- a/tools/miden-serde-utils-fuzz/Cargo.lock +++ b/tools/miden-serde-utils-fuzz/Cargo.lock @@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.29.0" +version = "0.30.0" dependencies = [ "p3-field", "p3-goldilocks", diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index 0a9c523e09..ec16ac1584 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-verifier" -version = "0.29.0" +version = "0.30.0" description = "Miden VM execution verifier" documentation = "https://docs.rs/miden-verifier" readme = "README.md" From 43acad4fdb007988b0c63b655cbbb3f892a60b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:48:39 -0400 Subject: [PATCH 02/17] fix: bump midenc-hir-type for serde 0.30 (#3472) midenc-hir-type 0.10.0 was published with a dependency on miden-serde-utils 0.29. Workspace builds on next masked that dependency because the local path crate inherited miden-serde-utils 0.30. Release dry-runs exclude the published 0.10.0 crate and resolve it from crates.io. That gives miden-mast-package incompatible 0.29 and 0.30 serialization trait identities. Bump midenc-hir-type to 0.11.0 so release verification stages a crate built against miden-serde-utils 0.30. The minor bump reflects the public serialization trait change. --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/midenc-hir-type/Cargo.toml | 2 +- tools/miden-core-fuzz/Cargo.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b004c1ccf4..32d7a5ac07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "midenc-hir-type" -version = "0.10.0" +version = "0.11.0" dependencies = [ "miden-formatting", "miden-serde-utils", diff --git a/Cargo.toml b/Cargo.toml index 5eff425cce..e14cc4709b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ miden-core-lib = { path = "./crates/lib/core", version = "0.30", defaul miden-serde-utils = { path = "./crates/serde-utils", version = "0.30", default-features = false } miden-stark-transcript = { path = "./crates/stark-transcript", version = "0.30", default-features = false } miden-stateful-hasher = { path = "./crates/stateful-hasher", version = "0.30", default-features = false } -midenc-hir-type = { path = "./crates/midenc-hir-type", version = "0.10", default-features = false } +midenc-hir-type = { path = "./crates/midenc-hir-type", version = "0.11", default-features = false } miden-utils-core-derive = { path = "./crates/utils-core-derive", version = "0.30", default-features = false } miden-utils-diagnostics = { path = "./crates/utils-diagnostics", version = "0.30", default-features = false } miden-utils-indexing = { path = "./crates/utils-indexing", version = "0.30", default-features = false } diff --git a/crates/midenc-hir-type/Cargo.toml b/crates/midenc-hir-type/Cargo.toml index 652b18773c..c78095dd8b 100644 --- a/crates/midenc-hir-type/Cargo.toml +++ b/crates/midenc-hir-type/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "midenc-hir-type" description = "Type system and utilities for Miden HIR" -version = "0.10.0" +version = "0.11.0" rust-version.workspace = true authors.workspace = true repository.workspace = true diff --git a/tools/miden-core-fuzz/Cargo.lock b/tools/miden-core-fuzz/Cargo.lock index 7d4afb1fa8..ec4e418bce 100644 --- a/tools/miden-core-fuzz/Cargo.lock +++ b/tools/miden-core-fuzz/Cargo.lock @@ -1072,7 +1072,7 @@ dependencies = [ [[package]] name = "midenc-hir-type" -version = "0.10.0" +version = "0.11.0" dependencies = [ "miden-formatting", "miden-serde-utils", From 772db481c366f5f97388fc32f239075cad9d9809 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:53:11 +0400 Subject: [PATCH 03/17] Cover same-clock access invariant (#3455) --- air/src/constraints/chiplets/memory.rs | 187 ++++++++++++++++-- docs/src/design/chiplets/memory.md | 2 +- processor/src/trace/chiplets/memory/mod.rs | 4 +- .../src/trace/chiplets/memory/segment.rs | 6 +- 4 files changed, 179 insertions(+), 20 deletions(-) diff --git a/air/src/constraints/chiplets/memory.rs b/air/src/constraints/chiplets/memory.rs index 882adb068e..7f830ba3c1 100644 --- a/air/src/constraints/chiplets/memory.rs +++ b/air/src/constraints/chiplets/memory.rs @@ -256,7 +256,7 @@ mod tests { use miden_core::{ Felt, - field::{PrimeCharacteristicRing, QuadFelt}, + field::{Field, PrimeCharacteristicRing, QuadFelt}, }; use miden_crypto::stark::{ air::{AirBuilder, ExtensionBuilder, PermutationAirBuilder, RowWindow}, @@ -395,13 +395,21 @@ mod tests { row.chiplets[19] = Felt::new_unchecked(hi); } - fn eval_memory_constraints(row: &ChipletCols) -> Vec { - let next = memory_row(); + fn eval_memory_window( + local: &ChipletCols, + next: &ChipletCols, + flags: &ChipletFlags, + ) -> Vec { let mut builder = ConstraintEvalBuilder::new(); - enforce_memory_constraints(&mut builder, row, &next, &memory_flags()); + enforce_memory_constraints(&mut builder, local, next, flags); builder.evaluations } + fn eval_memory_constraints(row: &ChipletCols) -> Vec { + let next = memory_row(); + eval_memory_window(row, &next, &memory_flags()) + } + fn assert_constraints_accept(row: &ChipletCols) { let evaluations = eval_memory_constraints(row); assert!( @@ -435,6 +443,167 @@ mod tests { assert_constraints_reject(&invalid); } + // SAME-CLOCK ACCESS TESTS + // ============================================================================================ + + #[derive(Clone, Copy, Debug)] + enum AccessType { + Read, + Write, + } + + impl AccessType { + fn is_read(self) -> Felt { + match self { + Self::Read => Felt::ONE, + Self::Write => Felt::ZERO, + } + } + } + + #[derive(Clone, Copy, Debug)] + enum AccessSize { + Element(u8), + Word, + } + + fn set_access_size(row: &mut ChipletCols, size: AccessSize) { + let cols = memory_cols(row); + match size { + AccessSize::Element(index) => { + assert!(index < 4, "element index must be in the range 0..4"); + cols.is_word = Felt::ZERO; + cols.idx0 = Felt::new_unchecked(u64::from(index & 1)); + cols.idx1 = Felt::new_unchecked(u64::from(index >> 1)); + }, + AccessSize::Word => { + cols.is_word = Felt::ONE; + cols.idx0 = Felt::ZERO; + cols.idx1 = Felt::ZERO; + }, + } + } + + fn same_key_memory_rows( + local_access: AccessType, + next_access: AccessType, + ) -> (ChipletCols, ChipletCols) { + let mut local = memory_row(); + let mut next = memory_row(); + let values = [ + Felt::new_unchecked(1), + Felt::new_unchecked(2), + Felt::new_unchecked(3), + Felt::new_unchecked(4), + ]; + + for (row, access) in [(&mut local, local_access), (&mut next, next_access)] { + let cols = memory_cols(row); + cols.is_read = access.is_read(); + cols.is_word = Felt::ONE; + cols.word_addr = Felt::new_unchecked(4); + cols.clk = Felt::new_unchecked(7); + cols.values = values; + } + memory_cols(&mut next).is_same_ctx_and_addr = Felt::ONE; + + // word_addr = 4 * (w0 + 2^16 * w1), with (w0, w1) = (1, 0). + set_word_addr_limbs(&mut local, 1, 0); + set_word_addr_limbs(&mut next, 1, 0); + + (local, next) + } + + fn eval_memory_transition( + local: &ChipletCols, + next: &ChipletCols, + ) -> Vec { + let flags = ChipletFlags { + is_active: Felt::ONE, + is_transition: Felt::ONE, + is_last: Felt::ZERO, + next_is_first: Felt::ZERO, + }; + eval_memory_window(local, next, &flags) + } + + /// Ensures that repeating `(ctx, word_addr, clk)` is valid only when both accesses are reads. + /// In particular, this prevents overlapping multi-memory operations such as `crypto_stream` + /// from reading and writing the same word in one clock cycle. + #[test] + fn same_key_memory_rows_allow_only_reads() { + for (local_access, next_access) in [ + (AccessType::Read, AccessType::Read), + (AccessType::Read, AccessType::Write), + (AccessType::Write, AccessType::Read), + (AccessType::Write, AccessType::Write), + ] { + let (local, next) = same_key_memory_rows(local_access, next_access); + let evaluations = eval_memory_transition(&local, &next); + let accepted = evaluations.iter().all(|value| *value == QuadFelt::ZERO); + let should_accept = + matches!((local_access, next_access), (AccessType::Read, AccessType::Read)); + assert_eq!( + accepted, should_accept, + "unexpected result for {local_access:?}/{next_access:?}", + ); + } + } + + /// Repeating `(ctx, word_addr, clk)` is safe for any combination of read access sizes. This is + /// required when operations such as `horner_eval_base` or ACE read multiple elements from the + /// same word in one clock cycle. + #[test] + fn same_key_memory_rows_allow_all_read_access_sizes() { + for (local_size, next_size) in [ + (AccessSize::Word, AccessSize::Word), + (AccessSize::Word, AccessSize::Element(2)), + (AccessSize::Element(1), AccessSize::Word), + (AccessSize::Element(0), AccessSize::Element(1)), + ] { + let (mut local, mut next) = same_key_memory_rows(AccessType::Read, AccessType::Read); + set_access_size(&mut local, local_size); + set_access_size(&mut next, next_size); + + let evaluations = eval_memory_transition(&local, &next); + assert!( + evaluations.iter().all(|value| *value == QuadFelt::ZERO), + "same-clock {local_size:?}/{next_size:?} reads should be accepted; got \ + {evaluations:?}", + ); + } + } + + /// Same-clock accesses at different word addresses may contain reads or writes. This covers the + /// two distinct word accesses emitted by operations such as `mem_stream` and `adv_pipe`. + #[test] + fn same_clock_memory_rows_at_different_addresses_allow_reads_and_writes() { + for (local_access, next_access) in [ + (AccessType::Read, AccessType::Read), + (AccessType::Read, AccessType::Write), + (AccessType::Write, AccessType::Read), + (AccessType::Write, AccessType::Write), + ] { + let (local, mut next) = same_key_memory_rows(local_access, next_access); + { + let next = memory_cols(&mut next); + next.word_addr = Felt::new_unchecked(8); + next.values = [Felt::ZERO; 4]; + next.d0 = Felt::new_unchecked(4); + next.d_inv = Felt::new_unchecked(4).inverse(); + next.is_same_ctx_and_addr = Felt::ZERO; + } + set_word_addr_limbs(&mut next, 2, 0); + + let evaluations = eval_memory_transition(&local, &next); + assert!( + evaluations.iter().all(|value| *value == QuadFelt::ZERO), + "same-clock accesses to different words should be accepted for \ + {local_access:?}/{next_access:?}; got {evaluations:?}", + ); + } + } + // EMPTY-SECTION BOUNDARY REGRESSION TESTS // ============================================================================================ // The memory chiplet's first-row initialization ("values not being written must be zero") is @@ -463,16 +632,6 @@ mod tests { build_chiplet_selectors(&mut builder, local, next).memory } - fn eval_memory_window( - local: &ChipletCols, - next: &ChipletCols, - flags: &ChipletFlags, - ) -> Vec { - let mut builder = ConstraintEvalBuilder::new(); - enforce_memory_constraints(&mut builder, local, next, flags); - builder.evaluations - } - #[test] fn memory_first_row_init_enforced_when_bitwise_empty() { // Empty-bitwise layout: the memory section begins directly after a controller row, so the diff --git a/docs/src/design/chiplets/memory.md b/docs/src/design/chiplets/memory.md index 772d338aab..c796046bdd 100644 --- a/docs/src/design/chiplets/memory.md +++ b/docs/src/design/chiplets/memory.md @@ -182,7 +182,7 @@ where: - `idx0` and `idx1` are selector columns used to identify which element in the word is being accessed. Specifically, the index within the word is computed as `idx1 * 2 + idx0`. - However, when `ew` is set to $1$ (indicating that a word is accessed), these columns are meaningless and are set to $0$. - `clk` contains clock cycle at which the memory operation happened. Values in this column must increase monotonically for a given context and memory word but there can be gaps between two consecutive values of up to $2^{32}$. - - When the context and word address are unchanged, `clk` may stay the same, but only read operations are allowed. + - When the context and word address are unchanged, `clk` may stay the same, but only read operations are allowed. This includes any combination of word and element reads; a write may not share the same `(ctx, word_addr, clk)` tuple with any other access. - `v0, v1, v2, v3` columns contain field elements stored at a given context/word/clock cycle after the memory operation. - Columns `d0` and `d1` contain lower and upper $16$ bits of the delta between two consecutive context IDs, addresses, or clock cycles. Specifically: - When the context changes within a frame, these columns contain $(ctx' - ctx)$ in the "next" row. diff --git a/processor/src/trace/chiplets/memory/mod.rs b/processor/src/trace/chiplets/memory/mod.rs index f302168f4b..76657054eb 100644 --- a/processor/src/trace/chiplets/memory/mod.rs +++ b/processor/src/trace/chiplets/memory/mod.rs @@ -165,7 +165,7 @@ impl Memory { /// /// # Errors /// - Returns an error if the address is equal or greater than 2^32. - /// - Returns an error if the same address is accessed more than once in the same clock cycle. + /// - Returns an error if the addressed word was previously written in the same clock cycle. pub fn read(&mut self, ctx: ContextId, addr: Felt, clk: RowIndex) -> Result { let addr: u32 = addr .as_canonical_u64() @@ -183,7 +183,7 @@ impl Memory { /// # Errors /// - Returns an error if the address is equal or greater than 2^32. /// - Returns an error if the address is not aligned to a word boundary. - /// - Returns an error if the same address is accessed more than once in the same clock cycle. + /// - Returns an error if the addressed word was previously written in the same clock cycle. pub fn read_word( &mut self, ctx: ContextId, diff --git a/processor/src/trace/chiplets/memory/segment.rs b/processor/src/trace/chiplets/memory/segment.rs index 84c7d63b7b..d3db2bccaa 100644 --- a/processor/src/trace/chiplets/memory/segment.rs +++ b/processor/src/trace/chiplets/memory/segment.rs @@ -114,7 +114,7 @@ impl MemorySegmentTrace { /// If the element at the specified address hasn't been previously written to, ZERO is returned. /// /// # Errors - /// - Returns an error if the same address is accessed more than once in the same clock cycle. + /// - Returns an error if the addressed word was previously written in the same clock cycle. pub fn read(&mut self, ctx: ContextId, addr: u32, clk: Felt) -> Result { let (word_addr, addr_idx_in_word) = addr_to_word_addr_and_idx(addr); @@ -138,7 +138,7 @@ impl MemorySegmentTrace { /// - Assumes that the address is word aligned. /// /// # Errors - /// - Returns an error if the same address is accessed more than once in the same clock cycle. + /// - Returns an error if the addressed word was previously written in the same clock cycle. pub fn read_word( &mut self, ctx: ContextId, @@ -283,7 +283,7 @@ impl MemorySegmentTrace { /// was read. /// /// # Errors - /// - Returns an error if the same address is accessed more than once in the same clock cycle. + /// - Returns an error if the addressed word was previously written in the same clock cycle. fn read_word_helper( &mut self, ctx: ContextId, From e960fe303e1dfeab45bfee75332ed3dd4f12fa43 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:19:49 +0400 Subject: [PATCH 04/17] refactor(pvm): Reduce the precompile STARK relation to 10 AIRs (#3464) Merge the chunk-node and Keccak-sponge AIRs, and merge the EC point and group-store AIRs. Share their constraint evaluators while placing each component in a separate column band. Derive the ACE quotient arity from the relation, correcting it from eight chunks to four. --- CHANGELOG.md | 4 + crates/lifted-stark/src/lib.rs | 7 +- crates/precompiles-prover/src/ace.rs | 112 +- crates/precompiles-prover/src/ec/add/mod.rs | 7 +- crates/precompiles-prover/src/ec/groups.rs | 120 +- crates/precompiles-prover/src/ec/mod.rs | 418 +++--- .../src/ec/point_store_groups/mod.rs | 115 ++ .../src/ec/point_store_groups/trace.rs | 49 + crates/precompiles-prover/src/ec/trace.rs | 29 +- .../precompiles-prover/src/hash/chunk/mod.rs | 2 +- .../src/hash/chunk_node/mod.rs | 937 ++++++------- .../src/hash/chunk_node/trace.rs | 63 - .../src/hash/chunk_node_sponge/mod.rs | 122 ++ .../src/hash/chunk_node_sponge/trace.rs | 78 ++ .../src/hash/keccak/node/mod.rs | 2 +- .../src/hash/keccak/round/mod.rs | 15 +- .../src/hash/keccak/sponge/mod.rs | 1204 +++++++++-------- .../src/hash/keccak/sponge/trace.rs | 26 +- crates/precompiles-prover/src/hash/mod.rs | 1 + crates/precompiles-prover/src/relations.rs | 10 +- crates/precompiles-prover/src/session/mod.rs | 47 +- .../src/session/preprocessed_cache.rs | 4 +- .../precompiles-prover/src/session/prove.rs | 76 +- ...e_relation_digest_matches_current_air.snap | 8 +- crates/precompiles-prover/src/stark_config.rs | 20 +- .../src/tests/bus_balance.rs | 17 +- .../src/tests/deferred_state.rs | 72 +- crates/precompiles-prover/src/tests/ec_add.rs | 66 +- crates/precompiles-prover/src/tests/ec_dag.rs | 12 +- crates/precompiles-prover/src/tests/ec_msm.rs | 2 +- crates/precompiles-prover/src/tests/mod.rs | 9 +- .../precompiles-prover/src/tests/uint_dag.rs | 18 +- .../precompiles-prover/src/tests/vm_uint.rs | 2 +- .../src/transcript/eval/mod.rs | 23 +- 34 files changed, 2079 insertions(+), 1618 deletions(-) create mode 100644 crates/precompiles-prover/src/ec/point_store_groups/mod.rs create mode 100644 crates/precompiles-prover/src/ec/point_store_groups/trace.rs delete mode 100644 crates/precompiles-prover/src/hash/chunk_node/trace.rs create mode 100644 crates/precompiles-prover/src/hash/chunk_node_sponge/mod.rs create mode 100644 crates/precompiles-prover/src/hash/chunk_node_sponge/trace.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 925d7f32fc..dc70e8a7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## v0.30.0 (Unreleased) +#### Changes + +- [BREAKING] Reduced the precompile STARK relation from 12 AIRs to 10 by merging the chunk/node/sponge and EC point/group stores ([#3464](https://github.com/0xMiden/miden-vm/pull/3464)). + ## v0.29.0 (2026-08-04) #### Changes diff --git a/crates/lifted-stark/src/lib.rs b/crates/lifted-stark/src/lib.rs index e7aaa49aa8..4f31451fa1 100644 --- a/crates/lifted-stark/src/lib.rs +++ b/crates/lifted-stark/src/lib.rs @@ -78,9 +78,10 @@ pub mod verifier; pub use config::{GenericStarkConfig, StarkConfig}; pub use debug::check_constraints; -// `domain` and `order` are internal modules, but these error types surface through the public -// `ProverError` / `VerifierError`, so they need a public path of their own. -pub use domain::DomainError; +// `domain` and `order` are internal modules. Their error types surface through the public +// `ProverError` / `VerifierError`, and quotient-degree derivation is part of the relation +// configuration contract, so these items need public paths of their own. +pub use domain::{DomainError, log_quotient_degree}; pub use order::ShapeError; pub use preprocessed::{Preprocessed, PreprocessedValidationError}; pub use prover::{ProverError, ProverInstance}; diff --git a/crates/precompiles-prover/src/ace.rs b/crates/precompiles-prover/src/ace.rs index 0a6fa14b83..5898c5471e 100644 --- a/crates/precompiles-prover/src/ace.rs +++ b/crates/precompiles-prover/src/ace.rs @@ -7,50 +7,122 @@ use alloc::vec::Vec; -use miden_ace_codegen::{AceCircuit, AceConfig, AceError, build_multi_air_ace_circuit}; -use miden_core::field::QuadFelt; +use miden_ace_codegen::{AceCircuit, AceConfig, AceError, LayoutKind, build_multi_air_ace_circuit}; +use miden_core::{Felt, field::QuadFelt}; -use crate::session::ChipletAir; +use crate::session::{ChipletAir, NUM_CHIPLETS}; // MULTI-AIR ACE CIRCUIT // ================================================================================================ +/// Per-AIR trace regions are padded to this width before concatenation, matching the LMCS wire +/// alignment used by the commitment scheme. +const LMCS_ALIGNMENT: usize = 8; + +/// Number of quotient chunks the precompile relation commits to. +/// +/// The lifted STARK verifier derives this quantity symbolically from the AIRs. Deriving it through +/// the same implementation keeps the ACE circuit's READ layout coupled to the proof protocol. +fn num_quotient_chunks() -> usize { + let max_log_quotient_degree = ChipletAir::all() + .iter() + .map(miden_lifted_stark::log_quotient_degree::) + .max() + .expect("the chiplet stack is non-empty"); + 1usize << max_log_quotient_degree +} + +/// ACE codegen settings for the precompile chiplet relation. +fn precompile_ace_config() -> AceConfig { + AceConfig { + num_quotient_chunks: num_quotient_chunks(), + layout: LayoutKind::Masm, + num_airs: NUM_CHIPLETS, + } +} + /// Builds the ACE circuit for the precompile chiplet multi-AIR relation. /// /// The circuit uses the stable [`ChipletAir::all`] instance order as its canonical ACE fold order /// and aligns trace regions to eight base-field elements. These choices define the committed ACE /// encoding; they do not prescribe the lifted STARK proof order. The cross-chiplet LogUp identity /// is checked separately by `ChipletMultiAir::eval_external`. -pub fn build_precompile_multi_air_ace_circuit( - config: AceConfig, -) -> Result, AceError> { - const LMCS_ALIGNMENT: usize = 8; - +pub fn build_precompile_multi_air_ace_circuit() -> Result, AceError> { let airs = ChipletAir::all(); let proof_order: Vec<_> = (0..airs.len()).collect(); - build_multi_air_ace_circuit(&airs, &proof_order, config, LMCS_ALIGNMENT) + build_multi_air_ace_circuit::( + &airs, + &proof_order, + precompile_ace_config(), + LMCS_ALIGNMENT, + ) } #[cfg(test)] mod tests { - use miden_ace_codegen::{AceConfig, LayoutKind}; + use alloc::{format, string::String, vec::Vec}; + + use miden_core::{Felt, field::QuadFelt}; - use super::build_precompile_multi_air_ace_circuit; - use crate::session::NUM_CHIPLETS; + use super::{build_precompile_multi_air_ace_circuit, precompile_ace_config}; + use crate::session::{ChipletAir, NUM_CHIPLETS}; #[test] fn precompile_multi_air_ace_circuit_builds() { - let config = AceConfig { - num_quotient_chunks: 8, - layout: LayoutKind::Masm, - num_airs: NUM_CHIPLETS, - }; - - let circuit = build_precompile_multi_air_ace_circuit(config) - .expect("precompile multi-AIR ACE circuit"); + let circuit = + build_precompile_multi_air_ace_circuit().expect("precompile multi-AIR ACE circuit"); assert_eq!(circuit.layout().counts.num_public, crate::logup::NUM_PUBLIC_VALUES); assert_eq!(circuit.layout().counts.num_aux_boundary, NUM_CHIPLETS); assert!(circuit.layout().counts.preprocessed_width >= 8); } + + /// Pin the complete quotient-degree vector, not merely its maximum: otherwise a chiplet could + /// drift between degrees while another chiplet kept the relation-wide maximum unchanged. + #[test] + fn quotient_chunks_match_the_symbolic_derivation() { + const EXPECTED: [(&str, u8); NUM_CHIPLETS] = [ + ("ChunkNodeSponge", 2), + ("Poseidon2", 2), + ("KeccakRound", 2), + ("BytePairLut", 1), + ("TranscriptEval", 1), + ("UintStoreMul", 1), + ("UintAdd", 1), + ("EcPointStoreGroups", 1), + ("EcGroupAdd", 1), + ("EcMsm", 1), + ]; + + let derived: Vec<(String, u8)> = ChipletAir::all() + .iter() + .map(|air| { + ( + format!("{air:?}"), + miden_lifted_stark::log_quotient_degree::(air), + ) + }) + .collect(); + let expected: Vec<(String, u8)> = + EXPECTED.iter().map(|(name, degree)| ((*name).into(), *degree)).collect(); + assert_eq!( + derived, expected, + "a chiplet's quotient degree moved; if intended, re-mint the relation digest" + ); + + let max = derived.iter().map(|(_, degree)| *degree).max().expect("non-empty stack"); + let expected_chunks = 1usize << max; + assert_eq!( + precompile_ace_config().num_quotient_chunks, + expected_chunks, + "the ACE circuit must read exactly the quotient chunks the proof carries" + ); + let circuit = + build_precompile_multi_air_ace_circuit().expect("precompile multi-AIR ACE circuit"); + assert_eq!( + circuit.layout().counts.num_quotient_chunks, + expected_chunks, + "the built circuit must preserve the derived quotient arity" + ); + } } diff --git a/crates/precompiles-prover/src/ec/add/mod.rs b/crates/precompiles-prover/src/ec/add/mod.rs index 38a650b5cc..03589b204b 100644 --- a/crates/precompiles-prover/src/ec/add/mod.rs +++ b/crates/precompiles-prover/src/ec/add/mod.rs @@ -829,9 +829,10 @@ where ); // col 11: the result-membership cert provide, alone. −1 per mint // op (negative ⇒ provide), naming the fresh result `r` and its - // group. Consumed by `r`'s point-store row (`EcPointStore`), - // discharging its on-curve obligation without the MAC trio; the - // bus balances because a fresh result is minted by exactly one op. + // group. Consumed by `r`'s point-store row (the point band of + // `EcPointStoreGroupsAir`), discharging its on-curve obligation + // without the MAC trio; the bus balances because a fresh result + // is minted by exactly one op. let cert_group: LB::Expr = local[CELL_GROUP].into(); let cert_r: LB::Expr = local[CELL_R].into(); frac_col!( diff --git a/crates/precompiles-prover/src/ec/groups.rs b/crates/precompiles-prover/src/ec/groups.rs index f7f54dc33d..6fd1d0faef 100644 --- a/crates/precompiles-prover/src/ec/groups.rs +++ b/crates/precompiles-prover/src/ec/groups.rs @@ -74,9 +74,9 @@ pub const COL_MULT: usize = 5; pub const NUM_MAIN_COLS: usize = 6; // Aux: the single LogUp running-sum column (one fraction). -const NUM_LOGUP_COLS: usize = 1; +pub(crate) const NUM_LOGUP_COLS: usize = 1; const AUX_WIDTH: usize = 1; -const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = [1]; +pub(crate) const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = [1]; // AIR // ================================================================================================ @@ -118,18 +118,7 @@ impl LiftedAir for EcGroupsAir { } fn eval>(&self, builder: &mut AB) { - let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0); - - let ptr: AB::Expr = local[COL_PTR].into(); - let ptr_next: AB::Expr = next[COL_PTR].into(); - - // The ungated chain: ptr = row + 1 for every prover, pads - // included (they are just mult = 0 rows), so ptr → tuple is - // injective by construction. The wrap edge is dropped, keeping - // the cyclic last → first transition free. - builder.when_transition().assert_zero(ptr_next - ptr.clone() - AB::Expr::ONE); - builder.when_first_row().assert_zero(ptr - AB::Expr::ONE); + eval_main(builder, 0); // Phase 2: LogUp. let mut lb = @@ -138,6 +127,25 @@ impl LiftedAir for EcGroupsAir { } } +/// Evaluate this component's base constraints in a main-trace column band. +pub(crate) fn eval_main(builder: &mut AB, main_col_offset: usize) +where + AB: LiftedAirBuilder, +{ + let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), main_col_offset); + + let ptr: AB::Expr = local[COL_PTR].into(); + let ptr_next: AB::Expr = next[COL_PTR].into(); + + // The ungated chain: ptr = row + 1 for every prover, pads + // included (they are just mult = 0 rows), so ptr → tuple is + // injective by construction. The wrap edge is dropped, keeping + // the cyclic last → first transition free. + builder.when_transition().assert_zero(ptr_next - ptr.clone() - AB::Expr::ONE); + builder.when_first_row().assert_zero(ptr - AB::Expr::ONE); +} + // LOOKUP AIR // ================================================================================================ @@ -162,43 +170,51 @@ where } fn eval(&self, builder: &mut LB) { - let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - - // Pads zero the mult cell, so the provide needs no act gate. - let neg_mult: LB::Expr = LB::Expr::ZERO - local[COL_MULT].into(); - - let provide_deg = Deg { v: 1, u: 1 }; - let col_deg = Deg { v: 1, u: 1 }; - - builder.next_column( - |col| { - col.group( - "ec-groups", - |g| { - g.batch( - "ec-groups-fractions", - LB::Expr::ONE, - |b| { - b.insert( - "provide-ecgroup", - neg_mult, - EcGroupMsg { - group_ptr: local[COL_PTR].into(), - a_ptr: local[COL_A_PTR].into(), - b_ptr: local[COL_B_PTR].into(), - bound_ptr: local[COL_BOUND_PTR].into(), - scalar_bound_ptr: local[COL_SBOUND_PTR].into(), - }, - provide_deg, - ); - }, - col_deg, - ); - }, - col_deg, - ); - }, - col_deg, - ); + eval_lookups(builder, 0); } } + +/// Evaluate this component's LogUp columns in a main-trace column band. +pub(crate) fn eval_lookups(builder: &mut LB, main_col_offset: usize) +where + LB: LookupBuilder, +{ + let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + + // Pads zero the mult cell, so the provide needs no act gate. + let neg_mult: LB::Expr = LB::Expr::ZERO - local[COL_MULT].into(); + + let provide_deg = Deg { v: 1, u: 1 }; + let col_deg = Deg { v: 1, u: 1 }; + + builder.next_column( + |col| { + col.group( + "ec-groups", + |g| { + g.batch( + "ec-groups-fractions", + LB::Expr::ONE, + |b| { + b.insert( + "provide-ecgroup", + neg_mult, + EcGroupMsg { + group_ptr: local[COL_PTR].into(), + a_ptr: local[COL_A_PTR].into(), + b_ptr: local[COL_B_PTR].into(), + bound_ptr: local[COL_BOUND_PTR].into(), + scalar_bound_ptr: local[COL_SBOUND_PTR].into(), + }, + provide_deg, + ); + }, + col_deg, + ); + }, + col_deg, + ); + }, + col_deg, + ); +} diff --git a/crates/precompiles-prover/src/ec/mod.rs b/crates/precompiles-prover/src/ec/mod.rs index 055ef732c7..5f13e178cf 100644 --- a/crates/precompiles-prover/src/ec/mod.rs +++ b/crates/precompiles-prover/src/ec/mod.rs @@ -1,12 +1,11 @@ //! EC layer — short-Weierstrass groups and points over the uint layer. //! -//! Three chiplets, two stores and a relation: +//! The production relation packs the two binding stores into one chiplet and keeps group addition +//! separate: //! -//! - [`groups::EcGroupsAir`] — the **group table**: one row per group binding `group_ptr → (a, b, -//! bound, scalar_bound)`; *provides* [`EcGroup`](crate::relations::BusId::EcGroup). -//! - [`EcPointStoreAir`] (this module) — the **point store**: one row per point; *provides* -//! [`EcPoint`](crate::relations::BusId::EcPoint), *consumes* its group's tuple and — unless -//! `is_pai` — the curve-membership MAC trio. +//! - [`point_store_groups::EcPointStoreGroupsAir`] contains the **group table** and **point store** +//! in disjoint column bands over one row range. The standalone [`groups::EcGroupsAir`] and +//! [`EcPointStoreAir`] implementations remain as component AIRs for isolated tests. //! - [`add::EcGroupAddAir`] — the complete group-law addition over the two stores. //! //! Both stores are **binding stores**, deliberately the thinnest @@ -52,6 +51,7 @@ pub mod add; pub mod groups; pub mod msm; +pub mod point_store_groups; pub mod require; pub mod trace; @@ -192,9 +192,9 @@ pub const NUM_MAIN_COLS: usize = 14; // on-curve-cert consume (col 1), and the three trio MAC consumes — each // degree 3, so each sits alone (cols 2-4). The trio and the cert are // mutually-exclusive membership modes. -const NUM_LOGUP_COLS: usize = 5; +pub(crate) const NUM_LOGUP_COLS: usize = 5; const AUX_WIDTH: usize = 5; -const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = [1, 2, 1, 1, 1]; +pub(crate) const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = [1, 2, 1, 1, 1]; // AIR // ================================================================================================ @@ -236,52 +236,7 @@ impl LiftedAir for EcPointStoreAir { } fn eval>(&self, builder: &mut AB) { - let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0); - - let is_pai: AB::Expr = local[COL_IS_PAI].into(); - let is_cert: AB::Expr = local[COL_IS_CERT].into(); - let act: AB::Expr = local[COL_ACT].into(); - let act_next: AB::Expr = next[COL_ACT].into(); - let ptr: AB::Expr = local[COL_PTR].into(); - let ptr_next: AB::Expr = next[COL_PTR].into(); - - // Booleanity. - builder.assert_zero(is_pai.clone() * (AB::Expr::ONE - is_pai.clone())); - builder.assert_zero(is_cert.clone() * (AB::Expr::ONE - is_cert.clone())); - builder.assert_zero(act.clone() * (AB::Expr::ONE - act.clone())); - // A cert point is finite — the two membership modes are exclusive. - builder.assert_zero(is_pai.clone() * is_cert.clone()); - - // PAI rows reference no uints: coordinate / transient ptrs are the - // none-sentinel. - for col in [COL_X_PTR, COL_Y_PTR, COL_U_PTR, COL_W_PTR] { - let cell: AB::Expr = local[col].into(); - builder.assert_zero(is_pai.clone() * cell); - } - // Cert rows carry real coordinates but no MAC transients — the trio - // ptrs are the none-sentinel (the cert discharges membership instead). - for col in [COL_U_PTR, COL_W_PTR] { - let cell: AB::Expr = local[col].into(); - builder.assert_zero(is_cert.clone() * cell); - } - // Inactive rows cannot provide phantom EcPoint tuples: their group and - // membership consumes are act-gated, while the provide self-gates via - // this multiplicity cell. - let point_mult: AB::Expr = local[COL_ECPOINT_MULT].into(); - builder.assert_zero((AB::Expr::ONE - act.clone()) * point_mult); - - // act is monotone (pads only at the tail; the wrap is dropped so - // the cyclic last → first edge stays free)… - builder - .when_transition() - .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); - // …and ptrs are consecutive along the active prefix, starting at 1 - // (an all-pad trace starts at 0). - builder - .when_transition() - .assert_zero(act_next * (ptr_next - ptr.clone() - AB::Expr::ONE)); - builder.when_first_row().assert_zero(ptr - act); + eval_point_store_main(builder, 0); // Phase 2: LogUp. let mut lb = @@ -290,6 +245,59 @@ impl LiftedAir for EcPointStoreAir { } } +/// Evaluate this component's base constraints in a main-trace column band. +pub(crate) fn eval_point_store_main(builder: &mut AB, main_col_offset: usize) +where + AB: LiftedAirBuilder, +{ + let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), main_col_offset); + + let is_pai: AB::Expr = local[COL_IS_PAI].into(); + let is_cert: AB::Expr = local[COL_IS_CERT].into(); + let act: AB::Expr = local[COL_ACT].into(); + let act_next: AB::Expr = next[COL_ACT].into(); + let ptr: AB::Expr = local[COL_PTR].into(); + let ptr_next: AB::Expr = next[COL_PTR].into(); + + // Booleanity. + builder.assert_zero(is_pai.clone() * (AB::Expr::ONE - is_pai.clone())); + builder.assert_zero(is_cert.clone() * (AB::Expr::ONE - is_cert.clone())); + builder.assert_zero(act.clone() * (AB::Expr::ONE - act.clone())); + // A cert point is finite — the two membership modes are exclusive. + builder.assert_zero(is_pai.clone() * is_cert.clone()); + + // PAI rows reference no uints: coordinate / transient ptrs are the + // none-sentinel. + for col in [COL_X_PTR, COL_Y_PTR, COL_U_PTR, COL_W_PTR] { + let cell: AB::Expr = local[col].into(); + builder.assert_zero(is_pai.clone() * cell); + } + // Cert rows carry real coordinates but no MAC transients — the trio + // ptrs are the none-sentinel (the cert discharges membership instead). + for col in [COL_U_PTR, COL_W_PTR] { + let cell: AB::Expr = local[col].into(); + builder.assert_zero(is_cert.clone() * cell); + } + // Inactive rows cannot provide phantom EcPoint tuples: their group and + // membership consumes are act-gated, while the provide self-gates via + // this multiplicity cell. + let point_mult: AB::Expr = local[COL_ECPOINT_MULT].into(); + builder.assert_zero((AB::Expr::ONE - act.clone()) * point_mult); + + // act is monotone (pads only at the tail; the wrap is dropped so + // the cyclic last → first edge stays free)… + builder + .when_transition() + .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); + // …and ptrs are consecutive along the active prefix, starting at 1 + // (an all-pad trace starts at 0). + builder + .when_transition() + .assert_zero(act_next * (ptr_next - ptr.clone() - AB::Expr::ONE)); + builder.when_first_row().assert_zero(ptr - act); +} + // LOOKUP AIR // ================================================================================================ @@ -314,152 +322,160 @@ where } fn eval(&self, builder: &mut LB) { - let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - - let ptr: LB::Expr = local[COL_PTR].into(); - let group_ptr: LB::Expr = local[COL_GROUP_PTR].into(); - let a_ptr: LB::Expr = local[COL_A_PTR].into(); - let b_ptr: LB::Expr = local[COL_B_PTR].into(); - let bound_ptr: LB::Expr = local[COL_BOUND_PTR].into(); - let sbound_ptr: LB::Expr = local[COL_SBOUND_PTR].into(); - let x_ptr: LB::Expr = local[COL_X_PTR].into(); - let y_ptr: LB::Expr = local[COL_Y_PTR].into(); - let u_ptr: LB::Expr = local[COL_U_PTR].into(); - let w_ptr: LB::Expr = local[COL_W_PTR].into(); - let is_pai: LB::Expr = local[COL_IS_PAI].into(); - let is_cert: LB::Expr = local[COL_IS_CERT].into(); - let act: LB::Expr = local[COL_ACT].into(); - - // Pads zero the mult cell, so the provide needs no act gate; the - // consumes do (an all-zero pad row must touch no bus). The trio fires - // on finite, non-cert rows; the cert consume on finite cert rows — - // disjoint, partitioning a finite point's one membership obligation. - let neg_mult: LB::Expr = LB::Expr::ZERO - local[COL_ECPOINT_MULT].into(); - let member_flag: LB::Expr = - act.clone() * (LB::Expr::ONE - is_pai.clone()) * (LB::Expr::ONE - is_cert.clone()); - let cert_flag: LB::Expr = act.clone() * is_cert; - - let one: LB::Expr = LB::Expr::ONE; - let zero: LB::Expr = LB::Expr::ZERO; - - let provide_deg = Deg { v: 1, u: 1 }; - let consume_deg = Deg { v: 1, u: 1 }; - let member_deg = Deg { v: 3, u: 1 }; - let cert_deg = Deg { v: 2, u: 1 }; - let single_deg = Deg { v: 1, u: 2 }; - let paired_deg = Deg { v: 3, u: 2 }; - - // col 0: the point binding, alone — the gated running-sum anchor. - frac_col!( - builder, - "ec-points", - single_deg, - ( - "provide-ecpoint", - neg_mult, - EcPointMsg { - point_ptr: ptr.clone(), - group_ptr: group_ptr.clone(), - x_ptr: x_ptr.clone(), - y_ptr: y_ptr.clone(), - is_pai: is_pai.clone(), - }, - provide_deg - ), - ); - // col 1 (paired, lqd-1): the group binding consume (forcing - // group_ptr onto a real group row and the a/b/bound/sbound cells - // onto its context — for PAI rows the only tie to a real group) - // paired with the closure-cert consume (a fresh group-law - // result's on-curve membership, discharged in place of the trio). - frac_col!( - builder, - "ec-points", - paired_deg, - ( - "consume-ecgroup", - act, - EcGroupMsg { - group_ptr: group_ptr.clone(), - a_ptr: a_ptr.clone(), - b_ptr: b_ptr.clone(), - bound_ptr: bound_ptr.clone(), - scalar_bound_ptr: sbound_ptr.clone(), - }, - consume_deg - ), - ( - "consume-ecgroupadd-cert", - cert_flag, - EcOnCurveCertMsg { - group_ptr: group_ptr.clone(), - r_ptr: ptr.clone() - }, - cert_deg - ), - ); - // cols 2-4: the curve-membership MAC trio (shared r_ptr = w makes - // y² = x³ + ax + b an identity of stored values), each degree 3 - // so each sits alone. - frac_col!( - builder, - "ec-points", - single_deg, - ( - "consume-mac-u", - member_flag.clone(), - UintMulMsg { - kappa_a: one.clone(), - kappa_c: one.clone(), - a_ptr: x_ptr.clone(), - b_ptr: x_ptr.clone(), - c_ptr: a_ptr.clone(), - r_ptr: u_ptr.clone(), - bound_ptr: bound_ptr.clone(), - is_sub: LB::Expr::ZERO, - }, - member_deg - ), - ); - frac_col!( - builder, - "ec-points", - single_deg, - ( - "consume-mac-w", - member_flag.clone(), - UintMulMsg { - kappa_a: one.clone(), - kappa_c: one.clone(), - a_ptr: x_ptr.clone(), - b_ptr: u_ptr.clone(), - c_ptr: b_ptr.clone(), - r_ptr: w_ptr.clone(), - bound_ptr: bound_ptr.clone(), - is_sub: LB::Expr::ZERO, - }, - member_deg - ), - ); - frac_col!( - builder, - "ec-points", - single_deg, - ( - "consume-mac-y", - member_flag, - UintMulMsg { - kappa_a: one, - kappa_c: zero, - a_ptr: y_ptr.clone(), - b_ptr: y_ptr.clone(), - c_ptr: bound_ptr.clone(), - r_ptr: w_ptr.clone(), - bound_ptr: bound_ptr.clone(), - is_sub: LB::Expr::ZERO, - }, - member_deg - ), - ); + eval_point_store_lookups(builder, 0); } } + +/// Evaluate this component's LogUp columns in a main-trace column band. +pub(crate) fn eval_point_store_lookups(builder: &mut LB, main_col_offset: usize) +where + LB: LookupBuilder, +{ + let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + + let ptr: LB::Expr = local[COL_PTR].into(); + let group_ptr: LB::Expr = local[COL_GROUP_PTR].into(); + let a_ptr: LB::Expr = local[COL_A_PTR].into(); + let b_ptr: LB::Expr = local[COL_B_PTR].into(); + let bound_ptr: LB::Expr = local[COL_BOUND_PTR].into(); + let sbound_ptr: LB::Expr = local[COL_SBOUND_PTR].into(); + let x_ptr: LB::Expr = local[COL_X_PTR].into(); + let y_ptr: LB::Expr = local[COL_Y_PTR].into(); + let u_ptr: LB::Expr = local[COL_U_PTR].into(); + let w_ptr: LB::Expr = local[COL_W_PTR].into(); + let is_pai: LB::Expr = local[COL_IS_PAI].into(); + let is_cert: LB::Expr = local[COL_IS_CERT].into(); + let act: LB::Expr = local[COL_ACT].into(); + + // Pads zero the mult cell, so the provide needs no act gate; the + // consumes do (an all-zero pad row must touch no bus). The trio fires + // on finite, non-cert rows; the cert consume on finite cert rows — + // disjoint, partitioning a finite point's one membership obligation. + let neg_mult: LB::Expr = LB::Expr::ZERO - local[COL_ECPOINT_MULT].into(); + let member_flag: LB::Expr = + act.clone() * (LB::Expr::ONE - is_pai.clone()) * (LB::Expr::ONE - is_cert.clone()); + let cert_flag: LB::Expr = act.clone() * is_cert; + + let one: LB::Expr = LB::Expr::ONE; + let zero: LB::Expr = LB::Expr::ZERO; + + let provide_deg = Deg { v: 1, u: 1 }; + let consume_deg = Deg { v: 1, u: 1 }; + let member_deg = Deg { v: 3, u: 1 }; + let cert_deg = Deg { v: 2, u: 1 }; + let single_deg = Deg { v: 1, u: 2 }; + let paired_deg = Deg { v: 3, u: 2 }; + + // col 0: the point binding, alone — the gated running-sum anchor. + frac_col!( + builder, + "ec-points", + single_deg, + ( + "provide-ecpoint", + neg_mult, + EcPointMsg { + point_ptr: ptr.clone(), + group_ptr: group_ptr.clone(), + x_ptr: x_ptr.clone(), + y_ptr: y_ptr.clone(), + is_pai: is_pai.clone(), + }, + provide_deg + ), + ); + // col 1 (paired, lqd-1): the group binding consume (forcing + // group_ptr onto a real group row and the a/b/bound/sbound cells + // onto its context — for PAI rows the only tie to a real group) + // paired with the closure-cert consume (a fresh group-law + // result's on-curve membership, discharged in place of the trio). + frac_col!( + builder, + "ec-points", + paired_deg, + ( + "consume-ecgroup", + act, + EcGroupMsg { + group_ptr: group_ptr.clone(), + a_ptr: a_ptr.clone(), + b_ptr: b_ptr.clone(), + bound_ptr: bound_ptr.clone(), + scalar_bound_ptr: sbound_ptr.clone(), + }, + consume_deg + ), + ( + "consume-ecgroupadd-cert", + cert_flag, + EcOnCurveCertMsg { + group_ptr: group_ptr.clone(), + r_ptr: ptr.clone() + }, + cert_deg + ), + ); + // cols 2-4: the curve-membership MAC trio (shared r_ptr = w makes + // y² = x³ + ax + b an identity of stored values), each degree 3 + // so each sits alone. + frac_col!( + builder, + "ec-points", + single_deg, + ( + "consume-mac-u", + member_flag.clone(), + UintMulMsg { + kappa_a: one.clone(), + kappa_c: one.clone(), + a_ptr: x_ptr.clone(), + b_ptr: x_ptr.clone(), + c_ptr: a_ptr.clone(), + r_ptr: u_ptr.clone(), + bound_ptr: bound_ptr.clone(), + is_sub: LB::Expr::ZERO, + }, + member_deg + ), + ); + frac_col!( + builder, + "ec-points", + single_deg, + ( + "consume-mac-w", + member_flag.clone(), + UintMulMsg { + kappa_a: one.clone(), + kappa_c: one.clone(), + a_ptr: x_ptr.clone(), + b_ptr: u_ptr.clone(), + c_ptr: b_ptr.clone(), + r_ptr: w_ptr.clone(), + bound_ptr: bound_ptr.clone(), + is_sub: LB::Expr::ZERO, + }, + member_deg + ), + ); + frac_col!( + builder, + "ec-points", + single_deg, + ( + "consume-mac-y", + member_flag, + UintMulMsg { + kappa_a: one, + kappa_c: zero, + a_ptr: y_ptr.clone(), + b_ptr: y_ptr.clone(), + c_ptr: bound_ptr.clone(), + r_ptr: w_ptr.clone(), + bound_ptr: bound_ptr.clone(), + is_sub: LB::Expr::ZERO, + }, + member_deg + ), + ); +} diff --git a/crates/precompiles-prover/src/ec/point_store_groups/mod.rs b/crates/precompiles-prover/src/ec/point_store_groups/mod.rs new file mode 100644 index 0000000000..4b419e6906 --- /dev/null +++ b/crates/precompiles-prover/src/ec/point_store_groups/mod.rs @@ -0,0 +1,115 @@ +//! Composite AIR for the EC point store and group table. +//! +//! The two components share a row range in disjoint column bands. Their +//! constraints and LogUp interactions delegate to the same offset-aware +//! evaluators used by the standalone component AIRs. + +pub mod trace; + +use alloc::vec::Vec; + +use miden_core::{Felt, field::QuadFelt, utils::RowMajorMatrix}; +use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder}; + +use crate::{ + ec::{self, groups}, + logup::{ + CyclicConstraintLookupBuilder, LookupAir, LookupBuilder, NUM_PUBLIC_VALUES, NUM_RANDOMNESS, + NUM_SIGMA_VALUES, + }, + relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS}, +}; + +/// First main-trace column of the group-table band. +pub const GROUPS_COL_OFFSET: usize = ec::NUM_MAIN_COLS; + +pub const NUM_MAIN_COLS: usize = ec::NUM_MAIN_COLS + groups::NUM_MAIN_COLS; +pub const NUM_AUX_COLS: usize = ec::NUM_LOGUP_COLS + groups::NUM_LOGUP_COLS; + +const fn column_shape() -> [usize; NUM_AUX_COLS] { + let mut shape = [0usize; NUM_AUX_COLS]; + let mut i = 0; + while i < ec::NUM_LOGUP_COLS { + shape[i] = ec::COLUMN_SHAPE[i]; + i += 1; + } + let mut j = 0; + while j < groups::NUM_LOGUP_COLS { + shape[ec::NUM_LOGUP_COLS + j] = groups::COLUMN_SHAPE[j]; + j += 1; + } + shape +} +const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = column_shape(); + +#[derive(Debug, Default, Clone, Copy)] +pub struct EcPointStoreGroupsAir; + +impl BaseAir for EcPointStoreGroupsAir { + fn width(&self) -> usize { + NUM_MAIN_COLS + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } +} + +impl LiftedAir for EcPointStoreGroupsAir { + fn num_randomness(&self) -> usize { + NUM_RANDOMNESS + } + + fn aux_width(&self) -> usize { + NUM_AUX_COLS + } + + fn num_aux_values(&self) -> usize { + NUM_SIGMA_VALUES + } + + fn build_aux_trace( + &self, + main: &RowMajorMatrix, + _air_inputs: &[Felt], + _aux_inputs: &[Felt], + challenges: &[QuadFelt], + ) -> (RowMajorMatrix, Vec) { + trace::build_aux(main, challenges) + } + + fn eval>(&self, builder: &mut AB) { + ec::eval_point_store_main(builder, 0); + groups::eval_main(builder, GROUPS_COL_OFFSET); + + let mut lb = + CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0); + >::eval(self, &mut lb); + } +} + +impl LookupAir for EcPointStoreGroupsAir +where + LB: LookupBuilder, +{ + fn num_columns(&self) -> usize { + NUM_AUX_COLS + } + + fn column_shape(&self) -> &[usize] { + &COLUMN_SHAPE + } + + fn max_message_width(&self) -> usize { + MAX_MESSAGE_WIDTH + } + + fn num_bus_ids(&self) -> usize { + NUM_BUS_IDS + } + + fn eval(&self, builder: &mut LB) { + ec::eval_point_store_lookups(builder, 0); + groups::eval_lookups(builder, GROUPS_COL_OFFSET); + } +} diff --git a/crates/precompiles-prover/src/ec/point_store_groups/trace.rs b/crates/precompiles-prover/src/ec/point_store_groups/trace.rs new file mode 100644 index 0000000000..bd8cb9cb3e --- /dev/null +++ b/crates/precompiles-prover/src/ec/point_store_groups/trace.rs @@ -0,0 +1,49 @@ +//! Trace generation for the merged point-store + ec-groups chiplet. + +use alloc::vec::Vec; + +use miden_core::{ + Felt, + field::QuadFelt, + utils::{Matrix, RowMajorMatrix}, +}; + +use crate::{ + ec::{ + NUM_MAIN_COLS as POINTS_NUM_MAIN_COLS, + groups::NUM_MAIN_COLS as G_NUM_MAIN_COLS, + point_store_groups::{EcPointStoreGroupsAir, NUM_MAIN_COLS}, + trace::{EcStoreRequires, groups_trace_padded_to, points_trace}, + }, + logup::build_logup_aux_trace, +}; + +/// Build the merged main trace at the largest component height. Point rows +/// can be zero-extended because they are activity-gated; group rows use +/// [`groups_trace_padded_to`] to preserve the ungated pointer chain. +pub fn generate_trace(requires: EcStoreRequires) -> RowMajorMatrix { + let mut points_main = points_trace(&requires); + let points_height = points_main.height(); + let groups_main = groups_trace_padded_to(&requires, points_height); + let height = groups_main.height(); + points_main.values.resize(height * POINTS_NUM_MAIN_COLS, Felt::ZERO); + + let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS); + for r in 0..height { + vals.extend_from_slice( + &points_main.values[r * POINTS_NUM_MAIN_COLS..(r + 1) * POINTS_NUM_MAIN_COLS], + ); + vals.extend_from_slice(&groups_main.values[r * G_NUM_MAIN_COLS..(r + 1) * G_NUM_MAIN_COLS]); + } + debug_assert_eq!(vals.len(), height * NUM_MAIN_COLS); + + RowMajorMatrix::new(vals, NUM_MAIN_COLS) +} + +/// Build the merged chiplet's LogUp trace. +pub(crate) fn build_aux( + main: &RowMajorMatrix, + challenges: &[QuadFelt], +) -> (RowMajorMatrix, Vec) { + build_logup_aux_trace(&EcPointStoreGroupsAir, main, challenges) +} diff --git a/crates/precompiles-prover/src/ec/trace.rs b/crates/precompiles-prover/src/ec/trace.rs index 52b88d0e50..e3d9724df4 100644 --- a/crates/precompiles-prover/src/ec/trace.rs +++ b/crates/precompiles-prover/src/ec/trace.rs @@ -335,15 +335,9 @@ impl EcStoreRequires { } } -/// Build **both** EC store main traces from the accumulator (consumed — -/// trace-gen is terminal, so the double-lay hazard is a compile error), -/// returning `(groups_main, points_main)` in -/// [`SessionTraces`](crate::session::SessionTraces) order. Pure reads of -/// the demand ledgers: every cross-chiplet consumer has already fed them -/// — the points' own `EcGroup` consume at intern (the store's -/// bound-ref analogue), the add relation's `EcGroup` / `EcPoint` -/// consumes in [`super::add::trace::generate_trace`], run first by the -/// Session sweep. +/// Build the standalone group and point-store traces for component tests. +/// Callers must record all cross-chiplet requirements before consuming the +/// accumulator here. pub fn generate_traces(requires: EcStoreRequires) -> (RowMajorMatrix, RowMajorMatrix) { (groups_trace(&requires), points_trace(&requires)) } @@ -353,7 +347,20 @@ pub fn generate_traces(requires: EcStoreRequires) -> (RowMajorMatrix, RowM /// `ptr = row + 1` on every row, so pads carry their ptr too — they are /// simply rows whose `mult` (and params) stay zero, touching no bus. fn groups_trace(requires: &EcStoreRequires) -> RowMajorMatrix { - let height = requires.groups.len().next_power_of_two().max(2); + groups_trace_padded_to(requires, 0) +} + +/// [`groups_trace`] with a height floor — for sharing the group table's +/// row range with another AIR (see [`super::point_store_groups`]): pads +/// past the natural height are the table's own padding mechanism +/// (`ptr = row + 1` continued, `mult` and params zero). `min_height` +/// must be 0 or a power of two so the padded height remains a power of two. +pub(crate) fn groups_trace_padded_to( + requires: &EcStoreRequires, + min_height: usize, +) -> RowMajorMatrix { + debug_assert!(min_height == 0 || min_height.is_power_of_two()); + let height = requires.groups.len().next_power_of_two().max(2).max(min_height); let mut vals = Vec::with_capacity(height * G_NUM_MAIN_COLS); for i in 0..height { @@ -377,7 +384,7 @@ fn groups_trace(requires: &EcStoreRequires) -> RowMajorMatrix { /// The point store — one row per point in allocation order /// (ptr = row + 1), padded to a power-of-two height (min 2) with /// all-zero (`act = 0`) rows that touch no bus. -fn points_trace(requires: &EcStoreRequires) -> RowMajorMatrix { +pub(crate) fn points_trace(requires: &EcStoreRequires) -> RowMajorMatrix { let height = requires.points.len().next_power_of_two().max(2); let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS); diff --git a/crates/precompiles-prover/src/hash/chunk/mod.rs b/crates/precompiles-prover/src/hash/chunk/mod.rs index 79b9a3e840..5141319f78 100644 --- a/crates/precompiles-prover/src/hash/chunk/mod.rs +++ b/crates/precompiles-prover/src/hash/chunk/mod.rs @@ -91,7 +91,7 @@ pub const NUM_MAIN_COLS: usize = COL_F_END; pub const NUM_AUX_COLS: usize = 5; /// Per-column fraction counts, matching the pairing above. -const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 2, 2, 1]; +pub(crate) const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 2, 2, 1]; // The single exposed σ ([`NUM_SIGMA_VALUES`]) follows the VM-wide σ // contract in [`crate::logup`]; aggregating the Memory64 + Poseidon2In diff --git a/crates/precompiles-prover/src/hash/chunk_node/mod.rs b/crates/precompiles-prover/src/hash/chunk_node/mod.rs index 5453acfea3..f2ddcf962a 100644 --- a/crates/precompiles-prover/src/hash/chunk_node/mod.rs +++ b/crates/precompiles-prover/src/hash/chunk_node/mod.rs @@ -1,5 +1,6 @@ -//! ChunkNode chiplet — the chunk chiplet and the Keccak transcript-DAG -//! node chiplet sharing one row range. +//! Chunk and Keccak transcript-DAG node bands sharing one row range, +//! composed into the merged hash chiplet by +//! [`crate::hash::chunk_node_sponge::ChunkNodeSpongeAir`]. //! //! Both are period-1 (no periodic columns) and their own trace heights //! are otherwise unrelated, so they run **simultaneously** on the same @@ -15,23 +16,11 @@ //! into chunk's — both still close into the one shared σ via the //! standard `acc_next[0] = Σ acc[i]` recurrence, and neither pays the //! degree cost of physically sharing column 0. -//! -//! The shared height is `max` of what each side natively needs -//! (independently `next_power_of_two`-padded, own padding mechanism), -//! not their sum, since they occupy the same rows. - -pub mod trace; -use alloc::vec::Vec; use core::array; -use miden_core::{ - Felt, - deferred::Tag, - field::{PrimeCharacteristicRing, QuadFelt}, - utils::RowMajorMatrix, -}; -use miden_lifted_air::{AirBuilder, BaseAir, LiftedAir, LiftedAirBuilder}; +use miden_core::{Felt, deferred::Tag, field::PrimeCharacteristicRing}; +use miden_lifted_air::{AirBuilder, LiftedAirBuilder}; use miden_precompiles::Keccak256Precompile; use crate::{ @@ -40,11 +29,7 @@ use crate::{ keccak::{node, sponge::KeccakSpongeMsg}, memory64::{CHUNK_ADDR_BASE, Memory64Msg}, }, - logup::{ - CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn, - LookupGroup, NUM_PUBLIC_VALUES, NUM_RANDOMNESS, NUM_SIGMA_VALUES, frac_col, - }, - relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS}, + logup::{Deg, LookupBatch, LookupBuilder, LookupColumn, LookupGroup, frac_col}, transcript::{ binding::BindingMsg, poseidon2::{Poseidon2InMsg, Poseidon2OutMsg}, @@ -66,501 +51,457 @@ pub const NUM_MAIN_COLS: usize = chunk::NUM_MAIN_COLS + node::NUM_MAIN_COLS; /// = keccak-node's original cols 1..8 unchanged. pub const NUM_AUX_COLS: usize = chunk::NUM_AUX_COLS + node::NUM_AUX_COLS; -const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2]; - -// AIR -// ================================================================================================ - -/// ChunkNode chiplet AIR. Period 1 (no periodic columns). -#[derive(Debug, Default, Clone, Copy)] -pub struct ChunkNodeAir; - -impl BaseAir for ChunkNodeAir { - fn width(&self) -> usize { - NUM_MAIN_COLS +const fn column_shape() -> [usize; NUM_AUX_COLS] { + let mut shape = [0usize; NUM_AUX_COLS]; + let mut i = 0; + while i < chunk::NUM_AUX_COLS { + shape[i] = chunk::COLUMN_SHAPE[i]; + i += 1; } - - fn num_public_values(&self) -> usize { - NUM_PUBLIC_VALUES + let mut j = 0; + while j < node::NUM_AUX_COLS { + shape[chunk::NUM_AUX_COLS + j] = node::COLUMN_SHAPE[j]; + j += 1; } + shape } +pub(crate) const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = column_shape(); -impl LiftedAir for ChunkNodeAir { - fn num_randomness(&self) -> usize { - NUM_RANDOMNESS - } - - fn aux_width(&self) -> usize { - NUM_AUX_COLS - } - - fn num_aux_values(&self) -> usize { - NUM_SIGMA_VALUES - } - - fn build_aux_trace( - &self, - main: &RowMajorMatrix, - _air_inputs: &[Felt], - _aux_inputs: &[Felt], - challenges: &[QuadFelt], - ) -> (RowMajorMatrix, Vec) { - trace::build_aux(main, challenges) - } - - fn eval>(&self, builder: &mut AB) { - // ---- CHUNK (verbatim from `chunk::ChunkAir::eval`, cols 0..12) ---- - { - let local: [AB::Var; chunk::NUM_MAIN_COLS] = current_main(builder.main(), 0); - let next: [AB::Var; chunk::NUM_MAIN_COLS] = next_main(builder.main(), 0); - - let chunk_seq_id: AB::Expr = local[chunk::COL_CHUNK_SEQ_ID].into(); - let chunk_seq_id_next: AB::Expr = next[chunk::COL_CHUNK_SEQ_ID].into(); - let perm_seq_id: AB::Expr = local[chunk::COL_PERM_SEQ_ID].into(); - let perm_seq_id_next: AB::Expr = next[chunk::COL_PERM_SEQ_ID].into(); - let act: AB::Expr = local[chunk::COL_ACT].into(); - let act_next: AB::Expr = next[chunk::COL_ACT].into(); - let is_head: AB::Expr = local[chunk::COL_IS_HEAD].into(); - let is_head_next: AB::Expr = next[chunk::COL_IS_HEAD].into(); - - builder.when_first_row().assert_zero(chunk_seq_id.clone()); - - builder - .when_transition() - .assert_zero(chunk_seq_id_next - chunk_seq_id - AB::Expr::ONE); - - builder.when_transition().assert_zero( - (AB::Expr::ONE - is_head_next) * (perm_seq_id_next - perm_seq_id - AB::Expr::ONE), - ); - - builder.assert_bool(local[chunk::COL_ACT]); - builder.when_transition().assert_zero((AB::Expr::ONE - act.clone()) * act_next); - - builder.assert_bool(local[chunk::COL_IS_HEAD]); - builder.assert_zero(is_head * (AB::Expr::ONE - act)); - } - - // ---- KECCAK-NODE (verbatim from `node::KeccakNodeAir::eval`, cols - // `NODE_COL_OFFSET`..`NUM_MAIN_COLS`) ---- - { - let local: [AB::Var; node::NUM_MAIN_COLS] = - current_main(builder.main(), NODE_COL_OFFSET); - let next: [AB::Var; node::NUM_MAIN_COLS] = next_main(builder.main(), NODE_COL_OFFSET); - - let act: AB::Expr = local[node::COL_ACT].into(); - let act_next: AB::Expr = next[node::COL_ACT].into(); - let out_mult: AB::Expr = local[node::COL_OUT_MULT].into(); - - let sponge_seq_id_head: AB::Expr = local[node::COL_SPONGE_SEQ_ID_HEAD].into(); - let sponge_seq_id_head_next: AB::Expr = next[node::COL_SPONGE_SEQ_ID_HEAD].into(); - let n_sponge_perms: AB::Expr = local[node::COL_N_SPONGE_PERMS].into(); - - let chunk_seq_id_head: AB::Expr = local[node::COL_CHUNK_SEQ_ID_HEAD].into(); - let chunk_seq_id_head_next: AB::Expr = next[node::COL_CHUNK_SEQ_ID_HEAD].into(); - let n_chunks: AB::Expr = local[node::COL_N_CHUNKS].into(); - - let _ = next[node::COL_PERM_SEQ_ID_CHUNKS]; - - builder.when_first_row().assert_zero(sponge_seq_id_head.clone()); - builder.when_first_row().assert_zero(chunk_seq_id_head.clone()); - - builder.assert_bool(local[node::COL_ACT]); - builder - .when_transition() - .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); - - builder.assert_zero((AB::Expr::ONE - act) * out_mult); - - builder.when_transition().assert_zero( - act_next.clone() - * (sponge_seq_id_head_next - - sponge_seq_id_head - - AB::Expr::from(Felt::from(32u8)) * n_sponge_perms), - ); - builder - .when_transition() - .assert_zero(act_next * (chunk_seq_id_head_next - chunk_seq_id_head - n_chunks)); - } - - // Phase 2: LogUp argument via the LogUp adapter. - let mut lb = - CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0); - >::eval(self, &mut lb); - } -} - -// LOOKUP AIR +// CONSTRAINTS // ================================================================================================ -impl LookupAir for ChunkNodeAir +/// Evaluate this component's base constraints in a main-trace column band. +pub(crate) fn eval_main(builder: &mut AB, main_col_offset: usize) where - LB: LookupBuilder, + AB: LiftedAirBuilder, { - fn num_columns(&self) -> usize { - NUM_AUX_COLS - } + // Chunk constraints. + { + let local: [AB::Var; chunk::NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + let next: [AB::Var; chunk::NUM_MAIN_COLS] = next_main(builder.main(), main_col_offset); + + let chunk_seq_id: AB::Expr = local[chunk::COL_CHUNK_SEQ_ID].into(); + let chunk_seq_id_next: AB::Expr = next[chunk::COL_CHUNK_SEQ_ID].into(); + let perm_seq_id: AB::Expr = local[chunk::COL_PERM_SEQ_ID].into(); + let perm_seq_id_next: AB::Expr = next[chunk::COL_PERM_SEQ_ID].into(); + let act: AB::Expr = local[chunk::COL_ACT].into(); + let act_next: AB::Expr = next[chunk::COL_ACT].into(); + let is_head: AB::Expr = local[chunk::COL_IS_HEAD].into(); + let is_head_next: AB::Expr = next[chunk::COL_IS_HEAD].into(); + + builder.when_first_row().assert_zero(chunk_seq_id.clone()); + + builder + .when_transition() + .assert_zero(chunk_seq_id_next - chunk_seq_id - AB::Expr::ONE); + + builder.when_transition().assert_zero( + (AB::Expr::ONE - is_head_next) * (perm_seq_id_next - perm_seq_id - AB::Expr::ONE), + ); - fn column_shape(&self) -> &[usize] { - &COLUMN_SHAPE - } + builder.assert_bool(local[chunk::COL_ACT]); + builder.when_transition().assert_zero((AB::Expr::ONE - act.clone()) * act_next); - fn max_message_width(&self) -> usize { - MAX_MESSAGE_WIDTH + builder.assert_bool(local[chunk::COL_IS_HEAD]); + builder.assert_zero(is_head * (AB::Expr::ONE - act)); } - fn num_bus_ids(&self) -> usize { - NUM_BUS_IDS - } + // Keccak-node constraints. + { + let local: [AB::Var; node::NUM_MAIN_COLS] = + current_main(builder.main(), main_col_offset + NODE_COL_OFFSET); + let next: [AB::Var; node::NUM_MAIN_COLS] = + next_main(builder.main(), main_col_offset + NODE_COL_OFFSET); - fn eval(&self, builder: &mut LB) { - // ---- CHUNK (verbatim from `chunk::ChunkAir`'s `LookupAir::eval`, aux cols 0..5) ---- - let local: [LB::Var; chunk::NUM_MAIN_COLS] = current_main(builder.main(), 0); - - let chunk_seq_id: LB::Expr = local[chunk::COL_CHUNK_SEQ_ID].into(); - let perm_seq_id: LB::Expr = local[chunk::COL_PERM_SEQ_ID].into(); - let act: LB::Expr = local[chunk::COL_ACT].into(); - let is_head: LB::Expr = local[chunk::COL_IS_HEAD].into(); - let f: [LB::Expr; chunk::NUM_F] = array::from_fn(|i| local[chunk::COL_F_BEGIN + i].into()); - - let chunk_addr_base = - Felt::new(CHUNK_ADDR_BASE).expect("CHUNK_ADDR_BASE fits in canonical Goldilocks"); - let addr0 = - LB::Expr::from(chunk_addr_base) + LB::Expr::from(Felt::from(4u8)) * chunk_seq_id; - let addr1 = addr0.clone() + LB::Expr::ONE; - let addr2 = addr0.clone() + LB::Expr::from(Felt::from(2u8)); - let addr3 = addr0.clone() + LB::Expr::from(Felt::from(3u8)); - - let neg_act: LB::Expr = LB::Expr::ZERO - act.clone(); - - let pos_act: LB::Expr = act.clone(); - let pos_act_head: LB::Expr = act * is_head; - - let rate0_chunk = [f[0].clone(), f[1].clone(), f[2].clone(), f[3].clone()]; - let rate1_chunk = [f[4].clone(), f[5].clone(), f[6].clone(), f[7].clone()]; - let cap_chunk = Tag::CHUNKS.as_word().map(LB::Expr::from); - - let interaction_deg = Deg { v: 1, u: 1 }; - let provides_deg = Deg { v: 1, u: 2 }; - let pair_deg = Deg { v: 3, u: 2 }; - - frac_col!( - builder, - "memory64", - provides_deg, - ( - "lane0", - neg_act.clone(), - Memory64Msg { - addr: addr0, - lo: f[0].clone(), - hi: f[1].clone() - }, - interaction_deg - ), - ); - frac_col!( - builder, - "memory64", - pair_deg, - ( - "lane1", - neg_act.clone(), - Memory64Msg { - addr: addr1, - lo: f[2].clone(), - hi: f[3].clone() - }, - interaction_deg - ), - ( - "lane2", - neg_act.clone(), - Memory64Msg { - addr: addr2, - lo: f[4].clone(), - hi: f[5].clone() - }, - interaction_deg - ), - ); - frac_col!( - builder, - "chunk-flatten", - pair_deg, - ( - "lane3", - neg_act, - Memory64Msg { - addr: addr3, - lo: f[6].clone(), - hi: f[7].clone() - }, - interaction_deg - ), - ( - "rate0", - pos_act.clone(), - Poseidon2InMsg::rate0(perm_seq_id.clone(), rate0_chunk), - interaction_deg - ), - ); - frac_col!( - builder, - "poseidon2-in", - pair_deg, - ( - "rate1", - pos_act, - Poseidon2InMsg::rate1(perm_seq_id.clone(), rate1_chunk), - interaction_deg - ), - ( - "cap", - pos_act_head.clone(), - Poseidon2InMsg::cap(perm_seq_id.clone(), cap_chunk), - interaction_deg - ), - ); + let act: AB::Expr = local[node::COL_ACT].into(); + let act_next: AB::Expr = next[node::COL_ACT].into(); + let out_mult: AB::Expr = local[node::COL_OUT_MULT].into(); - let neg_act_head: LB::Expr = LB::Expr::ZERO - pos_act_head; - frac_col!( - builder, - "chunk-chain", - provides_deg, - ( - "emit", - neg_act_head, - ChunkChainMsg { - chunk_seq_id_head: local[chunk::COL_CHUNK_SEQ_ID].into(), - perm_seq_id_head: perm_seq_id, - }, - interaction_deg - ), - ); + let sponge_seq_id_head: AB::Expr = local[node::COL_SPONGE_SEQ_ID_HEAD].into(); + let sponge_seq_id_head_next: AB::Expr = next[node::COL_SPONGE_SEQ_ID_HEAD].into(); + let n_sponge_perms: AB::Expr = local[node::COL_N_SPONGE_PERMS].into(); - // ---- KECCAK-NODE (verbatim from `node::KeccakNodeAir`'s `LookupAir::eval`, aux cols - // 5..14) ---- - let local: [LB::Var; node::NUM_MAIN_COLS] = current_main(builder.main(), NODE_COL_OFFSET); - - let act: LB::Expr = local[node::COL_ACT].into(); - let sponge_seq_id_head: LB::Expr = local[node::COL_SPONGE_SEQ_ID_HEAD].into(); - let n_sponge_perms: LB::Expr = local[node::COL_N_SPONGE_PERMS].into(); - let chunk_seq_id_head: LB::Expr = local[node::COL_CHUNK_SEQ_ID_HEAD].into(); - let n_chunks: LB::Expr = local[node::COL_N_CHUNKS].into(); - let perm_seq_id_chunks: LB::Expr = local[node::COL_PERM_SEQ_ID_CHUNKS].into(); - let len_bytes: LB::Expr = local[node::COL_LEN_BYTES].into(); - let perm_seq_id_digest_chunks: LB::Expr = local[node::COL_PERM_SEQ_ID_DIGEST_CHUNKS].into(); - let perm_seq_id_keccak: LB::Expr = local[node::COL_PERM_SEQ_ID_KECCAK].into(); - - let d: [LB::Expr; node::NUM_D] = array::from_fn(|i| local[node::COL_D_BEGIN + i].into()); - let h_input_chunks: [LB::Expr; node::NUM_HASH] = - array::from_fn(|i| local[node::COL_H_INPUT_CHUNKS_BEGIN + i].into()); - let h_digest_chunks: [LB::Expr; node::NUM_HASH] = - array::from_fn(|i| local[node::COL_H_DIGEST_CHUNKS_BEGIN + i].into()); - let h_keccak: [LB::Expr; node::NUM_HASH] = - array::from_fn(|i| local[node::COL_H_KECCAK_BEGIN + i].into()); - - let neg_act: LB::Expr = LB::Expr::ZERO - act.clone(); - let pos_act: LB::Expr = act.clone(); - let pos_act_x2: LB::Expr = LB::Expr::from(Felt::from(2u8)) * act; - let out_mult: LB::Expr = local[node::COL_OUT_MULT].into(); - let neg_out_mult: LB::Expr = LB::Expr::ZERO - out_mult; - - let chunk_ptr_head: LB::Expr = LB::Expr::from(Felt::from(4u8)) * chunk_seq_id_head.clone(); - let perm_seq_id_chunks_tail: LB::Expr = - perm_seq_id_chunks.clone() + n_chunks - LB::Expr::ONE; - let digest_addr_base: LB::Expr = LB::Expr::from(Felt::from(100u8)) * sponge_seq_id_head - + LB::Expr::from(Felt::from(3200u32)) * n_sponge_perms - - LB::Expr::from(Felt::from(128u8)); - - let cap_digest_chunks = Tag::CHUNKS.as_word().map(LB::Expr::from); - let cap_keccak = [ - LB::Expr::from(Keccak256Precompile::id()), - LB::Expr::from(Felt::from_u32(Keccak256Precompile::ASSERT_TAG_ID)), - len_bytes.clone(), - LB::Expr::ZERO, - ]; - - let d_rate0 = [d[0].clone(), d[1].clone(), d[2].clone(), d[3].clone()]; - let d_rate1 = [d[4].clone(), d[5].clone(), d[6].clone(), d[7].clone()]; - - frac_col!( - builder, - "handshake-and-chunks-digest", - provides_deg, - ( - "ks-request", - neg_act.clone(), - KeccakSpongeMsg { - sponge_seq_id: local[node::COL_SPONGE_SEQ_ID_HEAD].into(), - chunk_ptr: chunk_ptr_head, - len_bytes: len_bytes.clone() - }, - interaction_deg - ), - ); - frac_col!( - builder, - "handshake-and-chunks-digest", - pair_deg, - ( - "binding-truth", - neg_out_mult, - BindingMsg::truth(h_keccak.clone()), - interaction_deg - ), - ( - "chunk-chain", - pos_act.clone(), - ChunkChainMsg { - chunk_seq_id_head: chunk_seq_id_head.clone(), - perm_seq_id_head: perm_seq_id_chunks - }, - interaction_deg - ), - ); - frac_col!( - builder, - "handshake-and-chunks-digest", - provides_deg, - ( - "p2out-h-input-chunks", - pos_act.clone(), - Poseidon2OutMsg { - perm_seq_id: perm_seq_id_chunks_tail, - digest: h_input_chunks.clone() - }, - interaction_deg - ), - ); + let chunk_seq_id_head: AB::Expr = local[node::COL_CHUNK_SEQ_ID_HEAD].into(); + let chunk_seq_id_head_next: AB::Expr = next[node::COL_CHUNK_SEQ_ID_HEAD].into(); + let n_chunks: AB::Expr = local[node::COL_N_CHUNKS].into(); - let addr_lane = - |j: u8| -> LB::Expr { digest_addr_base.clone() + LB::Expr::from(Felt::from(j)) }; - frac_col!( - builder, - "memory64-d-limbs", - pair_deg, - ( - "d-lane-0", - pos_act_x2.clone(), - Memory64Msg { - addr: addr_lane(0), - lo: d[0].clone(), - hi: d[1].clone() - }, - interaction_deg - ), - ( - "d-lane-1", - pos_act_x2.clone(), - Memory64Msg { - addr: addr_lane(1), - lo: d[2].clone(), - hi: d[3].clone() - }, - interaction_deg - ), - ); - frac_col!( - builder, - "memory64-d-limbs", - pair_deg, - ( - "d-lane-2", - pos_act_x2.clone(), - Memory64Msg { - addr: addr_lane(2), - lo: d[4].clone(), - hi: d[5].clone() - }, - interaction_deg - ), - ( - "d-lane-3", - pos_act_x2, - Memory64Msg { - addr: addr_lane(3), - lo: d[6].clone(), - hi: d[7].clone() - }, - interaction_deg - ), - ); + let _ = next[node::COL_PERM_SEQ_ID_CHUNKS]; - frac_col!( - builder, - "digest-chunks-p2", - pair_deg, - ( - "p2in-rate0", - pos_act.clone(), - Poseidon2InMsg::rate0(perm_seq_id_digest_chunks.clone(), d_rate0), - interaction_deg - ), - ( - "p2in-rate1", - pos_act.clone(), - Poseidon2InMsg::rate1(perm_seq_id_digest_chunks.clone(), d_rate1), - interaction_deg - ), - ); - frac_col!( - builder, - "digest-chunks-p2", - pair_deg, - ( - "p2in-cap", - pos_act.clone(), - Poseidon2InMsg::cap(perm_seq_id_digest_chunks.clone(), cap_digest_chunks), - interaction_deg - ), - ( - "p2out-h-digest-chunks", - pos_act.clone(), - Poseidon2OutMsg { - perm_seq_id: perm_seq_id_digest_chunks, - digest: h_digest_chunks.clone() - }, - interaction_deg - ), - ); + builder.when_first_row().assert_zero(sponge_seq_id_head.clone()); + builder.when_first_row().assert_zero(chunk_seq_id_head.clone()); - frac_col!( - builder, - "keccak-p2", - pair_deg, - ( - "p2in-rate0", - pos_act.clone(), - Poseidon2InMsg::rate0(perm_seq_id_keccak.clone(), h_input_chunks), - interaction_deg - ), - ( - "p2in-rate1", - pos_act.clone(), - Poseidon2InMsg::rate1(perm_seq_id_keccak.clone(), h_digest_chunks), - interaction_deg - ), - ); - frac_col!( - builder, - "keccak-p2", - pair_deg, - ( - "p2in-cap", - pos_act.clone(), - Poseidon2InMsg::cap(perm_seq_id_keccak.clone(), cap_keccak), - interaction_deg - ), - ( - "p2out-h-keccak", - pos_act, - Poseidon2OutMsg { - perm_seq_id: perm_seq_id_keccak, - digest: h_keccak - }, - interaction_deg - ), + builder.assert_bool(local[node::COL_ACT]); + builder + .when_transition() + .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); + + builder.assert_zero((AB::Expr::ONE - act) * out_mult); + + builder.when_transition().assert_zero( + act_next.clone() + * (sponge_seq_id_head_next + - sponge_seq_id_head + - AB::Expr::from(Felt::from(32u8)) * n_sponge_perms), ); + builder + .when_transition() + .assert_zero(act_next * (chunk_seq_id_head_next - chunk_seq_id_head - n_chunks)); } } + +// LOOKUPS +// ================================================================================================ + +/// Evaluate this component's LogUp columns in a main-trace column band. +pub(crate) fn eval_lookups(builder: &mut LB, main_col_offset: usize) +where + LB: LookupBuilder, +{ + // Chunk lookups. + let local: [LB::Var; chunk::NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + + let chunk_seq_id: LB::Expr = local[chunk::COL_CHUNK_SEQ_ID].into(); + let perm_seq_id: LB::Expr = local[chunk::COL_PERM_SEQ_ID].into(); + let act: LB::Expr = local[chunk::COL_ACT].into(); + let is_head: LB::Expr = local[chunk::COL_IS_HEAD].into(); + let f: [LB::Expr; chunk::NUM_F] = array::from_fn(|i| local[chunk::COL_F_BEGIN + i].into()); + + let chunk_addr_base = + Felt::new(CHUNK_ADDR_BASE).expect("CHUNK_ADDR_BASE fits in canonical Goldilocks"); + let addr0 = LB::Expr::from(chunk_addr_base) + LB::Expr::from(Felt::from(4u8)) * chunk_seq_id; + let addr1 = addr0.clone() + LB::Expr::ONE; + let addr2 = addr0.clone() + LB::Expr::from(Felt::from(2u8)); + let addr3 = addr0.clone() + LB::Expr::from(Felt::from(3u8)); + + let neg_act: LB::Expr = LB::Expr::ZERO - act.clone(); + + let pos_act: LB::Expr = act.clone(); + let pos_act_head: LB::Expr = act * is_head; + + let rate0_chunk = [f[0].clone(), f[1].clone(), f[2].clone(), f[3].clone()]; + let rate1_chunk = [f[4].clone(), f[5].clone(), f[6].clone(), f[7].clone()]; + let cap_chunk = Tag::CHUNKS.as_word().map(LB::Expr::from); + + let interaction_deg = Deg { v: 1, u: 1 }; + let provides_deg = Deg { v: 1, u: 2 }; + let pair_deg = Deg { v: 3, u: 2 }; + + frac_col!( + builder, + "memory64", + provides_deg, + ( + "lane0", + neg_act.clone(), + Memory64Msg { + addr: addr0, + lo: f[0].clone(), + hi: f[1].clone() + }, + interaction_deg + ), + ); + frac_col!( + builder, + "memory64", + pair_deg, + ( + "lane1", + neg_act.clone(), + Memory64Msg { + addr: addr1, + lo: f[2].clone(), + hi: f[3].clone() + }, + interaction_deg + ), + ( + "lane2", + neg_act.clone(), + Memory64Msg { + addr: addr2, + lo: f[4].clone(), + hi: f[5].clone() + }, + interaction_deg + ), + ); + frac_col!( + builder, + "chunk-flatten", + pair_deg, + ( + "lane3", + neg_act, + Memory64Msg { + addr: addr3, + lo: f[6].clone(), + hi: f[7].clone() + }, + interaction_deg + ), + ( + "rate0", + pos_act.clone(), + Poseidon2InMsg::rate0(perm_seq_id.clone(), rate0_chunk), + interaction_deg + ), + ); + frac_col!( + builder, + "poseidon2-in", + pair_deg, + ( + "rate1", + pos_act, + Poseidon2InMsg::rate1(perm_seq_id.clone(), rate1_chunk), + interaction_deg + ), + ( + "cap", + pos_act_head.clone(), + Poseidon2InMsg::cap(perm_seq_id.clone(), cap_chunk), + interaction_deg + ), + ); + + let neg_act_head: LB::Expr = LB::Expr::ZERO - pos_act_head; + frac_col!( + builder, + "chunk-chain", + provides_deg, + ( + "emit", + neg_act_head, + ChunkChainMsg { + chunk_seq_id_head: local[chunk::COL_CHUNK_SEQ_ID].into(), + perm_seq_id_head: perm_seq_id, + }, + interaction_deg + ), + ); + + // Keccak-node lookups. + let local: [LB::Var; node::NUM_MAIN_COLS] = + current_main(builder.main(), main_col_offset + NODE_COL_OFFSET); + + let act: LB::Expr = local[node::COL_ACT].into(); + let sponge_seq_id_head: LB::Expr = local[node::COL_SPONGE_SEQ_ID_HEAD].into(); + let n_sponge_perms: LB::Expr = local[node::COL_N_SPONGE_PERMS].into(); + let chunk_seq_id_head: LB::Expr = local[node::COL_CHUNK_SEQ_ID_HEAD].into(); + let n_chunks: LB::Expr = local[node::COL_N_CHUNKS].into(); + let perm_seq_id_chunks: LB::Expr = local[node::COL_PERM_SEQ_ID_CHUNKS].into(); + let len_bytes: LB::Expr = local[node::COL_LEN_BYTES].into(); + let perm_seq_id_digest_chunks: LB::Expr = local[node::COL_PERM_SEQ_ID_DIGEST_CHUNKS].into(); + let perm_seq_id_keccak: LB::Expr = local[node::COL_PERM_SEQ_ID_KECCAK].into(); + + let d: [LB::Expr; node::NUM_D] = array::from_fn(|i| local[node::COL_D_BEGIN + i].into()); + let h_input_chunks: [LB::Expr; node::NUM_HASH] = + array::from_fn(|i| local[node::COL_H_INPUT_CHUNKS_BEGIN + i].into()); + let h_digest_chunks: [LB::Expr; node::NUM_HASH] = + array::from_fn(|i| local[node::COL_H_DIGEST_CHUNKS_BEGIN + i].into()); + let h_keccak: [LB::Expr; node::NUM_HASH] = + array::from_fn(|i| local[node::COL_H_KECCAK_BEGIN + i].into()); + + let neg_act: LB::Expr = LB::Expr::ZERO - act.clone(); + let pos_act: LB::Expr = act.clone(); + let pos_act_x2: LB::Expr = LB::Expr::from(Felt::from(2u8)) * act; + let out_mult: LB::Expr = local[node::COL_OUT_MULT].into(); + let neg_out_mult: LB::Expr = LB::Expr::ZERO - out_mult; + + let chunk_ptr_head: LB::Expr = LB::Expr::from(Felt::from(4u8)) * chunk_seq_id_head.clone(); + let perm_seq_id_chunks_tail: LB::Expr = perm_seq_id_chunks.clone() + n_chunks - LB::Expr::ONE; + let digest_addr_base: LB::Expr = LB::Expr::from(Felt::from(100u8)) * sponge_seq_id_head + + LB::Expr::from(Felt::from(3200u32)) * n_sponge_perms + - LB::Expr::from(Felt::from(128u8)); + + let cap_digest_chunks = Tag::CHUNKS.as_word().map(LB::Expr::from); + let cap_keccak = [ + LB::Expr::from(Keccak256Precompile::id()), + LB::Expr::from(Felt::from_u32(Keccak256Precompile::ASSERT_TAG_ID)), + len_bytes.clone(), + LB::Expr::ZERO, + ]; + + let d_rate0 = [d[0].clone(), d[1].clone(), d[2].clone(), d[3].clone()]; + let d_rate1 = [d[4].clone(), d[5].clone(), d[6].clone(), d[7].clone()]; + + frac_col!( + builder, + "handshake-and-chunks-digest", + provides_deg, + ( + "ks-request", + neg_act.clone(), + KeccakSpongeMsg { + sponge_seq_id: local[node::COL_SPONGE_SEQ_ID_HEAD].into(), + chunk_ptr: chunk_ptr_head, + len_bytes: len_bytes.clone() + }, + interaction_deg + ), + ); + frac_col!( + builder, + "handshake-and-chunks-digest", + pair_deg, + ( + "binding-truth", + neg_out_mult, + BindingMsg::truth(h_keccak.clone()), + interaction_deg + ), + ( + "chunk-chain", + pos_act.clone(), + ChunkChainMsg { + chunk_seq_id_head: chunk_seq_id_head.clone(), + perm_seq_id_head: perm_seq_id_chunks + }, + interaction_deg + ), + ); + frac_col!( + builder, + "handshake-and-chunks-digest", + provides_deg, + ( + "p2out-h-input-chunks", + pos_act.clone(), + Poseidon2OutMsg { + perm_seq_id: perm_seq_id_chunks_tail, + digest: h_input_chunks.clone() + }, + interaction_deg + ), + ); + + let addr_lane = + |j: u8| -> LB::Expr { digest_addr_base.clone() + LB::Expr::from(Felt::from(j)) }; + frac_col!( + builder, + "memory64-d-limbs", + pair_deg, + ( + "d-lane-0", + pos_act_x2.clone(), + Memory64Msg { + addr: addr_lane(0), + lo: d[0].clone(), + hi: d[1].clone() + }, + interaction_deg + ), + ( + "d-lane-1", + pos_act_x2.clone(), + Memory64Msg { + addr: addr_lane(1), + lo: d[2].clone(), + hi: d[3].clone() + }, + interaction_deg + ), + ); + frac_col!( + builder, + "memory64-d-limbs", + pair_deg, + ( + "d-lane-2", + pos_act_x2.clone(), + Memory64Msg { + addr: addr_lane(2), + lo: d[4].clone(), + hi: d[5].clone() + }, + interaction_deg + ), + ( + "d-lane-3", + pos_act_x2, + Memory64Msg { + addr: addr_lane(3), + lo: d[6].clone(), + hi: d[7].clone() + }, + interaction_deg + ), + ); + + frac_col!( + builder, + "digest-chunks-p2", + pair_deg, + ( + "p2in-rate0", + pos_act.clone(), + Poseidon2InMsg::rate0(perm_seq_id_digest_chunks.clone(), d_rate0), + interaction_deg + ), + ( + "p2in-rate1", + pos_act.clone(), + Poseidon2InMsg::rate1(perm_seq_id_digest_chunks.clone(), d_rate1), + interaction_deg + ), + ); + frac_col!( + builder, + "digest-chunks-p2", + pair_deg, + ( + "p2in-cap", + pos_act.clone(), + Poseidon2InMsg::cap(perm_seq_id_digest_chunks.clone(), cap_digest_chunks), + interaction_deg + ), + ( + "p2out-h-digest-chunks", + pos_act.clone(), + Poseidon2OutMsg { + perm_seq_id: perm_seq_id_digest_chunks, + digest: h_digest_chunks.clone() + }, + interaction_deg + ), + ); + + frac_col!( + builder, + "keccak-p2", + pair_deg, + ( + "p2in-rate0", + pos_act.clone(), + Poseidon2InMsg::rate0(perm_seq_id_keccak.clone(), h_input_chunks), + interaction_deg + ), + ( + "p2in-rate1", + pos_act.clone(), + Poseidon2InMsg::rate1(perm_seq_id_keccak.clone(), h_digest_chunks), + interaction_deg + ), + ); + frac_col!( + builder, + "keccak-p2", + pair_deg, + ( + "p2in-cap", + pos_act.clone(), + Poseidon2InMsg::cap(perm_seq_id_keccak.clone(), cap_keccak), + interaction_deg + ), + ( + "p2out-h-keccak", + pos_act, + Poseidon2OutMsg { + perm_seq_id: perm_seq_id_keccak, + digest: h_keccak + }, + interaction_deg + ), + ); +} diff --git a/crates/precompiles-prover/src/hash/chunk_node/trace.rs b/crates/precompiles-prover/src/hash/chunk_node/trace.rs deleted file mode 100644 index 2befe9989c..0000000000 --- a/crates/precompiles-prover/src/hash/chunk_node/trace.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Trace generation for the merged chunk + keccak-node chiplet. - -use alloc::vec::Vec; - -use miden_core::{ - Felt, - field::QuadFelt, - utils::{Matrix, RowMajorMatrix}, -}; - -use crate::{ - hash::{ - chunk::{ - self, - trace::{ChunkRequires, generate_trace_padded_to as chunk_trace}, - }, - chunk_node::{ChunkNodeAir, NODE_COL_OFFSET, NUM_MAIN_COLS}, - keccak::node::{ - self as node, - trace::{KeccakNodeRequires, generate_trace as node_trace}, - }, - }, - logup::build_logup_aux_trace, -}; - -/// Build the merged chunk + keccak-node main trace. Both sides run on -/// the same row range in disjoint column ranges (see the module doc), -/// so the shared height is `max` of what each side natively needs — the -/// keccak-node trace is computed first (its own padding is `act`-gated, -/// so zero-extending it is always sound), then chunk's own trace is -/// padded up to at least that height (chunk's `chunk_seq_id` / -/// `perm_seq_id` chains are unconditional, so it needs its own -/// continuation logic — see `chunk::trace::generate_trace_padded_to`). -pub fn generate_trace(chunk: ChunkRequires, node: KeccakNodeRequires) -> RowMajorMatrix { - let mut node_main = node_trace(node); - let node_height = node_main.height(); - let chunk_main = chunk_trace(chunk, node_height); - let height = chunk_main.height(); - node_main.values.resize(height * node::NUM_MAIN_COLS, Felt::ZERO); - - let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS); - for r in 0..height { - vals.extend_from_slice( - &chunk_main.values[r * chunk::NUM_MAIN_COLS..(r + 1) * chunk::NUM_MAIN_COLS], - ); - vals.extend_from_slice( - &node_main.values[r * node::NUM_MAIN_COLS..(r + 1) * node::NUM_MAIN_COLS], - ); - } - debug_assert_eq!(vals.len(), height * NUM_MAIN_COLS); - debug_assert_eq!(NODE_COL_OFFSET, chunk::NUM_MAIN_COLS); - - RowMajorMatrix::new(vals, NUM_MAIN_COLS) -} - -/// Build the merged chiplet's aux trace via the generic -/// [`build_logup_aux_trace`] driver. -pub(crate) fn build_aux( - main: &RowMajorMatrix, - challenges: &[QuadFelt], -) -> (RowMajorMatrix, Vec) { - build_logup_aux_trace(&ChunkNodeAir, main, challenges) -} diff --git a/crates/precompiles-prover/src/hash/chunk_node_sponge/mod.rs b/crates/precompiles-prover/src/hash/chunk_node_sponge/mod.rs new file mode 100644 index 0000000000..3d6c5acc9e --- /dev/null +++ b/crates/precompiles-prover/src/hash/chunk_node_sponge/mod.rs @@ -0,0 +1,122 @@ +//! Composite AIR for the chunk, Keccak-node, and Keccak-sponge chiplets. +//! +//! The three components share a row range in disjoint column bands. Their +//! constraints and LogUp interactions delegate to the same offset-aware +//! evaluators used by the standalone component AIRs. + +pub mod trace; + +use alloc::vec::Vec; + +use miden_core::{Felt, field::QuadFelt, utils::RowMajorMatrix}; +use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder}; + +use crate::{ + hash::{ + chunk_node, + keccak::sponge::{self, sponge_program}, + }, + logup::{ + CyclicConstraintLookupBuilder, LookupAir, LookupBuilder, NUM_PUBLIC_VALUES, NUM_RANDOMNESS, + NUM_SIGMA_VALUES, + }, + relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS}, +}; + +/// First main-trace column of the sponge band. +pub const SPONGE_COL_OFFSET: usize = chunk_node::NUM_MAIN_COLS; + +pub const NUM_MAIN_COLS: usize = chunk_node::NUM_MAIN_COLS + sponge::NUM_MAIN_COLS; +pub const NUM_AUX_COLS: usize = chunk_node::NUM_AUX_COLS + sponge::NUM_AUX_COLS; + +const fn column_shape() -> [usize; NUM_AUX_COLS] { + let mut shape = [0usize; NUM_AUX_COLS]; + let mut i = 0; + while i < chunk_node::NUM_AUX_COLS { + shape[i] = chunk_node::COLUMN_SHAPE[i]; + i += 1; + } + let mut j = 0; + while j < sponge::NUM_AUX_COLS { + shape[chunk_node::NUM_AUX_COLS + j] = sponge::COLUMN_SHAPE[j]; + j += 1; + } + shape +} +const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = column_shape(); + +#[derive(Debug, Default, Clone, Copy)] +pub struct ChunkNodeSpongeAir; + +impl BaseAir for ChunkNodeSpongeAir { + fn width(&self) -> usize { + NUM_MAIN_COLS + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } + + fn periodic_columns(&self) -> Vec> { + sponge_program().to_vec() + } +} + +impl LiftedAir for ChunkNodeSpongeAir { + fn num_randomness(&self) -> usize { + NUM_RANDOMNESS + } + + fn aux_width(&self) -> usize { + NUM_AUX_COLS + } + + fn num_aux_values(&self) -> usize { + NUM_SIGMA_VALUES + } + + fn build_aux_trace( + &self, + main: &RowMajorMatrix, + _air_inputs: &[Felt], + _aux_inputs: &[Felt], + challenges: &[QuadFelt], + ) -> (RowMajorMatrix, Vec) { + trace::build_aux(main, challenges) + } + + fn eval>(&self, builder: &mut AB) { + chunk_node::eval_main(builder, 0); + sponge::eval_main(builder, SPONGE_COL_OFFSET); + + let mut lb = + CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0); + >::eval(self, &mut lb); + } +} + +impl LookupAir for ChunkNodeSpongeAir +where + LB: LookupBuilder, +{ + fn num_columns(&self) -> usize { + NUM_AUX_COLS + } + + fn column_shape(&self) -> &[usize] { + &COLUMN_SHAPE + } + + fn max_message_width(&self) -> usize { + MAX_MESSAGE_WIDTH + } + + fn num_bus_ids(&self) -> usize { + NUM_BUS_IDS + } + + fn eval(&self, builder: &mut LB) { + chunk_node::eval_lookups(builder, 0); + sponge::eval_lookups(builder, SPONGE_COL_OFFSET); + } +} diff --git a/crates/precompiles-prover/src/hash/chunk_node_sponge/trace.rs b/crates/precompiles-prover/src/hash/chunk_node_sponge/trace.rs new file mode 100644 index 0000000000..50b45f11c0 --- /dev/null +++ b/crates/precompiles-prover/src/hash/chunk_node_sponge/trace.rs @@ -0,0 +1,78 @@ +//! Trace generation for the merged chunk + keccak-node + keccak-sponge +//! chiplet. + +use alloc::vec::Vec; + +use miden_core::{ + Felt, + field::QuadFelt, + utils::{Matrix, RowMajorMatrix}, +}; + +use crate::{ + hash::{ + chunk::{ + self, + trace::{ChunkRequires, generate_trace_padded_to as chunk_trace}, + }, + chunk_node_sponge::{ChunkNodeSpongeAir, NUM_MAIN_COLS}, + keccak::{ + node::{ + self as node, + trace::{KeccakNodeRequires, generate_trace as node_trace}, + }, + sponge::{ + self as sponge, + trace::{SpongeRequires, generate_trace_padded_to as sponge_trace}, + }, + }, + }, + logup::build_logup_aux_trace, +}; + +/// Build the merged main trace at the largest component height. Node rows +/// can be zero-extended because they are activity-gated; chunk and sponge +/// use their own padding generators to preserve unconditional chains. +pub fn generate_trace( + chunk: ChunkRequires, + node: KeccakNodeRequires, + sponge: SpongeRequires, +) -> RowMajorMatrix { + let mut node_main = node_trace(node); + let node_height = node_main.height(); + let sponge_main = sponge_trace(sponge, node_height); + let chunk_main = chunk_trace(chunk, sponge_main.height()); + let height = chunk_main.height(); + // The sponge uses at least 32 rows per invocation and therefore + // dominates the chunk trace's ceil(len / 32) rows. + assert_eq!( + height, + sponge_main.height(), + "the sponge band's height must dominate the chunk band's" + ); + node_main.values.resize(height * node::NUM_MAIN_COLS, Felt::ZERO); + + let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS); + for r in 0..height { + vals.extend_from_slice( + &chunk_main.values[r * chunk::NUM_MAIN_COLS..(r + 1) * chunk::NUM_MAIN_COLS], + ); + vals.extend_from_slice( + &node_main.values[r * node::NUM_MAIN_COLS..(r + 1) * node::NUM_MAIN_COLS], + ); + vals.extend_from_slice( + &sponge_main.values[r * sponge::NUM_MAIN_COLS..(r + 1) * sponge::NUM_MAIN_COLS], + ); + } + debug_assert_eq!(vals.len(), height * NUM_MAIN_COLS); + + RowMajorMatrix::new(vals, NUM_MAIN_COLS) +} + +/// Build the merged chiplet's LogUp trace. +pub(crate) fn build_aux( + main: &RowMajorMatrix, + challenges: &[QuadFelt], +) -> (RowMajorMatrix, Vec) { + build_logup_aux_trace(&ChunkNodeSpongeAir, main, challenges) +} diff --git a/crates/precompiles-prover/src/hash/keccak/node/mod.rs b/crates/precompiles-prover/src/hash/keccak/node/mod.rs index 39d5f98916..7d5b43fab8 100644 --- a/crates/precompiles-prover/src/hash/keccak/node/mod.rs +++ b/crates/precompiles-prover/src/hash/keccak/node/mod.rs @@ -171,7 +171,7 @@ pub const NUM_MAIN_COLS: usize = COL_OUT_MULT + 1; /// - col 7/8: keccak-node P2 perm — `Poseidon2In` rate0+rate1, then cap + `Poseidon2Out(H_keccak)`. pub const NUM_AUX_COLS: usize = 9; -const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 1, 2, 2, 2, 2, 2, 2]; +pub(crate) const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 1, 2, 2, 2, 2, 2, 2]; // AIR // ================================================================================================ diff --git a/crates/precompiles-prover/src/hash/keccak/round/mod.rs b/crates/precompiles-prover/src/hash/keccak/round/mod.rs index a6e8f63d07..41bb43786c 100644 --- a/crates/precompiles-prover/src/hash/keccak/round/mod.rs +++ b/crates/precompiles-prover/src/hash/keccak/round/mod.rs @@ -126,9 +126,10 @@ fn interleave_lanes(lane_cells: &[Vec; NUM_LANES], height: usize) -> RowMa // AUX COLUMN LAYOUT // ================================================================================================ -/// FLATTENED to lqd 1, repeated per lane: each lane's 10-column band holds -/// 19 fractions (all degree-≤2 multiplicities) split ≤ 2 per column, the -/// band's col 0 a single fraction: +/// Flattened running-sum layout, repeated per lane: each lane's 10-column band holds +/// 19 fractions split ≤ 2 per column, the band's col 0 a single fraction. The flattening does not +/// bring this AIR to `log_quotient_degree = 1`; it derives 2 because the memory64 destination +/// fraction carries degree 5 (see `dst_deg` in the lookup evaluator): /// - band col 0: memory64 dst provide. /// - band col 1: memory64 `src_a` + `src_b` requires. /// - band cols 2–5: 8 `BytePairLut` byte requires verifying `r = a OP b` (or `r = a` on pure-ROL @@ -356,14 +357,14 @@ fn memory_provide_c, V: Copy + Into>( // LOOKUP AIR // ================================================================================================ -/// Aux column shape (FLATTENED to lqd 1), repeated per lane (band-local): -/// - band col 0: memory64 dst provide (one degree-≤3 fraction; lane 0's is the running sum). +/// Aux column shape, repeated per lane (band-local): +/// - band col 0: memory64 dst provide (lane 0's is the running sum). /// - band col 1: memory64 `src_a` + `src_b` requires. /// - band cols 2–5: 8 `BytePairLut` byte requires, two per column. /// - band cols 6–9: 8 `Range16` requires on `rot_limbs`, two per column. /// -/// Every closing constraint is degree ≤ 3, so `log_quotient_degree = 1` -/// (aux blowup factor = 2). Width disregarded. +/// This AIR derives `log_quotient_degree = 2`: the memory64 destination fraction below carries +/// `Deg { v: 5, .. }`. The symbolic derivation is authoritative; trace width does not enter it. const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = build_column_shape(); const fn build_column_shape() -> [usize; NUM_AUX_COLS] { diff --git a/crates/precompiles-prover/src/hash/keccak/sponge/mod.rs b/crates/precompiles-prover/src/hash/keccak/sponge/mod.rs index 6e57066ccf..16c254b0b4 100644 --- a/crates/precompiles-prover/src/hash/keccak/sponge/mod.rs +++ b/crates/precompiles-prover/src/hash/keccak/sponge/mod.rs @@ -318,7 +318,7 @@ pub const PAD_CONST_BYTES: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0x80]; /// halves are `lo`/`hi`. Extracts from the already-verified `_LO`/`_HI` /// per-`byte_offset` mask constants, so the per-byte view is correct by /// construction rather than a hand-rederived bit pattern. -const fn mask_byte(lo: u32, hi: u32, byte_idx: usize) -> u8 { +pub(crate) const fn mask_byte(lo: u32, hi: u32, byte_idx: usize) -> u8 { let word = if byte_idx < 4 { lo } else { hi }; ((word >> (8 * (byte_idx % 4))) & 0xff) as u8 } @@ -350,266 +350,266 @@ impl LiftedAir for KeccakSpongeAir { } fn eval>(&self, builder: &mut AB) { - // Phase 1: local row constraints. - let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - // Next-row window: the same 27 columns at row r+1. Cyclic at - // row N-1 (`when_transition` gates out the wrap explicitly - // where needed; other transition constraints rely on - // `p_last`/`p_rate_block` factors making the wrap vacuous). - let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0); - - let periodic = builder.periodic_values(); - let p_first: AB::Expr = periodic[PCOL_FIRST].into(); - let p_last: AB::Expr = periodic[PCOL_LAST].into(); - let p_rate_block: AB::Expr = periodic[PCOL_RATE_BLOCK].into(); - let p_capacity: AB::Expr = periodic[PCOL_CAPACITY].into(); - let p_extra: AB::Expr = periodic[PCOL_EXTRA].into(); - let p_state_lane: AB::Expr = p_rate_block.clone() + p_capacity.clone(); - - // Frequently-used local / next-row expressions. - let act: AB::Expr = local[COL_ACT].into(); - let act_next: AB::Expr = next[COL_ACT].into(); - let sponge_seq_id: AB::Expr = local[COL_SPONGE_SEQ_ID].into(); - let sponge_seq_id_next: AB::Expr = next[COL_SPONGE_SEQ_ID].into(); - let bytes_left: AB::Expr = local[COL_BYTES_LEFT].into(); - let bytes_left_next: AB::Expr = next[COL_BYTES_LEFT].into(); - let chunk_ptr: AB::Expr = local[COL_CHUNK_PTR].into(); - let chunk_ptr_next: AB::Expr = next[COL_CHUNK_PTR].into(); - let is_first_block: AB::Expr = local[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); - let is_first_block_next: AB::Expr = next[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); - let is_zero: AB::Expr = local[COL_IS_ZERO].into(); - let is_zero_next: AB::Expr = next[COL_IS_ZERO].into(); - let is_chunk_avail: AB::Expr = local[COL_IS_CHUNK_AVAIL].into(); - let is_chunk_avail_next: AB::Expr = next[COL_IS_CHUNK_AVAIL].into(); - let state_prev_lo: AB::Expr = local[COL_STATE_PREV_LO].into(); - let state_prev_hi: AB::Expr = local[COL_STATE_PREV_HI].into(); - let state_new_lo: AB::Expr = local[COL_STATE_NEW_LO].into(); - let state_new_hi: AB::Expr = local[COL_STATE_NEW_HI].into(); - - // Σ b_j (= `is_last_block_period`) and Σ j·b_j (= byte_offset). - let mut b_sum = AB::Expr::ZERO; - let mut b_weighted = AB::Expr::ZERO; - for (j, col) in COL_B_RANGE.enumerate() { - let b_j: AB::Expr = local[col].into(); - b_sum += b_j.clone(); - b_weighted += AB::Expr::from(Felt::from(j as u32)) * b_j; - } - - // Boundary (`when_first_row`) --------------------------- - // `sponge_seq_id` starts at 0 (row counter convention). No - // `chunk_ptr` boundary: the chunk-tape base is pinned per - // invocation by the `KeccakSponge` request (the first - // invocation supplies base 0 by convention), and the chain is - // relaxed at invocation seams — see the `chunk_ptr` chain below. - builder.when_first_row().assert_zero(sponge_seq_id.clone()); - - // Activity ---------------------------------------------- - // Binary: act ∈ {0, 1}. Deg 2. - builder.assert_bool(local[COL_ACT]); - // Sticky-downward: forbids 0 → 1 within [0, N-2]. The cyclic - // wrap is intentionally unconstrained so a 1's-prefix / 0's- - // suffix trace cycles back to `act_0 = 1` on the next loop. - builder - .when_transition() - .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); - // Drop placement: the unique 1 → 0 transition must land at - // slot 31 (where `p_last = 1`) of a last-block period - // (`Σ b_j = 1`). Equivalent to `act · (1 - act')` under the - // sticky-down constraint above; the linear form costs one - // fewer witness multiplication. - builder.when_transition().assert_zero( - (act.clone() - act_next) * (AB::Expr::ONE - p_last.clone() * b_sum.clone()), - ); - - // Row counter ------------------------------------------- - // sponge_seq_id' - sponge_seq_id - 1 = 0. Deg 1. - // `when_transition` keeps the cyclic wrap (which would force - // sponge_seq_id_0 = N) unconstrained. - builder - .when_transition() - .assert_zero(sponge_seq_id_next - sponge_seq_id - AB::Expr::ONE); - - // `is_first_block_of_invocation` structure -------------- - builder.assert_bool(local[COL_IS_FIRST_BLOCK_OF_INVOCATION]); - // Constant within period. `(1 - p_last)` makes the wrap - // vacuous (p_last_{N-1} = 1) and lets `is_first_block` toggle - // at period boundaries. - builder.assert_zero( - (AB::Expr::ONE - p_last.clone()) - * (is_first_block_next.clone() - is_first_block.clone()), - ); + eval_main(builder, 0); - // `bytes_left` decrement chain -------------------------- - // Both branches are gated by `act`: on dead rows `bytes_left` is - // unconstrained, so the all-dead (zero-invocation) trace is - // admissible — the only valid empty-transcript trace (see - // the design notes §`bytes_left` decrement chain). - // On active rows the chain is identical, so it still forbids the - // `act = 1 ∧ is_first_block = 0` cyclic-fixed-point forgery via the - // `M · 136 ≢ 0 mod p` argument (`act` doesn't weaken it — the forgery - // it rules out is fully active). Dead rows are bus-inert, so leaving - // `bytes_left` free there is sound. - // - // Absorb row (`p_rate_block = 1`): decrements by 8. - builder.assert_zero( - act.clone() - * p_rate_block.clone() - * (bytes_left_next.clone() - bytes_left.clone() + AB::Expr::from(Felt::from(8u8))), - ); - // Non-absorb row + no invocation boundary at next row: holds steady. - let enters_new_invocation = p_last.clone() * is_first_block_next.clone(); - builder.assert_zero( - act.clone() - * (AB::Expr::ONE - enters_new_invocation.clone()) - * (AB::Expr::ONE - p_rate_block.clone()) - * (bytes_left_next - bytes_left.clone()), - ); - - // `chunk_ptr` increment chain --------------------------- - // Within an invocation: chunk_ptr' - chunk_ptr - - // (p_rate_block + p_extra · b_sum) · is_chunk_avail = 0 (advance - // by 1 per consumed chunk lane). Rate rows consume on every - // block; the extra rows consume the last block's overshoot - // lanes (gated to the last block by `b_sum`), so `chunk_ptr` - // walks all 4·num_chunks tape lanes contiguously. Gated off at - // invocation seams (`enters_new_invocation`), where the - // `KeccakSponge` request re-pins chunk_ptr to the next - // invocation's chunk-tape base. No global enumeration from 0 — - // per-invocation overlap/gap freedom is enforced by Memory64 - // bus balance against the chunk chiplet's contiguous emissions, - // not by the chain. `when_transition` also keeps the cyclic - // wrap unconstrained. - builder.when_transition().assert_zero( - (AB::Expr::ONE - enters_new_invocation) - * (chunk_ptr_next - - chunk_ptr - - (p_rate_block.clone() + p_extra * b_sum.clone()) * is_chunk_avail.clone()), - ); - - // Chunk zero-fill on `is_chunk_avail = 0` -------------- - // When the chunk chiplet doesn't provide at this row, pin - // `chunk_lo = chunk_hi = 0` so the witness is canonical and - // pre-pad verbatim XORs can't be steered by a - // prover-chosen unpinned chunk value. Effect: any - // under-emission by the chunk chiplet yields a deterministic - // zero-extended digest, caught by the downstream digest - // check at the transcript chiplet. Ungated — pinning chunk - // to 0 on non-rate / dead rows is benign since those rows - // never consume the chunk columns elsewhere. - let chunk_lo_local: AB::Expr = local[COL_CHUNK_LO].into(); - let chunk_hi_local: AB::Expr = local[COL_CHUNK_HI].into(); - builder.assert_zero((AB::Expr::ONE - is_chunk_avail.clone()) * chunk_lo_local); - builder.assert_zero((AB::Expr::ONE - is_chunk_avail.clone()) * chunk_hi_local); - - // Padding state machine --------------------------------- - // Binarity. - builder.assert_bool(local[COL_IS_ZERO]); - builder.assert_bool(local[COL_IS_CHUNK_AVAIL]); - for col in COL_B_RANGE { - builder.assert_bool(local[col]); - } - - // `is_zero` non-decreasing within period. - builder.assert_zero( - (AB::Expr::ONE - p_last.clone()) - * is_zero.clone() - * (AB::Expr::ONE - is_zero_next.clone()), - ); - // `is_chunk_avail` non-increasing within period. - builder.assert_zero( - (AB::Expr::ONE - p_last.clone()) - * (AB::Expr::ONE - is_chunk_avail) - * is_chunk_avail_next, - ); - // Period boundary: pad hasn't fired yet at slot 0. - builder.assert_zero(p_first * is_zero.clone()); - // Selector bits constant within period. - for col in COL_B_RANGE { - let b_j: AB::Expr = local[col].into(); - let b_j_next: AB::Expr = next[col].into(); - builder.assert_zero((AB::Expr::ONE - p_last.clone()) * (b_j_next - b_j)); - } - // Selector sum ties to `is_zero` on non-absorb rows. - builder.assert_zero( - (AB::Expr::ONE - p_rate_block.clone()) * (b_sum.clone() - is_zero.clone()), - ); - - // Pad-must-fire (gated by `act`) ------------------------ - // At slot 31 of any *active* period followed by a new - // invocation, force `is_zero = 1` (the pad fired earlier in - // this period) — i.e. a new invocation may only start right - // after a last block, so no invocation is truncated. Covers - // active→active seams. The last active invocation's last block - // is instead pinned by `act` drop placement (act may drop only - // after a `b_sum = 1` period). The `act` gate makes the cyclic - // wrap from the trailing dead pad region into row 0 vacuous - // (dead rows carry `is_zero = 0`), so an invocation set whose - // total block count isn't a power of two — padded out with dead - // rows — is admissible. Without the gate the wrap would demand - // `is_zero = 1` on the final dead row. - builder.assert_zero(act * p_last * is_first_block_next * (AB::Expr::ONE - is_zero.clone())); - - // Pad-lane tie-down ------------------------------------- - // On the unique pad transition row (`p_rate_block = 1`, - // `is_pad = 1`), pin `byte_offset = bytes_left`. Vacuous - // everywhere else. The `p_rate_block` gate also absorbs - // the period-wrap `is_pad = −1` case, which always lands - // on `p_idx = 31` where `p_rate_block = 0`. - let is_pad: AB::Expr = is_zero_next - is_zero.clone(); - builder.assert_zero(p_rate_block.clone() * is_pad * (b_weighted - bytes_left)); - - // state_prev = 0 on first-block state-lane rows --------- - builder.assert_zero(p_state_lane.clone() * is_first_block.clone() * state_prev_lo.clone()); - builder.assert_zero(p_state_lane * is_first_block * state_prev_hi.clone()); - - // State propagation (no BytePairLut request fires) -------- - // Past-pad rate XORin rows: `state_new = state_prev`. - builder.assert_zero( - p_rate_block.clone() * is_zero.clone() * (state_new_lo.clone() - state_prev_lo.clone()), - ); - builder - .assert_zero(p_rate_block * is_zero * (state_new_hi.clone() - state_prev_hi.clone())); - // Capacity rows: identity passthrough. - builder.assert_zero(p_capacity.clone() * (state_new_lo.clone() - state_prev_lo.clone())); - builder.assert_zero(p_capacity * (state_new_hi.clone() - state_prev_hi.clone())); - - // Byte-shadow linking (ungated) -------------------------- - // Every `_lo`/`_hi` pair below also has an 8-byte little-endian - // shadow (used by the `BytePairLut` requires in Phase 2, which - // range-check and byte-verify the pad/absorb XOR/ANDNOT ops - // directly). Without this link the byte columns would be a - // second, independent free witness disconnected from the halves - // every other bus message (Memory64 prev-perm consume, new-state - // provide, chunk consume) actually reads — pinning them together - // is what makes a `BytePairLut`-verified byte result also the - // value committed elsewhere. Ungated: both sides are otherwise - // free witness on rows where the value is unused, so an honest - // prover always satisfies this by construction. - let chunk_lo: AB::Expr = local[COL_CHUNK_LO].into(); - let chunk_hi: AB::Expr = local[COL_CHUNK_HI].into(); - let cleared_lo: AB::Expr = local[COL_CLEARED_LO].into(); - let cleared_hi: AB::Expr = local[COL_CLEARED_HI].into(); - let padded_lo: AB::Expr = local[COL_PADDED_LO].into(); - let padded_hi: AB::Expr = local[COL_PADDED_HI].into(); - let link = |builder: &mut AB, range: Range, lo: AB::Expr, hi: AB::Expr| { - let bytes: [AB::Var; 8] = array::from_fn(|i| local[range.start + i]); - let [lo_from_bytes, hi_from_bytes]: [AB::Expr; 2] = halves_le(&bytes, 256); - builder.assert_zero(lo_from_bytes - lo); - builder.assert_zero(hi_from_bytes - hi); - }; - link(builder, CHUNK_BYTES_RANGE, chunk_lo, chunk_hi); - link(builder, STATE_PREV_BYTES_RANGE, state_prev_lo, state_prev_hi); - link(builder, STATE_NEW_BYTES_RANGE, state_new_lo, state_new_hi); - link(builder, CLEARED_BYTES_RANGE, cleared_lo, cleared_hi); - link(builder, PADDED_BYTES_RANGE, padded_lo, padded_hi); - - // Phase 2: LogUp argument via the LogUp adapter. + // Phase 2: LogUp. let mut lb = CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0); >::eval(self, &mut lb); } } +/// Evaluate this component's base constraints in a main-trace column band. +pub(crate) fn eval_main(builder: &mut AB, main_col_offset: usize) +where + AB: LiftedAirBuilder, +{ + // Phase 1: local row constraints. + let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + // Next-row window: the same 67 columns at row r+1. Cyclic at + // row N-1 (`when_transition` gates out the wrap explicitly + // where needed; other transition constraints rely on + // `p_last`/`p_rate_block` factors making the wrap vacuous). + let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), main_col_offset); + + let periodic = builder.periodic_values(); + let p_first: AB::Expr = periodic[PCOL_FIRST].into(); + let p_last: AB::Expr = periodic[PCOL_LAST].into(); + let p_rate_block: AB::Expr = periodic[PCOL_RATE_BLOCK].into(); + let p_capacity: AB::Expr = periodic[PCOL_CAPACITY].into(); + let p_extra: AB::Expr = periodic[PCOL_EXTRA].into(); + let p_state_lane: AB::Expr = p_rate_block.clone() + p_capacity.clone(); + + // Frequently-used local / next-row expressions. + let act: AB::Expr = local[COL_ACT].into(); + let act_next: AB::Expr = next[COL_ACT].into(); + let sponge_seq_id: AB::Expr = local[COL_SPONGE_SEQ_ID].into(); + let sponge_seq_id_next: AB::Expr = next[COL_SPONGE_SEQ_ID].into(); + let bytes_left: AB::Expr = local[COL_BYTES_LEFT].into(); + let bytes_left_next: AB::Expr = next[COL_BYTES_LEFT].into(); + let chunk_ptr: AB::Expr = local[COL_CHUNK_PTR].into(); + let chunk_ptr_next: AB::Expr = next[COL_CHUNK_PTR].into(); + let is_first_block: AB::Expr = local[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); + let is_first_block_next: AB::Expr = next[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); + let is_zero: AB::Expr = local[COL_IS_ZERO].into(); + let is_zero_next: AB::Expr = next[COL_IS_ZERO].into(); + let is_chunk_avail: AB::Expr = local[COL_IS_CHUNK_AVAIL].into(); + let is_chunk_avail_next: AB::Expr = next[COL_IS_CHUNK_AVAIL].into(); + let state_prev_lo: AB::Expr = local[COL_STATE_PREV_LO].into(); + let state_prev_hi: AB::Expr = local[COL_STATE_PREV_HI].into(); + let state_new_lo: AB::Expr = local[COL_STATE_NEW_LO].into(); + let state_new_hi: AB::Expr = local[COL_STATE_NEW_HI].into(); + + // Σ b_j (= `is_last_block_period`) and Σ j·b_j (= byte_offset). + let mut b_sum = AB::Expr::ZERO; + let mut b_weighted = AB::Expr::ZERO; + for (j, col) in COL_B_RANGE.enumerate() { + let b_j: AB::Expr = local[col].into(); + b_sum += b_j.clone(); + b_weighted += AB::Expr::from(Felt::from(j as u32)) * b_j; + } + + // Boundary (`when_first_row`) --------------------------- + // `sponge_seq_id` starts at 0 (row counter convention). No + // `chunk_ptr` boundary: the chunk-tape base is pinned per + // invocation by the `KeccakSponge` request (the first + // invocation supplies base 0 by convention), and the chain is + // relaxed at invocation seams — see the `chunk_ptr` chain below. + builder.when_first_row().assert_zero(sponge_seq_id.clone()); + + // Activity ---------------------------------------------- + // Binary: act ∈ {0, 1}. Deg 2. + builder.assert_bool(local[COL_ACT]); + // Sticky-downward: forbids 0 → 1 within [0, N-2]. The cyclic + // wrap is intentionally unconstrained so a 1's-prefix / 0's- + // suffix trace cycles back to `act_0 = 1` on the next loop. + builder + .when_transition() + .assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone()); + // Drop placement: the unique 1 → 0 transition must land at + // slot 31 (where `p_last = 1`) of a last-block period + // (`Σ b_j = 1`). Equivalent to `act · (1 - act')` under the + // sticky-down constraint above; the linear form costs one + // fewer witness multiplication. + builder + .when_transition() + .assert_zero((act.clone() - act_next) * (AB::Expr::ONE - p_last.clone() * b_sum.clone())); + + // Row counter ------------------------------------------- + // sponge_seq_id' - sponge_seq_id - 1 = 0. Deg 1. + // `when_transition` keeps the cyclic wrap (which would force + // sponge_seq_id_0 = N) unconstrained. + builder + .when_transition() + .assert_zero(sponge_seq_id_next - sponge_seq_id - AB::Expr::ONE); + + // `is_first_block_of_invocation` structure -------------- + builder.assert_bool(local[COL_IS_FIRST_BLOCK_OF_INVOCATION]); + // Constant within period. `(1 - p_last)` makes the wrap + // vacuous (p_last_{N-1} = 1) and lets `is_first_block` toggle + // at period boundaries. + builder.assert_zero( + (AB::Expr::ONE - p_last.clone()) * (is_first_block_next.clone() - is_first_block.clone()), + ); + + // `bytes_left` decrement chain -------------------------- + // Both branches are gated by `act`: on dead rows `bytes_left` is + // unconstrained, so the all-dead (zero-invocation) trace is + // admissible — the only valid empty-transcript trace (see + // the design notes §`bytes_left` decrement chain). + // On active rows the chain is identical, so it still forbids the + // `act = 1 ∧ is_first_block = 0` cyclic-fixed-point forgery via the + // `M · 136 ≢ 0 mod p` argument (`act` doesn't weaken it — the forgery + // it rules out is fully active). Dead rows are bus-inert, so leaving + // `bytes_left` free there is sound. + // + // Absorb row (`p_rate_block = 1`): decrements by 8. + builder.assert_zero( + act.clone() + * p_rate_block.clone() + * (bytes_left_next.clone() - bytes_left.clone() + AB::Expr::from(Felt::from(8u8))), + ); + // Non-absorb row + no invocation boundary at next row: holds steady. + let enters_new_invocation = p_last.clone() * is_first_block_next.clone(); + builder.assert_zero( + act.clone() + * (AB::Expr::ONE - enters_new_invocation.clone()) + * (AB::Expr::ONE - p_rate_block.clone()) + * (bytes_left_next - bytes_left.clone()), + ); + + // `chunk_ptr` increment chain --------------------------- + // Within an invocation: chunk_ptr' - chunk_ptr - + // (p_rate_block + p_extra · b_sum) · is_chunk_avail = 0 (advance + // by 1 per consumed chunk lane). Rate rows consume on every + // block; the extra rows consume the last block's overshoot + // lanes (gated to the last block by `b_sum`), so `chunk_ptr` + // walks all 4·num_chunks tape lanes contiguously. Gated off at + // invocation seams (`enters_new_invocation`), where the + // `KeccakSponge` request re-pins chunk_ptr to the next + // invocation's chunk-tape base. No global enumeration from 0 — + // per-invocation overlap/gap freedom is enforced by Memory64 + // bus balance against the chunk chiplet's contiguous emissions, + // not by the chain. `when_transition` also keeps the cyclic + // wrap unconstrained. + builder.when_transition().assert_zero( + (AB::Expr::ONE - enters_new_invocation) + * (chunk_ptr_next + - chunk_ptr + - (p_rate_block.clone() + p_extra * b_sum.clone()) * is_chunk_avail.clone()), + ); + + // Chunk zero-fill on `is_chunk_avail = 0` -------------- + // When the chunk chiplet doesn't provide at this row, pin + // `chunk_lo = chunk_hi = 0` so the witness is canonical and + // pre-pad verbatim XORs can't be steered by a + // prover-chosen unpinned chunk value. Effect: any + // under-emission by the chunk chiplet yields a deterministic + // zero-extended digest, caught by the downstream digest + // check at the transcript chiplet. Ungated — pinning chunk + // to 0 on non-rate / dead rows is benign since those rows + // never consume the chunk columns elsewhere. + let chunk_lo_local: AB::Expr = local[COL_CHUNK_LO].into(); + let chunk_hi_local: AB::Expr = local[COL_CHUNK_HI].into(); + builder.assert_zero((AB::Expr::ONE - is_chunk_avail.clone()) * chunk_lo_local); + builder.assert_zero((AB::Expr::ONE - is_chunk_avail.clone()) * chunk_hi_local); + + // Padding state machine --------------------------------- + // Binarity. + builder.assert_bool(local[COL_IS_ZERO]); + builder.assert_bool(local[COL_IS_CHUNK_AVAIL]); + for col in COL_B_RANGE { + builder.assert_bool(local[col]); + } + + // `is_zero` non-decreasing within period. + builder.assert_zero( + (AB::Expr::ONE - p_last.clone()) * is_zero.clone() * (AB::Expr::ONE - is_zero_next.clone()), + ); + // `is_chunk_avail` non-increasing within period. + builder.assert_zero( + (AB::Expr::ONE - p_last.clone()) * (AB::Expr::ONE - is_chunk_avail) * is_chunk_avail_next, + ); + // Period boundary: pad hasn't fired yet at slot 0. + builder.assert_zero(p_first * is_zero.clone()); + // Selector bits constant within period. + for col in COL_B_RANGE { + let b_j: AB::Expr = local[col].into(); + let b_j_next: AB::Expr = next[col].into(); + builder.assert_zero((AB::Expr::ONE - p_last.clone()) * (b_j_next - b_j)); + } + // Selector sum ties to `is_zero` on non-absorb rows. + builder.assert_zero((AB::Expr::ONE - p_rate_block.clone()) * (b_sum.clone() - is_zero.clone())); + + // Pad-must-fire (gated by `act`) ------------------------ + // At slot 31 of any *active* period followed by a new + // invocation, force `is_zero = 1` (the pad fired earlier in + // this period) — i.e. a new invocation may only start right + // after a last block, so no invocation is truncated. Covers + // active→active seams. The last active invocation's last block + // is instead pinned by `act` drop placement (act may drop only + // after a `b_sum = 1` period). The `act` gate makes the cyclic + // wrap from the trailing dead pad region into row 0 vacuous + // (dead rows carry `is_zero = 0`), so an invocation set whose + // total block count isn't a power of two — padded out with dead + // rows — is admissible. Without the gate the wrap would demand + // `is_zero = 1` on the final dead row. + builder.assert_zero(act * p_last * is_first_block_next * (AB::Expr::ONE - is_zero.clone())); + + // Pad-lane tie-down ------------------------------------- + // On the unique pad transition row (`p_rate_block = 1`, + // `is_pad = 1`), pin `byte_offset = bytes_left`. Vacuous + // everywhere else. The `p_rate_block` gate also absorbs + // the period-wrap `is_pad = −1` case, which always lands + // on `p_idx = 31` where `p_rate_block = 0`. + let is_pad: AB::Expr = is_zero_next - is_zero.clone(); + builder.assert_zero(p_rate_block.clone() * is_pad * (b_weighted - bytes_left)); + + // state_prev = 0 on first-block state-lane rows --------- + builder.assert_zero(p_state_lane.clone() * is_first_block.clone() * state_prev_lo.clone()); + builder.assert_zero(p_state_lane * is_first_block * state_prev_hi.clone()); + + // State propagation (no BytePairLut request fires) -------- + // Past-pad rate XORin rows: `state_new = state_prev`. + builder.assert_zero( + p_rate_block.clone() * is_zero.clone() * (state_new_lo.clone() - state_prev_lo.clone()), + ); + builder.assert_zero(p_rate_block * is_zero * (state_new_hi.clone() - state_prev_hi.clone())); + // Capacity rows: identity passthrough. + builder.assert_zero(p_capacity.clone() * (state_new_lo.clone() - state_prev_lo.clone())); + builder.assert_zero(p_capacity * (state_new_hi.clone() - state_prev_hi.clone())); + + // Byte-shadow linking (ungated) -------------------------- + // Every `_lo`/`_hi` pair below also has an 8-byte little-endian + // shadow (used by the `BytePairLut` requires in Phase 2, which + // range-check and byte-verify the pad/absorb XOR/ANDNOT ops + // directly). Without this link the byte columns would be a + // second, independent free witness disconnected from the halves + // every other bus message (Memory64 prev-perm consume, new-state + // provide, chunk consume) actually reads — pinning them together + // is what makes a `BytePairLut`-verified byte result also the + // value committed elsewhere. Ungated: both sides are otherwise + // free witness on rows where the value is unused, so an honest + // prover always satisfies this by construction. + let chunk_lo: AB::Expr = local[COL_CHUNK_LO].into(); + let chunk_hi: AB::Expr = local[COL_CHUNK_HI].into(); + let cleared_lo: AB::Expr = local[COL_CLEARED_LO].into(); + let cleared_hi: AB::Expr = local[COL_CLEARED_HI].into(); + let padded_lo: AB::Expr = local[COL_PADDED_LO].into(); + let padded_hi: AB::Expr = local[COL_PADDED_HI].into(); + let link = |builder: &mut AB, range: Range, lo: AB::Expr, hi: AB::Expr| { + let bytes: [AB::Var; 8] = array::from_fn(|i| local[range.start + i]); + let [lo_from_bytes, hi_from_bytes]: [AB::Expr; 2] = halves_le(&bytes, 256); + builder.assert_zero(lo_from_bytes - lo); + builder.assert_zero(hi_from_bytes - hi); + }; + link(builder, CHUNK_BYTES_RANGE, chunk_lo, chunk_hi); + link(builder, STATE_PREV_BYTES_RANGE, state_prev_lo, state_prev_hi); + link(builder, STATE_NEW_BYTES_RANGE, state_new_lo, state_new_hi); + link(builder, CLEARED_BYTES_RANGE, cleared_lo, cleared_hi); + link(builder, PADDED_BYTES_RANGE, padded_lo, padded_hi); +} + // LOOKUP AIR // ================================================================================================ @@ -619,7 +619,7 @@ impl LiftedAir for KeccakSpongeAir { /// closing constraint is degree ≤ 5. The chunk-consume fires on rate rows /// and, on the last block, the extra rows [26,29) that mop up overshoot /// lanes (gated by `p_extra · b_sum`). -const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = build_column_shape(); +pub(crate) const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = build_column_shape(); const fn build_column_shape() -> [usize; NUM_AUX_COLS] { let mut shape = [2usize; NUM_AUX_COLS]; @@ -651,398 +651,402 @@ where } fn eval(&self, builder: &mut LB) { - let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0); - let next: [LB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0); - let periodic = builder.periodic_values(); - let p_first: LB::Expr = periodic[PCOL_FIRST].into(); - let p_rate_block: LB::Expr = periodic[PCOL_RATE_BLOCK].into(); - let p_capacity: LB::Expr = periodic[PCOL_CAPACITY].into(); - let p_rc_active: LB::Expr = periodic[PCOL_RC_ACTIVE].into(); - let p_squeeze_active: LB::Expr = periodic[PCOL_SQUEEZE_ACTIVE].into(); - let p_pad_0x80: LB::Expr = periodic[PCOL_PAD_0X80].into(); - let p_extra: LB::Expr = periodic[PCOL_EXTRA].into(); - let p_idx: LB::Expr = periodic[PCOL_IDX].into(); - let rc_lo: LB::Expr = periodic[PCOL_RC_LO].into(); - let rc_hi: LB::Expr = periodic[PCOL_RC_HI].into(); - let p_state_lane: LB::Expr = p_rate_block.clone() + p_capacity; - - let act: LB::Expr = local[COL_ACT].into(); - let sponge_seq_id: LB::Expr = local[COL_SPONGE_SEQ_ID].into(); - let chunk_ptr: LB::Expr = local[COL_CHUNK_PTR].into(); - let bytes_left: LB::Expr = local[COL_BYTES_LEFT].into(); - let is_first_block: LB::Expr = local[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); - let is_chunk_avail: LB::Expr = local[COL_IS_CHUNK_AVAIL].into(); - let is_zero: LB::Expr = local[COL_IS_ZERO].into(); - let is_zero_next: LB::Expr = next[COL_IS_ZERO].into(); - let chunk_lo: LB::Expr = local[COL_CHUNK_LO].into(); - let chunk_hi: LB::Expr = local[COL_CHUNK_HI].into(); - let state_prev_lo: LB::Expr = local[COL_STATE_PREV_LO].into(); - let state_prev_hi: LB::Expr = local[COL_STATE_PREV_HI].into(); - let state_new_lo: LB::Expr = local[COL_STATE_NEW_LO].into(); - let state_new_hi: LB::Expr = local[COL_STATE_NEW_HI].into(); - let state_out_lo: LB::Expr = local[COL_STATE_OUT_LO].into(); - let state_out_hi: LB::Expr = local[COL_STATE_OUT_HI].into(); - let cleared_bytes: [LB::Var; 8] = array::from_fn(|i| local[CLEARED_BYTES_RANGE.start + i]); - let padded_bytes: [LB::Var; 8] = array::from_fn(|i| local[PADDED_BYTES_RANGE.start + i]); - let chunk_bytes: [LB::Var; 8] = array::from_fn(|i| local[CHUNK_BYTES_RANGE.start + i]); - let state_prev_bytes: [LB::Var; 8] = - array::from_fn(|i| local[STATE_PREV_BYTES_RANGE.start + i]); - let state_new_bytes: [LB::Var; 8] = - array::from_fn(|i| local[STATE_NEW_BYTES_RANGE.start + i]); - - // Σ b_j (= `is_last_block_period`); `andnot_mask` and - // `padding_mask`, byte-decomposed, as `Σ_j b_j · MASK_BYTE[i][j]` - // inlines (`mask_byte` extracts byte `i` from the existing - // verifier-known `*_LO`/`*_HI` per-`byte_offset` constants, so the - // per-byte tables are correct by construction from the - // already-verified 32-bit ones). - let mut b_sum = LB::Expr::ZERO; - let mut andnot_mask_bytes: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); - let mut padding_mask_bytes: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); - for (j, col) in COL_B_RANGE.enumerate() { - let b_j: LB::Expr = local[col].into(); - b_sum += b_j.clone(); - for i in 0..8 { - let andnot_byte = mask_byte(ANDNOT_MASK_LO[j], ANDNOT_MASK_HI[j], i); - let padding_byte = mask_byte(PADDING_MASK_LO[j], PADDING_MASK_HI[j], i); - andnot_mask_bytes[i] += LB::Expr::from(Felt::from(andnot_byte)) * b_j.clone(); - padding_mask_bytes[i] += LB::Expr::from(Felt::from(padding_byte)) * b_j.clone(); - } + eval_lookups(builder, 0); + } +} + +/// Evaluate this component's LogUp columns in a main-trace column band. +pub(crate) fn eval_lookups(builder: &mut LB, main_col_offset: usize) +where + LB: LookupBuilder, +{ + let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), main_col_offset); + let next: [LB::Var; NUM_MAIN_COLS] = next_main(builder.main(), main_col_offset); + let periodic = builder.periodic_values(); + let p_first: LB::Expr = periodic[PCOL_FIRST].into(); + let p_rate_block: LB::Expr = periodic[PCOL_RATE_BLOCK].into(); + let p_capacity: LB::Expr = periodic[PCOL_CAPACITY].into(); + let p_rc_active: LB::Expr = periodic[PCOL_RC_ACTIVE].into(); + let p_squeeze_active: LB::Expr = periodic[PCOL_SQUEEZE_ACTIVE].into(); + let p_pad_0x80: LB::Expr = periodic[PCOL_PAD_0X80].into(); + let p_extra: LB::Expr = periodic[PCOL_EXTRA].into(); + let p_idx: LB::Expr = periodic[PCOL_IDX].into(); + let rc_lo: LB::Expr = periodic[PCOL_RC_LO].into(); + let rc_hi: LB::Expr = periodic[PCOL_RC_HI].into(); + let p_state_lane: LB::Expr = p_rate_block.clone() + p_capacity; + + let act: LB::Expr = local[COL_ACT].into(); + let sponge_seq_id: LB::Expr = local[COL_SPONGE_SEQ_ID].into(); + let chunk_ptr: LB::Expr = local[COL_CHUNK_PTR].into(); + let bytes_left: LB::Expr = local[COL_BYTES_LEFT].into(); + let is_first_block: LB::Expr = local[COL_IS_FIRST_BLOCK_OF_INVOCATION].into(); + let is_chunk_avail: LB::Expr = local[COL_IS_CHUNK_AVAIL].into(); + let is_zero: LB::Expr = local[COL_IS_ZERO].into(); + let is_zero_next: LB::Expr = next[COL_IS_ZERO].into(); + let chunk_lo: LB::Expr = local[COL_CHUNK_LO].into(); + let chunk_hi: LB::Expr = local[COL_CHUNK_HI].into(); + let state_prev_lo: LB::Expr = local[COL_STATE_PREV_LO].into(); + let state_prev_hi: LB::Expr = local[COL_STATE_PREV_HI].into(); + let state_new_lo: LB::Expr = local[COL_STATE_NEW_LO].into(); + let state_new_hi: LB::Expr = local[COL_STATE_NEW_HI].into(); + let state_out_lo: LB::Expr = local[COL_STATE_OUT_LO].into(); + let state_out_hi: LB::Expr = local[COL_STATE_OUT_HI].into(); + let cleared_bytes: [LB::Var; 8] = array::from_fn(|i| local[CLEARED_BYTES_RANGE.start + i]); + let padded_bytes: [LB::Var; 8] = array::from_fn(|i| local[PADDED_BYTES_RANGE.start + i]); + let chunk_bytes: [LB::Var; 8] = array::from_fn(|i| local[CHUNK_BYTES_RANGE.start + i]); + let state_prev_bytes: [LB::Var; 8] = + array::from_fn(|i| local[STATE_PREV_BYTES_RANGE.start + i]); + let state_new_bytes: [LB::Var; 8] = array::from_fn(|i| local[STATE_NEW_BYTES_RANGE.start + i]); + + // Σ b_j (= `is_last_block_period`); `andnot_mask` and + // `padding_mask`, byte-decomposed, as `Σ_j b_j · MASK_BYTE[i][j]` + // inlines (`mask_byte` extracts byte `i` from the existing + // verifier-known `*_LO`/`*_HI` per-`byte_offset` constants, so the + // per-byte tables are correct by construction from the + // already-verified 32-bit ones). + let mut b_sum = LB::Expr::ZERO; + let mut andnot_mask_bytes: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); + let mut padding_mask_bytes: [LB::Expr; 8] = array::from_fn(|_| LB::Expr::ZERO); + for (j, col) in COL_B_RANGE.enumerate() { + let b_j: LB::Expr = local[col].into(); + b_sum += b_j.clone(); + for i in 0..8 { + let andnot_byte = mask_byte(ANDNOT_MASK_LO[j], ANDNOT_MASK_HI[j], i); + let padding_byte = mask_byte(PADDING_MASK_LO[j], PADDING_MASK_HI[j], i); + andnot_mask_bytes[i] += LB::Expr::from(Felt::from(andnot_byte)) * b_j.clone(); + padding_mask_bytes[i] += LB::Expr::from(Felt::from(padding_byte)) * b_j.clone(); } + } - // Derived signals (see the design notes - // §"Derived multiplicity signals"). - let is_intra: LB::Expr = LB::Expr::ONE - is_first_block.clone(); - let is_first_row_of_invocation: LB::Expr = p_first * is_first_block; - let is_pad: LB::Expr = is_zero_next.clone() - is_zero; - let is_verbatim: LB::Expr = LB::Expr::ONE - is_zero_next; - - // Per-row address expressions. - let hundred_seq = LB::Expr::from(Felt::from(100u8)) * sponge_seq_id.clone(); - let ninety_nine_idx = LB::Expr::from(Felt::from(99u8)) * p_idx.clone(); - let addr_state_lane_prev = - hundred_seq.clone() - ninety_nine_idx.clone() - LB::Expr::from(Felt::from(128u8)); - let addr_state_lane_new = hundred_seq.clone() - ninety_nine_idx.clone(); - let addr_rc = hundred_seq.clone() - + LB::Expr::from(Felt::from(28u8)) * p_idx - + LB::Expr::from(Felt::from(25u8)); - let addr_squeeze = - hundred_seq.clone() - ninety_nine_idx + LB::Expr::from(Felt::from(3072u32)); - let addr_lane16 = hundred_seq - LB::Expr::from(Felt::from(2484u32)); - let chunk_addr_base = - Felt::new(CHUNK_ADDR_BASE).expect("CHUNK_ADDR_BASE fits in canonical Goldilocks"); - let addr_chunk = LB::Expr::from(chunk_addr_base) + chunk_ptr.clone(); - - // Per-message multiplicity factors, all gated by `act`. - let mult_prev_perm: LB::Expr = LB::Expr::from(Felt::from(2u8)) * act.clone() * is_intra; - let mult_new_state: LB::Expr = - LB::Expr::ZERO - LB::Expr::from(Felt::from(2u8)) * act.clone(); - let mult_rc: LB::Expr = - LB::Expr::ZERO - LB::Expr::from(Felt::from(1u8)) * act.clone() * p_rc_active; - let mult_squeeze: LB::Expr = - LB::Expr::from(Felt::from(2u8)) * act.clone() * p_squeeze_active * b_sum.clone(); - let mult_lane16_consume: LB::Expr = - LB::Expr::from(Felt::from(2u8)) * act.clone() * b_sum.clone(); - let mult_lane16_provide: LB::Expr = - LB::Expr::ZERO - LB::Expr::from(Felt::from(2u8)) * act.clone() * b_sum.clone(); - - let andnot_tag = LB::Expr::from(Felt::from(BytePairOp::AndNot.tag())); - let xor_tag = LB::Expr::from(Felt::from(BytePairOp::Xor.tag())); - - let interaction_deg = Deg { v: 1, u: 1 }; - // FLATTENED to lqd 2: the mutex outer flags are folded into each - // insert's multiplicity (sound — the one-hot flags are binary on the - // rows where they fire, the precondition the mutex fold already - // relied on), and the 13 fractions are partitioned ≤ 3 per column - // so every closing constraint is degree ≤ 5. Column-degree hints are - // ignored on the constraint path. - let pair_deg = Deg { v: 4, u: 2 }; - let triple_deg = Deg { v: 5, u: 3 }; - let solo_deg = Deg { v: 4, u: 1 }; - let mixed_deg = Deg { v: 5, u: 2 }; - - // col 0 (running sum): Memory64 state-lane new-state + prev-perm — the - // two lowest-degree fractions, so the gated last-row close stays ≤ 5. + // Derived signals (see the design notes + // §"Derived multiplicity signals"). + let is_intra: LB::Expr = LB::Expr::ONE - is_first_block.clone(); + let is_first_row_of_invocation: LB::Expr = p_first * is_first_block; + let is_pad: LB::Expr = is_zero_next.clone() - is_zero; + let is_verbatim: LB::Expr = LB::Expr::ONE - is_zero_next; + + // Per-row address expressions. + let hundred_seq = LB::Expr::from(Felt::from(100u8)) * sponge_seq_id.clone(); + let ninety_nine_idx = LB::Expr::from(Felt::from(99u8)) * p_idx.clone(); + let addr_state_lane_prev = + hundred_seq.clone() - ninety_nine_idx.clone() - LB::Expr::from(Felt::from(128u8)); + let addr_state_lane_new = hundred_seq.clone() - ninety_nine_idx.clone(); + let addr_rc = hundred_seq.clone() + + LB::Expr::from(Felt::from(28u8)) * p_idx + + LB::Expr::from(Felt::from(25u8)); + let addr_squeeze = hundred_seq.clone() - ninety_nine_idx + LB::Expr::from(Felt::from(3072u32)); + let addr_lane16 = hundred_seq - LB::Expr::from(Felt::from(2484u32)); + let chunk_addr_base = + Felt::new(CHUNK_ADDR_BASE).expect("CHUNK_ADDR_BASE fits in canonical Goldilocks"); + let addr_chunk = LB::Expr::from(chunk_addr_base) + chunk_ptr.clone(); + + // Per-message multiplicity factors, all gated by `act`. + let mult_prev_perm: LB::Expr = LB::Expr::from(Felt::from(2u8)) * act.clone() * is_intra; + let mult_new_state: LB::Expr = LB::Expr::ZERO - LB::Expr::from(Felt::from(2u8)) * act.clone(); + let mult_rc: LB::Expr = + LB::Expr::ZERO - LB::Expr::from(Felt::from(1u8)) * act.clone() * p_rc_active; + let mult_squeeze: LB::Expr = + LB::Expr::from(Felt::from(2u8)) * act.clone() * p_squeeze_active * b_sum.clone(); + let mult_lane16_consume: LB::Expr = + LB::Expr::from(Felt::from(2u8)) * act.clone() * b_sum.clone(); + let mult_lane16_provide: LB::Expr = + LB::Expr::ZERO - LB::Expr::from(Felt::from(2u8)) * act.clone() * b_sum.clone(); + + let andnot_tag = LB::Expr::from(Felt::from(BytePairOp::AndNot.tag())); + let xor_tag = LB::Expr::from(Felt::from(BytePairOp::Xor.tag())); + + let interaction_deg = Deg { v: 1, u: 1 }; + // FLATTENED to lqd 2: the mutex outer flags are folded into each + // insert's multiplicity (sound — the one-hot flags are binary on the + // rows where they fire, the precondition the mutex fold already + // relied on), and the 13 fractions are partitioned ≤ 3 per column + // so every closing constraint is degree ≤ 5. Column-degree hints are + // ignored on the constraint path. + let pair_deg = Deg { v: 4, u: 2 }; + let triple_deg = Deg { v: 5, u: 3 }; + let solo_deg = Deg { v: 4, u: 1 }; + let mixed_deg = Deg { v: 5, u: 2 }; + + // col 0 (running sum): Memory64 state-lane new-state + prev-perm — the + // two lowest-degree fractions, so the gated last-row close stays ≤ 5. + frac_col!( + builder, + "memory64", + pair_deg, + ( + "new-state", + p_state_lane.clone() * mult_new_state.clone(), + Memory64Msg { + addr: addr_state_lane_new.clone(), + lo: state_new_lo.clone(), + hi: state_new_hi.clone(), + }, + interaction_deg + ), + ( + "prev-perm", + p_state_lane.clone() * mult_prev_perm.clone(), + Memory64Msg { + addr: addr_state_lane_prev.clone(), + lo: state_prev_lo.clone(), + hi: state_prev_hi.clone(), + }, + interaction_deg + ), + ); + // col 1: Memory64 state-lane rc + lane-16 0x80 consume / provide. + frac_col!( + builder, + "memory64", + triple_deg, + ( + "rc", + p_state_lane.clone() * mult_rc.clone(), + Memory64Msg { + addr: addr_rc.clone(), + lo: rc_lo.clone(), + hi: rc_hi.clone() + }, + interaction_deg + ), + ( + "lane16-consume", + p_pad_0x80.clone() * mult_lane16_consume.clone(), + Memory64Msg { + addr: addr_lane16.clone(), + lo: state_prev_lo.clone(), + hi: state_prev_hi.clone(), + }, + interaction_deg + ), + ( + "lane16-provide", + p_pad_0x80.clone() * mult_lane16_provide.clone(), + Memory64Msg { + addr: addr_lane16.clone(), + lo: state_new_lo.clone(), + hi: state_new_hi.clone(), + }, + interaction_deg + ), + ); + // col 2: Memory64 squeeze — a degree-4 multiplicity, alone (closing 4). + frac_col!( + builder, + "memory64", + solo_deg, + ( + "squeeze", + p_state_lane.clone() * mult_squeeze.clone(), + Memory64Msg { + addr: addr_squeeze.clone(), + lo: state_out_lo.clone(), + hi: state_out_hi.clone() + }, + interaction_deg + ), + ); + + // cols 3..15: pad-row `BytePairLut` requests, split into three + // four-column groups: `andnot` (mask, chunk) → cleared, + // `xor-padding` (cleared, padding_mask) → padded, and `xor-state` + // (state_prev, padded) → state_new. Each group checks eight bytes, + // two per column. + let pad_mult = p_rate_block.clone() * is_pad * act.clone(); + for pair in 0..4 { + let i0 = pair * 2; + let i1 = i0 + 1; frac_col!( builder, - "memory64", + "byte-pair-lut", pair_deg, ( - "new-state", - p_state_lane.clone() * mult_new_state.clone(), - Memory64Msg { - addr: addr_state_lane_new.clone(), - lo: state_new_lo.clone(), - hi: state_new_hi.clone(), + "andnot", + pad_mult.clone(), + BytePairLutMsg { + op: andnot_tag.clone(), + a: andnot_mask_bytes[i0].clone(), + b: chunk_bytes[i0].into(), + c: cleared_bytes[i0].into() }, interaction_deg ), ( - "prev-perm", - p_state_lane.clone() * mult_prev_perm.clone(), - Memory64Msg { - addr: addr_state_lane_prev.clone(), - lo: state_prev_lo.clone(), - hi: state_prev_hi.clone(), + "andnot", + pad_mult.clone(), + BytePairLutMsg { + op: andnot_tag.clone(), + a: andnot_mask_bytes[i1].clone(), + b: chunk_bytes[i1].into(), + c: cleared_bytes[i1].into() }, interaction_deg ), ); - // col 1: Memory64 state-lane rc + lane-16 0x80 consume / provide. + } + for pair in 0..4 { + let i0 = pair * 2; + let i1 = i0 + 1; frac_col!( builder, - "memory64", - triple_deg, + "byte-pair-lut", + pair_deg, ( - "rc", - p_state_lane.clone() * mult_rc.clone(), - Memory64Msg { - addr: addr_rc.clone(), - lo: rc_lo.clone(), - hi: rc_hi.clone() + "xor-padding", + pad_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: cleared_bytes[i0].into(), + b: padding_mask_bytes[i0].clone(), + c: padded_bytes[i0].into() }, interaction_deg ), ( - "lane16-consume", - p_pad_0x80.clone() * mult_lane16_consume.clone(), - Memory64Msg { - addr: addr_lane16.clone(), - lo: state_prev_lo.clone(), - hi: state_prev_hi.clone(), + "xor-padding", + pad_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: cleared_bytes[i1].into(), + b: padding_mask_bytes[i1].clone(), + c: padded_bytes[i1].into() + }, + interaction_deg + ), + ); + } + for pair in 0..4 { + let i0 = pair * 2; + let i1 = i0 + 1; + frac_col!( + builder, + "byte-pair-lut", + pair_deg, + ( + "xor-state", + pad_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i0].into(), + b: padded_bytes[i0].into(), + c: state_new_bytes[i0].into() }, interaction_deg ), ( - "lane16-provide", - p_pad_0x80.clone() * mult_lane16_provide.clone(), - Memory64Msg { - addr: addr_lane16.clone(), - lo: state_new_lo.clone(), - hi: state_new_hi.clone(), + "xor-state", + pad_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i1].into(), + b: padded_bytes[i1].into(), + c: state_new_bytes[i1].into() }, interaction_deg ), ); - // col 2: Memory64 squeeze — a degree-4 multiplicity, alone (closing 4). + } + + // cols 15..19: verbatim `xor-state` (state_prev, chunk) → + // state_new, 8 bytes. + let verbatim_mult = p_rate_block.clone() * is_verbatim * act.clone(); + for pair in 0..4 { + let i0 = pair * 2; + let i1 = i0 + 1; frac_col!( builder, - "memory64", - solo_deg, + "byte-pair-lut", + pair_deg, ( - "squeeze", - p_state_lane.clone() * mult_squeeze.clone(), - Memory64Msg { - addr: addr_squeeze.clone(), - lo: state_out_lo.clone(), - hi: state_out_hi.clone() + "xor-state-verbatim", + verbatim_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i0].into(), + b: chunk_bytes[i0].into(), + c: state_new_bytes[i0].into() + }, + interaction_deg + ), + ( + "xor-state-verbatim", + verbatim_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i1].into(), + b: chunk_bytes[i1].into(), + c: state_new_bytes[i1].into() }, interaction_deg ), ); + } - // cols 3..7: pad-row `BytePairLut` byte requires, 8 bytes each — - // `andnot` (mask, chunk) → cleared, `xor-padding` (cleared, - // padding_mask) → padded, `xor-state` (state_prev, padded) → - // state_new. Same 8-byte-request pattern the round chiplet uses; - // each op verifies its own row's bytes directly, no intermediate - // chiplet or chain trick. - let pad_mult = p_rate_block.clone() * is_pad * act.clone(); - for pair in 0..4 { - let i0 = pair * 2; - let i1 = i0 + 1; - frac_col!( - builder, - "byte-pair-lut", - pair_deg, - ( - "andnot", - pad_mult.clone(), - BytePairLutMsg { - op: andnot_tag.clone(), - a: andnot_mask_bytes[i0].clone(), - b: chunk_bytes[i0].into(), - c: cleared_bytes[i0].into() - }, - interaction_deg - ), - ( - "andnot", - pad_mult.clone(), - BytePairLutMsg { - op: andnot_tag.clone(), - a: andnot_mask_bytes[i1].clone(), - b: chunk_bytes[i1].into(), - c: cleared_bytes[i1].into() - }, - interaction_deg - ), - ); - } - for pair in 0..4 { - let i0 = pair * 2; - let i1 = i0 + 1; - frac_col!( - builder, - "byte-pair-lut", - pair_deg, - ( - "xor-padding", - pad_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: cleared_bytes[i0].into(), - b: padding_mask_bytes[i0].clone(), - c: padded_bytes[i0].into() - }, - interaction_deg - ), - ( - "xor-padding", - pad_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: cleared_bytes[i1].into(), - b: padding_mask_bytes[i1].clone(), - c: padded_bytes[i1].into() - }, - interaction_deg - ), - ); - } - for pair in 0..4 { - let i0 = pair * 2; - let i1 = i0 + 1; - frac_col!( - builder, - "byte-pair-lut", - pair_deg, - ( - "xor-state", - pad_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i0].into(), - b: padded_bytes[i0].into(), - c: state_new_bytes[i0].into() - }, - interaction_deg - ), - ( - "xor-state", - pad_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i1].into(), - b: padded_bytes[i1].into(), - c: state_new_bytes[i1].into() - }, - interaction_deg - ), - ); - } - - // cols 7..11: verbatim `xor-state` (state_prev, chunk) → - // state_new, 8 bytes. - let verbatim_mult = p_rate_block.clone() * is_verbatim * act.clone(); - for pair in 0..4 { - let i0 = pair * 2; - let i1 = i0 + 1; - frac_col!( - builder, - "byte-pair-lut", - pair_deg, - ( - "xor-state-verbatim", - verbatim_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i0].into(), - b: chunk_bytes[i0].into(), - c: state_new_bytes[i0].into() - }, - interaction_deg - ), - ( - "xor-state-verbatim", - verbatim_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i1].into(), - b: chunk_bytes[i1].into(), - c: state_new_bytes[i1].into() - }, - interaction_deg - ), - ); - } - - // cols 11..15: lane-16 `xor-lane16` (state_prev, PAD_CONST) → - // state_new, 8 bytes. `PAD_CONST_BYTES` is a plain constant (not - // selector-dependent), so the `b` field is a literal per byte. - let lane16_mult = p_pad_0x80.clone() * b_sum.clone() * act.clone(); - for pair in 0..4 { - let i0 = pair * 2; - let i1 = i0 + 1; - frac_col!( - builder, - "byte-pair-lut", - pair_deg, - ( - "xor-lane16", - lane16_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i0].into(), - b: LB::Expr::from(Felt::from(PAD_CONST_BYTES[i0])), - c: state_new_bytes[i0].into() - }, - interaction_deg - ), - ( - "xor-lane16", - lane16_mult.clone(), - BytePairLutMsg { - op: xor_tag.clone(), - a: state_prev_bytes[i1].into(), - b: LB::Expr::from(Felt::from(PAD_CONST_BYTES[i1])), - c: state_new_bytes[i1].into() - }, - interaction_deg - ), - ); - } - - // col 5: the KeccakSponge request + the chunk consume (a degree-4 - // multiplicity, paired → closing 5). Two independent inserts on - // different buses, bus-prefix-distinguished encodings keeping the - // contributions algebraically distinct. + // cols 19..23: lane-16 `xor-lane16` (state_prev, PAD_CONST) → + // state_new, 8 bytes. `PAD_CONST_BYTES` is a plain constant (not + // selector-dependent), so the `b` field is a literal per byte. + let lane16_mult = p_pad_0x80.clone() * b_sum.clone() * act.clone(); + for pair in 0..4 { + let i0 = pair * 2; + let i1 = i0 + 1; frac_col!( builder, - "ks-and-chunk", - mixed_deg, + "byte-pair-lut", + pair_deg, ( - "ks-request", - act.clone() * is_first_row_of_invocation.clone(), - KeccakSpongeMsg { - sponge_seq_id: sponge_seq_id.clone(), - chunk_ptr: chunk_ptr.clone(), - len_bytes: bytes_left.clone(), + "xor-lane16", + lane16_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i0].into(), + b: LB::Expr::from(Felt::from(PAD_CONST_BYTES[i0])), + c: state_new_bytes[i0].into() }, interaction_deg ), ( - "chunk-consume", - act.clone() - * (p_rate_block.clone() + p_extra.clone() * b_sum.clone()) - * is_chunk_avail.clone(), - Memory64Msg { - addr: addr_chunk.clone(), - lo: chunk_lo.clone(), - hi: chunk_hi.clone() + "xor-lane16", + lane16_mult.clone(), + BytePairLutMsg { + op: xor_tag.clone(), + a: state_prev_bytes[i1].into(), + b: LB::Expr::from(Felt::from(PAD_CONST_BYTES[i1])), + c: state_new_bytes[i1].into() }, interaction_deg ), ); } + + // col 23: the KeccakSponge request + the chunk consume (a degree-4 + // multiplicity, paired → closing 5). Two independent inserts on + // different buses, bus-prefix-distinguished encodings keeping the + // contributions algebraically distinct. + frac_col!( + builder, + "ks-and-chunk", + mixed_deg, + ( + "ks-request", + act.clone() * is_first_row_of_invocation.clone(), + KeccakSpongeMsg { + sponge_seq_id: sponge_seq_id.clone(), + chunk_ptr: chunk_ptr.clone(), + len_bytes: bytes_left.clone(), + }, + interaction_deg + ), + ( + "chunk-consume", + act.clone() + * (p_rate_block.clone() + p_extra.clone() * b_sum.clone()) + * is_chunk_avail.clone(), + Memory64Msg { + addr: addr_chunk.clone(), + lo: chunk_lo.clone(), + hi: chunk_hi.clone() + }, + interaction_deg + ), + ); } diff --git a/crates/precompiles-prover/src/hash/keccak/sponge/trace.rs b/crates/precompiles-prover/src/hash/keccak/sponge/trace.rs index 670e677bc5..07c2a8a510 100644 --- a/crates/precompiles-prover/src/hash/keccak/sponge/trace.rs +++ b/crates/precompiles-prover/src/hash/keccak/sponge/trace.rs @@ -382,8 +382,24 @@ fn compute_block_snapshots(inv: &Invocation, layout: &InvocationLayout) -> Vec RowMajorMatrix { + generate_trace_padded_to(requires, 0) +} + +/// Same as [`generate_trace`], but the trace height is at least `min_height` +/// (still rounded up to a power of two) — lets a caller sharing this +/// chiplet's row range with another AIR (see `hash::chunk_node_sponge`) pad +/// the sponge's trace up to match the other side's height. Pads past the +/// natural height are the sponge's own trailing inactive rows (`act = 0`, +/// the `sponge_seq_id` / `bytes_left` chains continued). +pub(crate) fn generate_trace_padded_to( + requires: SpongeRequires, + min_height: usize, +) -> RowMajorMatrix { let active_rows = requires.total_active_rows() as usize; - let height = active_rows.next_power_of_two().max(SPONGE_PERIOD); + let min_height = min_height + .checked_next_power_of_two() + .expect("minimum sponge trace height exceeds the host power-of-two range"); + let height = active_rows.next_power_of_two().max(SPONGE_PERIOD).max(min_height); let mut trace = Vec::with_capacity(height * NUM_MAIN_COLS); @@ -642,6 +658,8 @@ pub(crate) fn build_aux( mod tests { use std::vec; + use miden_core::utils::Matrix; + use super::*; #[test] @@ -672,4 +690,10 @@ mod tests { // lanes past the input). assert_eq!(Invocation { input: vec![0; 200] }.chunk_lanes(), 28); } + + #[test] + fn padded_height_rounds_the_floor_to_a_power_of_two() { + let trace = generate_trace_padded_to(SpongeRequires::new(), 33); + assert_eq!(trace.height(), 64); + } } diff --git a/crates/precompiles-prover/src/hash/mod.rs b/crates/precompiles-prover/src/hash/mod.rs index b2b4c27fa8..52a1d2b9cc 100644 --- a/crates/precompiles-prover/src/hash/mod.rs +++ b/crates/precompiles-prover/src/hash/mod.rs @@ -9,5 +9,6 @@ pub mod chunk; pub mod chunk_node; +pub mod chunk_node_sponge; pub mod keccak; pub mod memory64; diff --git a/crates/precompiles-prover/src/relations.rs b/crates/precompiles-prover/src/relations.rs index b41b91c5f3..bbf9cfb5f1 100644 --- a/crates/precompiles-prover/src/relations.rs +++ b/crates/precompiles-prover/src/relations.rs @@ -22,13 +22,13 @@ //! | 6 | `Poseidon2In` | `poseidon2::Poseidon2Air` | `(perm_seq_id, tag, c0, c1, c2, c3)`, `tag ∈ {0, 1, 2}` for rate0/rate1/cap | //! | 7 | `Poseidon2Out` | `poseidon2::Poseidon2Air` | `(perm_seq_id, d0, d1, d2, d3)` — digest = first 4 lanes of post-perm state | //! | 8 | `Binding` | transcript eval chips | `(h0, h1, h2, h3, value_tag, ptr)` — node hash ↦ typed value (self-referential) | -//! | 9 | `ChunkChain` | `chunk::ChunkAir` | `(chunk_seq_id_head, perm_seq_id_head)` — per-invocation chain head, in chunk's native namespace | -//! | 10 | `UintVal` | `uint::UintStoreAir` | `(ptr, bound_ptr, offset, c0..c3)` — 256-bit uint half: 4×32-bit recombined view at offset ∈ {0, 1} | +//! | 9 | `ChunkChain` | `hash::chunk_node_sponge::ChunkNodeSpongeAir` (chunk band) | `(chunk_seq_id_head, perm_seq_id_head)` — per-invocation chain head, in chunk's native namespace | +//! | 10 | `UintVal` | `uint::store_mul::UintStoreMulAir` (store band) | `(ptr, bound_ptr, c0..c7)` — complete 256-bit value as 8×32-bit recombined limbs | //! | 11 | `UintAdd` | `uint::add::UintAddAir` | `(bound_ptr, a_ptr, b_ptr, c_ptr)` — asserts `a + b ≡ c (mod p)` for uints sharing `bound_ptr` | //! | 12 | `UintMul` | `uint::mul::UintMulAir` | `(kappa_a, kappa_c, a_ptr, b_ptr, c_ptr, r_ptr, bound_ptr)` — asserts `κₐ·a·b + κ_c·c ≡ r (mod p)` for uints sharing `bound_ptr` | -//! | 13 | `UintLimbs` | `uint::UintStoreAir` | `(ptr, bound_ptr, offset, l0..l7)` — 256-bit uint half: raw 8×16-bit limb view at offset ∈ {0, 1} | -//! | 14 | `EcGroup` | `ec::groups::EcGroupsAir` | `(group_ptr, a_ptr, b_ptr, bound_ptr, scalar_bound_ptr)` — a short-Weierstrass group binding its curve context (params + base-field modulus + scalar-field modulus, the latter = `bound_ptr` while unconstrained) | -//! | 15 | `EcPoint` | `ec::EcPointStoreAir` | `(point_ptr, group_ptr, x_ptr, y_ptr, is_pai)` — a stored on-curve point (or the group's ∞ when `is_pai`) | +//! | 13 | `UintLimbs` | `uint::store_mul::UintStoreMulAir` (store band) | `(ptr, bound_ptr, l0..l15)` — raw 16×16-bit limb view of the complete 256-bit uint | +//! | 14 | `EcGroup` | `ec::point_store_groups::EcPointStoreGroupsAir` (group band) | `(group_ptr, a_ptr, b_ptr, bound_ptr, scalar_bound_ptr)` — a short-Weierstrass group binding its curve context (params + base-field modulus + scalar-field modulus, the latter = `bound_ptr` while unconstrained) | +//! | 15 | `EcPoint` | `ec::point_store_groups::EcPointStoreGroupsAir` (point band) | `(point_ptr, group_ptr, x_ptr, y_ptr, is_pai)` — a stored on-curve point (or the group's ∞ when `is_pai`) | //! | 16 | `EcGroupAdd` | `ec::add::EcGroupAddAir` | `(group_ptr, p_ptr, q_ptr, r_ptr)` — asserts `R = P + Q` in the group | //! | 17 | `EcOnCurveCert` | `ec::add::EcGroupAddAir`, `ec::msm::EcMsmAir` | `(group_ptr, r_ptr)` — an on-curve membership certificate for a fresh point `r`: provided by its minting op (a group-law add result, or an MSM `neg`'s value `−P`), consumed by `r`'s point-store row in place of the on-curve MAC trio | //! | 18 | `MsmTerm` | `ec::msm::EcMsmAir` | `(expr_ptr, idx, base_ptr, scalar_ptr)` — one term `P × s` of MSM expression `expr_ptr` at position `idx` | diff --git a/crates/precompiles-prover/src/session/mod.rs b/crates/precompiles-prover/src/session/mod.rs index 4e396c4e48..2a7f4159eb 100644 --- a/crates/precompiles-prover/src/session/mod.rs +++ b/crates/precompiles-prover/src/session/mod.rs @@ -1,4 +1,4 @@ -//! Orchestration facade over the thirteen-chiplet stack: the Keccak +//! Orchestration facade over the ten-chiplet stack: the Keccak //! transcript, the uint store and its arithmetic relations, and the EC //! layer (group table + point store + group-law add). //! @@ -47,16 +47,17 @@ use crate::{ require, trace::{EcExprPtr, EcMsmRequires, generate_trace as msm_trace}, }, - trace::{EcGroupPtr, generate_traces as ec_store_traces}, + point_store_groups::trace::generate_trace as ec_store_trace, + trace::EcGroupPtr, }, hash::{ chunk::trace::ChunkRequires, - chunk_node::trace::generate_trace as chunk_node_trace, + chunk_node_sponge::trace::generate_trace as chunk_node_sponge_trace, keccak::{ digest::KeccakDigest, node::trace::KeccakNodeRequires, round::{RoundRequires, generate_trace as round_trace}, - sponge::trace::{SpongeRequires, generate_trace as sponge_trace}, + sponge::trace::SpongeRequires, }, }, math::{U256, from_limbs32, to_limbs32}, @@ -84,7 +85,7 @@ pub mod strategies; pub use prove::{ChipletAir, ChipletMultiAir, VerifyError, verify_deferred, verify_stark}; /// Number of chiplets in the stack (= the width of [`SessionTraces::mains`]). -pub const NUM_CHIPLETS: usize = 12; +pub const NUM_CHIPLETS: usize = 10; /// Stateful builder over the full chiplet stack. /// @@ -455,9 +456,11 @@ impl Session { self.eval.assert_no_stray_values(); // EcCreate rows hash the group pointer and bind it through their EcPoint consume. let eval = trace_span!("eval", eval_trace(self.eval, root)); - let chunk_node = trace_span!("chunk_node", chunk_node_trace(self.chunk, self.node)); + let chunk_node_sponge = trace_span!( + "chunk_node_sponge", + chunk_node_sponge_trace(self.chunk, self.node, self.sponge) + ); let p2 = trace_span!("poseidon2", p2_trace(self.p2)); - let sponge = trace_span!("keccak_sponge", sponge_trace(self.sponge)); let round = trace_span!("keccak_round", round_trace(self.round, &mut self.bpl)); // The relation traces route their store demand as they lay, so // they run before the store reads its provide multiplicities; @@ -480,19 +483,17 @@ impl Session { // (which is traced last, below). let ec_add = trace_span!("ec_add", ec_add_trace(self.ec.add, &mut self.ec.store, &mut self.bpl)); - let (ec_groups, ec) = trace_span!("ec_store", ec_store_traces(self.ec.store)); + let ec = trace_span!("ec_store", ec_store_trace(self.ec.store)); let bpl = trace_span!("byte_pair_lut", bpl_trace(self.bpl)); SessionTraces { - chunk_node, + chunk_node_sponge, p2, round, bpl, - sponge, eval, uint, add, - ec_groups, ec, ec_add, msm, @@ -507,19 +508,17 @@ impl Default for Session { } } -/// The twelve chiplet main traces plus the transcript root, ready to +/// The ten chiplet main traces plus the transcript root, ready to /// feed `prove_multi` or a bus-balance check. #[derive(Debug)] pub struct SessionTraces { - chunk_node: RowMajorMatrix, + chunk_node_sponge: RowMajorMatrix, p2: RowMajorMatrix, round: RowMajorMatrix, bpl: RowMajorMatrix, - sponge: RowMajorMatrix, eval: RowMajorMatrix, uint: RowMajorMatrix, add: RowMajorMatrix, - ec_groups: RowMajorMatrix, ec: RowMajorMatrix, ec_add: RowMajorMatrix, msm: RowMajorMatrix, @@ -527,41 +526,37 @@ pub struct SessionTraces { } impl SessionTraces { - /// The twelve main traces in canonical chiplet order: chunk-node, - /// poseidon2, round, byte_pair_lut, sponge, eval, uint-store-mul, - /// uint-add, ec-groups, ec-points, ec-add, ec-msm. The AIRs, provers, - /// and public values a caller assembles must line up with this order. + /// The ten main traces in canonical chiplet order: chunk-node-sponge, + /// poseidon2, round, byte_pair_lut, eval, uint-store-mul, uint-add, + /// ec-point-store-groups, ec-add, ec-msm. The AIRs, provers, and + /// public values a caller assembles must line up with this order. pub fn mains(&self) -> [&RowMajorMatrix; NUM_CHIPLETS] { [ - &self.chunk_node, + &self.chunk_node_sponge, &self.p2, &self.round, &self.bpl, - &self.sponge, &self.eval, &self.uint, &self.add, - &self.ec_groups, &self.ec, &self.ec_add, &self.msm, ] } - /// The twelve main traces by value in [`mains`](Self::mains) order, + /// The ten main traces by value in [`mains`](Self::mains) order, /// consuming the bundle — lets the prover take ownership rather than /// clone the (potentially large) traces. pub fn into_mains(self) -> Vec> { vec![ - self.chunk_node, + self.chunk_node_sponge, self.p2, self.round, self.bpl, - self.sponge, self.eval, self.uint, self.add, - self.ec_groups, self.ec, self.ec_add, self.msm, diff --git a/crates/precompiles-prover/src/session/preprocessed_cache.rs b/crates/precompiles-prover/src/session/preprocessed_cache.rs index 3ba403a046..1f9ca2566c 100644 --- a/crates/precompiles-prover/src/session/preprocessed_cache.rs +++ b/crates/precompiles-prover/src/session/preprocessed_cache.rs @@ -1,7 +1,7 @@ //! Process-lifetime caching of the chiplet stack's preprocessed bundle. //! //! `Preprocessed::build` LDEs and commits the fixed `BytePairLut` table — a -//! pure function of the (fixed, 12-AIR) chiplet list and the STARK config's +//! pure function of the fixed ten-chiplet list and the STARK config's //! blowup/LMCS/DFT — yet both `prove_stark` and `verify_stark` rebuild it on //! every call. Under `std`, each hash function's bundle is built once per //! process and reused via `OnceLock`; without `std` (e.g. a `no_std` @@ -49,7 +49,7 @@ where } } -/// The AIR list never varies across calls (the fixed 12-chiplet stack), and +/// The AIR list never varies across calls (the fixed ten-chiplet stack), and /// `Preprocessed::build` reads only `statement.airs()` — never the public /// inputs — so a scratch statement with dummy public inputs builds the exact /// same bundle as the real per-proof statement would. diff --git a/crates/precompiles-prover/src/session/prove.rs b/crates/precompiles-prover/src/session/prove.rs index b34c5d7c72..0dc4e7fddd 100644 --- a/crates/precompiles-prover/src/session/prove.rs +++ b/crates/precompiles-prover/src/session/prove.rs @@ -1,6 +1,6 @@ //! Multi-AIR proving for the chiplet stack. //! -//! [`ChipletAir`] wraps the twelve heterogeneous AIRs into one enum (the +//! [`ChipletAir`] wraps the ten heterogeneous AIRs into one enum (the //! `MultiAir::Air` type); [`ChipletMultiAir`] owns them and closes the //! cross-chiplet LogUp identity — `Σ σ = 0` — in //! [`MultiAir::eval_external`]. @@ -36,11 +36,8 @@ const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024; use crate::{ ProveError, - ec::{EcPointStoreAir, add::EcGroupAddAir, groups::EcGroupsAir, msm::EcMsmAir}, - hash::{ - chunk_node::ChunkNodeAir, - keccak::{round::KeccakRoundAir, sponge::KeccakSpongeAir}, - }, + ec::{add::EcGroupAddAir, msm::EcMsmAir, point_store_groups::EcPointStoreGroupsAir}, + hash::{chunk_node_sponge::ChunkNodeSpongeAir, keccak::round::KeccakRoundAir}, logup::{Challenges, LookupMessage, lookup_challenges_from_slice, sigma_sum}, primitives::byte_pair_lut::BytePairLutAir, session::{NUM_CHIPLETS, SessionTraces, fixed_ecgroup_msgs, fixed_uintval_msgs}, @@ -56,21 +53,19 @@ use crate::{ uint::{add::UintAddAir, store_mul::UintStoreMulAir}, }; -/// The twelve chiplet AIRs wrapped into one enum — the heterogeneous +/// The ten chiplet AIRs wrapped into one enum — the heterogeneous /// `MultiAir::Air` type. Variant order is the canonical /// [`SessionTraces::mains`] order. #[derive(Clone, Debug)] pub enum ChipletAir { - ChunkNode, + ChunkNodeSponge, Poseidon2, KeccakRound, BytePairLut, - KeccakSponge, TranscriptEval, UintStoreMul, UintAdd, - EcGroups, - EcPointStore, + EcPointStoreGroups, EcGroupAdd, EcMsm, } @@ -78,16 +73,14 @@ pub enum ChipletAir { macro_rules! delegate { ($self:ident, $method:ident $(, $arg:expr)*) => { match $self { - ChipletAir::ChunkNode => ChunkNodeAir.$method($($arg),*), + ChipletAir::ChunkNodeSponge => ChunkNodeSpongeAir.$method($($arg),*), ChipletAir::Poseidon2 => Poseidon2Air.$method($($arg),*), ChipletAir::KeccakRound => KeccakRoundAir.$method($($arg),*), ChipletAir::BytePairLut => BytePairLutAir.$method($($arg),*), - ChipletAir::KeccakSponge => KeccakSpongeAir.$method($($arg),*), ChipletAir::TranscriptEval => TranscriptEvalAir.$method($($arg),*), ChipletAir::UintStoreMul => UintStoreMulAir.$method($($arg),*), ChipletAir::UintAdd => UintAddAir.$method($($arg),*), - ChipletAir::EcGroups => EcGroupsAir.$method($($arg),*), - ChipletAir::EcPointStore => EcPointStoreAir.$method($($arg),*), + ChipletAir::EcPointStoreGroups => EcPointStoreGroupsAir.$method($($arg),*), ChipletAir::EcGroupAdd => EcGroupAddAir.$method($($arg),*), ChipletAir::EcMsm => EcMsmAir.$method($($arg),*), } @@ -103,19 +96,17 @@ where } impl ChipletAir { - /// The twelve AIRs in canonical [`SessionTraces::mains`] order. + /// The ten AIRs in canonical [`SessionTraces::mains`] order. pub fn all() -> [ChipletAir; NUM_CHIPLETS] { [ - ChipletAir::ChunkNode, + ChipletAir::ChunkNodeSponge, ChipletAir::Poseidon2, ChipletAir::KeccakRound, ChipletAir::BytePairLut, - ChipletAir::KeccakSponge, ChipletAir::TranscriptEval, ChipletAir::UintStoreMul, ChipletAir::UintAdd, - ChipletAir::EcGroups, - ChipletAir::EcPointStore, + ChipletAir::EcPointStoreGroups, ChipletAir::EcGroupAdd, ChipletAir::EcMsm, ] @@ -161,23 +152,21 @@ impl LiftedAir for ChipletAir { } fn eval>(&self, builder: &mut AB) { match self { - ChipletAir::ChunkNode => eval_lifted(&ChunkNodeAir, builder), + ChipletAir::ChunkNodeSponge => eval_lifted(&ChunkNodeSpongeAir, builder), ChipletAir::Poseidon2 => eval_lifted(&Poseidon2Air, builder), ChipletAir::KeccakRound => eval_lifted(&KeccakRoundAir, builder), ChipletAir::BytePairLut => eval_lifted(&BytePairLutAir, builder), - ChipletAir::KeccakSponge => eval_lifted(&KeccakSpongeAir, builder), ChipletAir::TranscriptEval => eval_lifted(&TranscriptEvalAir, builder), ChipletAir::UintStoreMul => eval_lifted(&UintStoreMulAir, builder), ChipletAir::UintAdd => eval_lifted(&UintAddAir, builder), - ChipletAir::EcGroups => eval_lifted(&EcGroupsAir, builder), - ChipletAir::EcPointStore => eval_lifted(&EcPointStoreAir, builder), + ChipletAir::EcPointStoreGroups => eval_lifted(&EcPointStoreGroupsAir, builder), ChipletAir::EcGroupAdd => eval_lifted(&EcGroupAddAir, builder), ChipletAir::EcMsm => eval_lifted(&EcMsmAir, builder), } } } -/// The chiplet stack as a [`MultiAir`]: owns the thirteen AIRs (in canonical +/// The chiplet stack as a [`MultiAir`]: owns the ten AIRs (in canonical /// order) and closes the cross-chiplet LogUp identity — `Σ σ = 0` over /// every AIR's committed residue — in [`eval_external`](Self::eval_external). #[derive(Debug, Clone)] @@ -252,7 +241,7 @@ impl MultiAir for ChipletMultiAir { impl SessionTraces { /// Build the [`ProverStatement`]: the [`ChipletMultiAir`] + the shared - /// `air_inputs` (the transcript root) + the thirteen main traces in + /// `air_inputs` (the transcript root) + the ten main traces in /// canonical [`mains`](Self::mains) order. fn prover_statement(&self) -> ProverStatement { let statement = Statement::new(ChipletMultiAir::new(), self.air_inputs(), Vec::new()) @@ -261,10 +250,8 @@ impl SessionTraces { ProverStatement::new(statement, mains).expect("chiplet trace shapes are valid") } - /// Per-AIR `check_constraints` under the legacy fast test config — a cheap - /// local-constraint sanity pass (catches AIR regressions before the more - /// opaque `prove` failure; no cross-chiplet bus balance, which only the - /// full prove/verify closes via `eval_external`). + /// `check_constraints` under the legacy fast test config — a cheap constraint sanity pass + /// covering each AIR and the cross-chiplet assertion returned by `eval_external`. pub fn check(&self) { check_constraints(&self.prover_statement(), test_challenger()); } @@ -469,3 +456,32 @@ pub enum VerifyError { #[error("deferred proof is not STARK-backed")] InvalidDeferredProof, } + +#[cfg(test)] +mod tests { + use miden_core::field::PrimeCharacteristicRing; + + use super::*; + + /// The external assertion is part of the production relation but excluded from the ACE + /// circuit digest. This test guards its cardinality; raw bus-balance tests cover the + /// underlying lookup semantics independently. + #[test] + fn chiplet_multi_air_exposes_the_sigma_closure() { + let challenges = [ + QuadFelt::new([Felt::from(3u32), Felt::from(5u32)]), + QuadFelt::new([Felt::from(7u32), Felt::from(11u32)]), + ]; + let aux_values: [[QuadFelt; 1]; NUM_CHIPLETS] = core::array::from_fn(|i| { + [QuadFelt::new([Felt::from((i + 1) as u32), Felt::from((2 * i + 1) as u32)])] + }); + let aux_refs: Vec<&[QuadFelt]> = aux_values.iter().map(<[QuadFelt; 1]>::as_slice).collect(); + + let assertions = ChipletMultiAir::new() + .eval_external(&challenges, &[], &[], &aux_refs, &[]) + .expect("fixed boundary denominators are non-zero for the fixture"); + + assert_eq!(assertions.len(), 1, "the relation exposes exactly one external assertion"); + assert_ne!(assertions[0], QuadFelt::ZERO, "the closure fixture must be non-vacuous"); + } +} diff --git a/crates/precompiles-prover/src/snapshots/miden_precompiles_prover__stark_config__tests__precompile_relation_digest_matches_current_air.snap b/crates/precompiles-prover/src/snapshots/miden_precompiles_prover__stark_config__tests__precompile_relation_digest_matches_current_air.snap index e0bf3b31dc..92ff8152a9 100644 --- a/crates/precompiles-prover/src/snapshots/miden_precompiles_prover__stark_config__tests__precompile_relation_digest_matches_current_air.snap +++ b/crates/precompiles-prover/src/snapshots/miden_precompiles_prover__stark_config__tests__precompile_relation_digest_matches_current_air.snap @@ -2,7 +2,7 @@ source: crates/precompiles-prover/src/stark_config.rs expression: snapshot --- -num_inputs: 3128 -num_eval_gates: 9664 -stream_len: 12680 -relation_digest: [15901056294547705196, 13548154566962352054, 13148050606838836712, 2433548564999773594] +num_inputs: 3072 +num_eval_gates: 9576 +stream_len: 12592 +relation_digest: [14908530032554120817, 6253902297572432860, 17042470880692173314, 1364863901074227591] diff --git a/crates/precompiles-prover/src/stark_config.rs b/crates/precompiles-prover/src/stark_config.rs index 82b72a69a2..dc4b1edb29 100644 --- a/crates/precompiles-prover/src/stark_config.rs +++ b/crates/precompiles-prover/src/stark_config.rs @@ -48,10 +48,10 @@ const COMPRESSION_INPUTS: usize = 2; /// the lifted STARK protocol outside this circuit hash. /// Keep this in sync with [`crate::ace::build_precompile_multi_air_ace_circuit`]. pub const PRECOMPILE_RELATION_DIGEST: RelationDigest = [ - Felt::new_unchecked(15901056294547705196), - Felt::new_unchecked(13548154566962352054), - Felt::new_unchecked(13148050606838836712), - Felt::new_unchecked(2433548564999773594), + Felt::new_unchecked(14908530032554120817), + Felt::new_unchecked(6253902297572432860), + Felt::new_unchecked(17042470880692173314), + Felt::new_unchecked(1364863901074227591), ]; /// Default hash function for compatibility APIs such as /// [`SessionTraces::prove`](crate::session::SessionTraces::prove). @@ -69,7 +69,7 @@ pub const DEFAULT_HASH_FUNCTION: miden_core::proof::HashFunction = /// evolve together: every chiplet AIR in /// [`ChipletAir`](crate::session::ChipletAir) closes at a `log_quotient_degree` /// well under the core VM's degree-8 constraints (see the -/// `log_quotient_degrees_fit_the_blowup` test), so `log_blowup` could be +/// `ace::tests::quotient_chunks_match_the_symbolic_derivation` test), so `log_blowup` could be /// lowered independently of the core VM in the future. That has not been done /// here, and doing so would need a dedicated security review of the /// resulting FRI parameters before use outside benchmarking. @@ -168,10 +168,9 @@ mod tests { extern crate alloc; use alloc::{format, vec::Vec}; - use miden_ace_codegen::{AceConfig, LayoutKind}; use miden_core::{Felt, crypto::hash::Poseidon2}; - use crate::{ace, session::NUM_CHIPLETS}; + use crate::ace; const PROTOCOL_ID: u64 = 0; const REGEN_HINT: &str = "update PRECOMPILE_RELATION_DIGEST in crates/precompiles-prover/src/stark_config.rs and accept the insta snapshot"; @@ -179,12 +178,7 @@ mod tests { /// Snapshot test: catches any precompile chiplet AIR change that alters the constraint circuit. #[test] fn precompile_relation_digest_matches_current_air() { - let config = AceConfig { - num_quotient_chunks: 8, - layout: LayoutKind::Masm, - num_airs: NUM_CHIPLETS, - }; - let circuit = ace::build_precompile_multi_air_ace_circuit(config).unwrap(); + let circuit = ace::build_precompile_multi_air_ace_circuit().unwrap(); let encoded = circuit.to_ace().unwrap(); let circuit_commitment: [Felt; 4] = encoded.circuit_hash().into(); diff --git a/crates/precompiles-prover/src/tests/bus_balance.rs b/crates/precompiles-prover/src/tests/bus_balance.rs index 7475c1dee1..11fd6dc8f8 100644 --- a/crates/precompiles-prover/src/tests/bus_balance.rs +++ b/crates/precompiles-prover/src/tests/bus_balance.rs @@ -10,11 +10,8 @@ use miden_core::{Felt, field::QuadFelt, utils::RowMajorMatrix}; use miden_lifted_air::LiftedAir; use crate::{ - ec::{EcPointStoreAir, add::EcGroupAddAir, groups::EcGroupsAir, msm::EcMsmAir}, - hash::{ - chunk_node::ChunkNodeAir, - keccak::{round::KeccakRoundAir, sponge::KeccakSpongeAir}, - }, + ec::{add::EcGroupAddAir, msm::EcMsmAir, point_store_groups::EcPointStoreGroupsAir}, + hash::{chunk_node_sponge::ChunkNodeSpongeAir, keccak::round::KeccakRoundAir}, logup::LookupMessage, primitives::byte_pair_lut::BytePairLutAir, session::{ChipletAir, NUM_CHIPLETS, fixed_ecgroup_msgs, fixed_uintval_msgs}, @@ -87,18 +84,20 @@ pub(crate) fn session_stack_residual( .find_map(|(replacement_idx, main)| (*replacement_idx == idx).then_some(*main)) .unwrap_or(mains[idx]); match air { - ChipletAir::ChunkNode => fold_balance(&ChunkNodeAir, main, challenges, &mut net), + ChipletAir::ChunkNodeSponge => { + fold_balance(&ChunkNodeSpongeAir, main, challenges, &mut net) + }, ChipletAir::Poseidon2 => fold_balance(&Poseidon2Air, main, challenges, &mut net), ChipletAir::KeccakRound => fold_balance(&KeccakRoundAir, main, challenges, &mut net), ChipletAir::BytePairLut => fold_balance(&BytePairLutAir, main, challenges, &mut net), - ChipletAir::KeccakSponge => fold_balance(&KeccakSpongeAir, main, challenges, &mut net), ChipletAir::TranscriptEval => { fold_balance(&TranscriptEvalAir, main, challenges, &mut net) }, ChipletAir::UintStoreMul => fold_balance(&UintStoreMulAir, main, challenges, &mut net), ChipletAir::UintAdd => fold_balance(&UintAddAir, main, challenges, &mut net), - ChipletAir::EcGroups => fold_balance(&EcGroupsAir, main, challenges, &mut net), - ChipletAir::EcPointStore => fold_balance(&EcPointStoreAir, main, challenges, &mut net), + ChipletAir::EcPointStoreGroups => { + fold_balance(&EcPointStoreGroupsAir, main, challenges, &mut net) + }, ChipletAir::EcGroupAdd => fold_balance(&EcGroupAddAir, main, challenges, &mut net), ChipletAir::EcMsm => fold_balance(&EcMsmAir, main, challenges, &mut net), } diff --git a/crates/precompiles-prover/src/tests/deferred_state.rs b/crates/precompiles-prover/src/tests/deferred_state.rs index 33ffa2a6aa..48c9d382b5 100644 --- a/crates/precompiles-prover/src/tests/deferred_state.rs +++ b/crates/precompiles-prover/src/tests/deferred_state.rs @@ -1,24 +1,32 @@ use std::{format, string::String, sync::Arc, vec, vec::Vec}; use k256::{ProjectivePoint, elliptic_curve::sec1::ToSec1Point}; +use miden_air::lookup::Challenges; use miden_core::{ Felt, deferred::{ DeferredState, DeferredStateWire, Digest, Node as VmNode, PrecompileRegistry, TRUE_DIGEST as VM_TRUE_DIGEST, TRUE_INDEX, Tag, WireEntry, }, + field::QuadFelt, proof::{DeferredProof, HashFunction, StarkProof}, }; use miden_precompiles::{ CurveId, CurvePrecompile, Keccak256Precompile, UintDomain, UintPrecompile, }; +use rand::{Rng, RngExt, SeedableRng, rngs::StdRng}; use crate::{ deferred::{DeferredSession, session_from_deferred_state}, - hash::keccak::sponge::trace::keccak_oracle, + hash::{ + chunk_node_sponge::SPONGE_COL_OFFSET, + keccak::sponge::{COL_ACT as SPONGE_COL_ACT, SPONGE_PERIOD, trace::keccak_oracle}, + }, math::{U256, from_hex, to_limbs32}, prove_deferred_state, + relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS}, session::{Session, SessionTraces, VerifyError, verify_deferred}, + tests::bus_balance::session_stack_residual, transcript::poseidon2::P2Digest, }; @@ -478,3 +486,65 @@ fn prove_deferred_state_round_trips_for_every_hash_function() { } } } + +/// Reconstruct the full ten-chiplet LogUp balance, including verifier-side fixed-boundary +/// consumes. This checks the generated traces against each AIR's lookup evaluator; +/// `eval_external` is tested separately in `session::prove`. +fn assert_session_balanced(traces: &SessionTraces, rng: &mut impl Rng) { + let challenges = Challenges::new( + QuadFelt::new([Felt::new(rng.random()).unwrap(), Felt::new(rng.random()).unwrap()]), + QuadFelt::new([Felt::new(rng.random()).unwrap(), Felt::new(rng.random()).unwrap()]), + MAX_MESSAGE_WIDTH, + NUM_BUS_IDS, + ); + let mains = traces.mains(); + let residual = session_stack_residual(&mains, &[], &challenges); + assert!( + residual.is_empty(), + "session stack imbalance: {} unmatched denom(s); e.g. net {:?} on {}", + residual.len(), + residual.first().map(|(m, _)| *m), + residual.first().map(|(_, s)| s.as_str()).unwrap_or(""), + ); +} + +/// Exercises the merged sponge band on multi-block (`> 136`-byte) messages. +/// The default full-proof fixtures use the single-block input `b"abc"`; this +/// test covers cross-block state, invocation seams, overshoot lanes, padding, +/// and final squeezing through both constraint and bus-balance checks. +#[test] +fn merged_chunk_node_sponge_multi_block_checks_and_balances() { + let mut rng = StdRng::seed_from_u64(0xc0de_5b09); + // 137: first byte past the rate boundary (2 blocks, pad in block 2). + // 271: rate boundary − 1 across two blocks. 300, 407: overshoot variety. + for len in [137usize, 271, 300, 407] { + let input: Vec = (0..len).map(|i| i as u8).collect(); + let traces = keccak_session_traces(&input); + // Inspect the production merged band rather than inferring activity from the input. This + // fails if trace construction silently truncates the sponge invocation. + let merged = traces.mains()[0]; + let active_sponge_rows = merged + .values + .chunks_exact(merged.width) + .filter(|row| row[SPONGE_COL_OFFSET + SPONGE_COL_ACT] == Felt::ONE) + .count(); + assert!( + active_sponge_rows > SPONGE_PERIOD, + "case len={len} must activate more than one sponge block, got {active_sponge_rows} rows" + ); + traces.check(); + assert_session_balanced(&traces, &mut rng); + } +} + +/// Explicit full prove+verify of a multi-block Keccak session — the +/// end-to-end counterpart to the fast check/balance guard above, closing +/// the merged-AIR multi-block gap through the real STARK path. +#[test] +#[ignore = "full prove/verify round-trip; run explicitly"] +fn prove_deferred_state_round_trips_for_multi_block_keccak() { + let synthetic = synthetic_keccak_state(&(0u8..200).collect::>()); + let proof = prove_deferred_state(&synthetic.state, HashFunction::Blake3_256) + .expect("multi-block keccak session should prove"); + verify_deferred(&proof).expect("multi-block keccak session should verify"); +} diff --git a/crates/precompiles-prover/src/tests/ec_add.rs b/crates/precompiles-prover/src/tests/ec_add.rs index d1346049e8..773a58f65f 100644 --- a/crates/precompiles-prover/src/tests/ec_add.rs +++ b/crates/precompiles-prover/src/tests/ec_add.rs @@ -28,14 +28,17 @@ use rand::{Rng, RngExt, SeedableRng, rngs::StdRng}; use crate::logup::{NUM_PUBLIC_VALUES, sigma_sum}; use crate::{ ec::{ - COL_IS_CERT, EcPointStoreAir, EcRequire, NUM_MAIN_COLS as POINT_COLS, + COL_IS_CERT, EcRequire, add::{ CELL_R, COL_CANCEL, COL_DBL, COL_GEN, COL_MINTS, COL_PAI_P, COL_PAI_Q, EcGroupAddAir, NUM_MAIN_COLS as ADD_COLS, PERIOD, ROW_RES, trace::{EcAddRequires, generate_trace as ec_add_trace}, }, - groups::EcGroupsAir, - trace::{EcGroupPtr, EcPointPtr, EcStoreRequires, generate_traces as ec_store_traces}, + point_store_groups::{ + EcPointStoreGroupsAir, NUM_MAIN_COLS as POINTS_GROUPS_COLS, + trace::generate_trace as ec_points_groups_trace, + }, + trace::{EcGroupPtr, EcPointPtr, EcStoreRequires}, }, math::{U256, from_hex}, primitives::byte_pair_lut::{BytePairLutAir, BytePairLutRequires, generate_trace as bpl_trace}, @@ -77,14 +80,14 @@ const FP: u32 = 1000; /// The arithmetic + EC chiplet subset, in canonical /// [`SessionTraces::mains`](crate::session::SessionTraces::mains) order: -/// byte-pair LUT, uint store+mul, uint add, EC groups, EC points, EC add. -const NUM_STACK: usize = 6; +/// byte-pair LUT, uint store+mul, uint add, EC points+groups, EC add. +const NUM_STACK: usize = 5; /// Bare-requires stack over the arithmetic + EC chiplet subset — the EC /// layer's analogue of the Session sweep, driven through [`EcRequire`]. /// The subset is bus-closed: `Range16` nets against the LUT, `UintVal` / /// `UintLimbs` / `UintAdd` / `UintMul` / `EcGroup` / `EcPoint` all net -/// within the six chiplets. +/// within the five chiplets. struct EcStack { store: UintStoreRequires, /// The pinned modulus's handle. @@ -133,18 +136,18 @@ impl EcStack { /// accumulator and routes the demand its rows consume, so relations /// run before the store / EC stores read their provide ledgers and /// every `Range16` consumer fires before the LUT — and bundle the - /// six main traces in [`NUM_STACK`] order. + /// five main traces in [`NUM_STACK`] order. fn traces(mut self) -> EcStackTraces { let mut bpl = BytePairLutRequires::new(); let add = uint_add_trace(self.adds, &mut self.store); let ec_add = ec_add_trace(self.ec_add, &mut self.ec, &mut bpl); let uint = uint_store_mul_trace(self.store, self.muls, &mut bpl); - let (ec_groups, ec_points) = ec_store_traces(self.ec); - EcStackTraces([bpl_trace(bpl), uint, add, ec_groups, ec_points, ec_add]) + let ec_points_groups = ec_points_groups_trace(self.ec); + EcStackTraces([bpl_trace(bpl), uint, add, ec_points_groups, ec_add]) } } -/// The six subset main traces, with the per-chiplet check / balance / +/// The five subset main traces, with the per-chiplet check / balance / /// prove harness over them. struct EcStackTraces([RowMajorMatrix; NUM_STACK]); @@ -154,13 +157,12 @@ fn stack_airs() -> [ChipletAir; NUM_STACK] { ChipletAir::BytePairLut, ChipletAir::UintStoreMul, ChipletAir::UintAdd, - ChipletAir::EcGroups, - ChipletAir::EcPointStore, + ChipletAir::EcPointStoreGroups, ChipletAir::EcGroupAdd, ] } -/// The subset as a [`MultiAir`] over the seven [`stack_airs`] in +/// The subset as a [`MultiAir`] over the five [`stack_airs`] in /// [`NUM_STACK`] order, closing the same cross-AIR `Σ σ = 0` bus identity /// as the full [`ChipletMultiAir`](crate::session::ChipletMultiAir) — the /// subset is bus-closed, so the residue sum vanishes. Drives the @@ -198,18 +200,20 @@ impl MultiAir for EcStackMultiAir { impl EcStackTraces { fn mains(&self) -> [&RowMajorMatrix; NUM_STACK] { - let [a, b, c, d, e, f] = &self.0; - [a, b, c, d, e, f] + let [a, b, c, d, e] = &self.0; + [a, b, c, d, e] } /// The EcGroupAdd main — the tamper tests' target. fn ec_add_main(&self) -> &RowMajorMatrix { - &self.0[5] + &self.0[4] } - /// The EcPointStore main — the closure-cert necessity tests' target. + /// The EcPointStoreGroups main — the closure-cert necessity tests' + /// target (the point-store band sits at columns 0..14, so the point + /// tamper helpers only need the merged row stride). fn ec_points_main(&self) -> &RowMajorMatrix { - &self.0[4] + &self.0[3] } /// Per-chiplet local-constraint check (one AIR at a time, no @@ -230,7 +234,7 @@ impl EcStackTraces { } /// The subset's [`ProverStatement`]: the [`EcStackMultiAir`] + the dummy - /// shared `air_inputs` + the seven main traces in [`NUM_STACK`] order. + /// shared `air_inputs` + the five main traces in [`NUM_STACK`] order. fn prover_statement(&self) -> ProverStatement { let statement = Statement::new(EcStackMultiAir::new(), Self::dummy_air_inputs(), Vec::new()) @@ -277,9 +281,8 @@ fn stack_residual(mains: &[&RowMajorMatrix; NUM_STACK], rng: &mut impl Rng fold_balance(&BytePairLutAir, mains[0], &challenges, &mut net); fold_balance(&UintStoreMulAir, mains[1], &challenges, &mut net); fold_balance(&UintAddAir, mains[2], &challenges, &mut net); - fold_balance(&EcGroupsAir, mains[3], &challenges, &mut net); - fold_balance(&EcPointStoreAir, mains[4], &challenges, &mut net); - fold_balance(&EcGroupAddAir, mains[5], &challenges, &mut net); + fold_balance(&EcPointStoreGroupsAir, mains[3], &challenges, &mut net); + fold_balance(&EcGroupAddAir, mains[4], &challenges, &mut net); net.into_values().filter(|(m, _)| *m != Felt::ZERO).count() } @@ -328,7 +331,8 @@ fn tamper_cell(m: &mut RowMajorMatrix, row: usize, cell: usize, v: u32) { m.values[row * ADD_COLS + cell] = Felt::from(v); } -/// Clone the EcPointStore main and rewrite one column of a given row. +/// Clone the EcPointStoreGroups main and rewrite one point-store-band +/// column of a given row. fn tamper_ec_points( main: &RowMajorMatrix, row: usize, @@ -336,7 +340,7 @@ fn tamper_ec_points( v: u32, ) -> RowMajorMatrix { let mut m = main.clone(); - m.values[row * POINT_COLS + col] = Felt::from(v); + m.values[row * POINTS_GROUPS_COLS + col] = Felt::from(v); m } @@ -653,7 +657,7 @@ fn double_forged_as_generic_unbalances() { check_ec_add(&forged); let mut mains = traces.mains(); - mains[5] = &forged; + mains[4] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } @@ -711,7 +715,7 @@ fn finite_forged_as_pai_unbalances() { check_ec_add(&forged); let mut mains = traces.mains(); - mains[5] = &forged; + mains[4] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } @@ -734,7 +738,7 @@ fn double_forged_as_cancel_unbalances() { check_ec_add(&forged); let mut mains = traces.mains(); - mains[5] = &forged; + mains[4] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } @@ -767,7 +771,7 @@ fn ed25519_torsion_forged_as_double_unbalances() { check_ec_add(&forged); let mut mains = traces.mains(); - mains[5] = &forged; + mains[4] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } @@ -790,7 +794,7 @@ fn forged_result_ptr_unbalances() { check_ec_add(&forged); let mut mains = traces.mains(); - mains[5] = &forged; + mains[4] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } @@ -853,9 +857,9 @@ fn cert_point_forged_as_trio_unbalances() { let traces = k1.stack.traces(); let forged = tamper_ec_points(traces.ec_points_main(), r.addr() as usize - 1, COL_IS_CERT, 0); - crate::tests::check_local(EcPointStoreAir, &forged); + crate::tests::check_local(EcPointStoreGroupsAir, &forged); let mut rng = StdRng::seed_from_u64(0xecad_dce3); let mut mains = traces.mains(); - mains[4] = &forged; + mains[3] = &forged; assert_ne!(stack_residual(&mains, &mut rng), 0); } diff --git a/crates/precompiles-prover/src/tests/ec_dag.rs b/crates/precompiles-prover/src/tests/ec_dag.rs index e362d3f50b..530cc1f416 100644 --- a/crates/precompiles-prover/src/tests/ec_dag.rs +++ b/crates/precompiles-prover/src/tests/ec_dag.rs @@ -239,7 +239,7 @@ fn ec_dag_double_proves() { // the cross-chiplet bus: a mismatched or dangling provide. // ============================================================================ -/// Net unmatched LogUp denominators across the full thirteen-chiplet +/// Net unmatched LogUp denominators across the full ten-chiplet /// stack (0 ⟺ every bus closes), with the `eval` main replaced by /// `eval_main`. fn dag_residual( @@ -247,7 +247,7 @@ fn dag_residual( eval_main: &RowMajorMatrix, rng: &mut impl Rng, ) -> usize { - dag_residual_with(traces, eval_main, traces.mains()[10], rng) + dag_residual_with(traces, eval_main, traces.mains()[8], rng) } /// [`dag_residual`] with the `EcGroupAdd` (ec_add) main also overridden — @@ -261,7 +261,7 @@ fn dag_residual_with( ) -> usize { let mains = traces.mains(); let challenges = Challenges::new(rand_qf(rng), rand_qf(rng), MAX_MESSAGE_WIDTH, NUM_BUS_IDS); - session_stack_residual(&mains, &[(5, eval_main), (10, add_main)], &challenges).len() + session_stack_residual(&mains, &[(4, eval_main), (8, add_main)], &challenges).len() } /// First row whose `col` flag is 1 (width taken from the matrix, so this @@ -316,7 +316,7 @@ fn dag_pai_payload_must_be_true_true() { // A PAI VALUE node has no coordinate children. Its canonical payload is // `(TRUE_DIGEST, TRUE_DIGEST)`, i.e. zero digest in both rate halves. let traces = ec_dag_pai_traces(); - let eval = traces.mains()[5]; + let eval = traces.mains()[4]; let row = first_row_with_flag(eval, COL_IS_EC_PAI); let forged = tamper(eval, row, &[(COL_LHS_BEGIN, Felt::ONE)]); @@ -332,7 +332,7 @@ fn dag_finite_forged_as_pai_unbalances() { // consumer, and the coord children / Poseidon2 messages dangle. let traces = ec_dag_3g_traces(); let mut rng = StdRng::seed_from_u64(0xec_da9_f01); - let eval = traces.mains()[5]; + let eval = traces.mains()[4]; assert_eq!(dag_residual(&traces, eval, &mut rng), 0, "honest stack must balance"); let row = first_row_with_flag(eval, COL_IS_EC_CREATE); @@ -365,7 +365,7 @@ fn dag_sub_result_forged_unbalances() { // `ec_is` consumer — the rearrangement is load-bearing, not decorative. let traces = ec_dag_sub_from_pai_traces(); let mut rng = StdRng::seed_from_u64(0xec_da9_f03); - let eval = traces.mains()[5]; + let eval = traces.mains()[4]; assert_eq!(dag_residual(&traces, eval, &mut rng), 0, "honest stack must balance"); let row = first_ec_op_row(eval, COL_IS_SUB); diff --git a/crates/precompiles-prover/src/tests/ec_msm.rs b/crates/precompiles-prover/src/tests/ec_msm.rs index c56507bd3e..7f5dea4878 100644 --- a/crates/precompiles-prover/src/tests/ec_msm.rs +++ b/crates/precompiles-prover/src/tests/ec_msm.rs @@ -559,7 +559,7 @@ fn msm_resolve_duplicate_base_rejected() { #[should_panic(expected = "constraint not satisfied")] fn msm_resolve_run_expr_must_be_constant() { let traces = msm_resolve_two_term_traces(); - let eval = traces.mains()[5]; // the transcript-eval main + let eval = traces.mains()[4]; // the transcript-eval main let ncols = eval.width(); // The first absorb row of a 2-term run is non-boundary. diff --git a/crates/precompiles-prover/src/tests/mod.rs b/crates/precompiles-prover/src/tests/mod.rs index e0aa53391d..f3771d8ad3 100644 --- a/crates/precompiles-prover/src/tests/mod.rs +++ b/crates/precompiles-prover/src/tests/mod.rs @@ -90,17 +90,12 @@ where check_local_inputs(air, main, vec![Felt::ZERO; n]); } -/// The per-AIR `log_quotient_degree` — replicated from the public -/// `constraint_degree()` because 0.26 keeps the framework's own -/// `domain::log_quotient_degree` `pub(crate)`. This is the value the lqd -/// design-target smoke tests assert (= `⌈log₂ max(d−1, 1)⌉`, `d` the max -/// base/ext constraint-degree multiple). +/// The per-AIR quotient degree used by the design-target smoke tests. pub(crate) fn log_quotient_degree(air: &A) -> u8 where A: LiftedAir, { - let d = air.constraint_degree().max(); - miden_lifted_air::log2_ceil_u8(d.saturating_sub(1).max(1)) + miden_lifted_stark::log_quotient_degree::(air) } /// The `[preprocessed ++ main]` matrix the lookup eval reads for a chiplet diff --git a/crates/precompiles-prover/src/tests/uint_dag.rs b/crates/precompiles-prover/src/tests/uint_dag.rs index f5d078a13f..09e0e68def 100644 --- a/crates/precompiles-prover/src/tests/uint_dag.rs +++ b/crates/precompiles-prover/src/tests/uint_dag.rs @@ -111,10 +111,10 @@ fn horner_sign_alternation_full_stack() { // 22 eval rows (AND + zero + 6 leaves + 13 value ops + Is) pad to 32; fixed // uints live only in the store and verifier boundary correction, not eval rows. // The add relation count is unchanged; mul no longer has its own main - // (shares the store's merged trace at index 7). + // (shares the store's merged trace at index 5). let mains = traces.mains(); - assert_eq!(mains[5].height(), 32, "eval: 22 rows pad to 32"); - assert_eq!(mains[7].height(), 16, "uint-add: 7 two-row blocks pad to 8"); + assert_eq!(mains[4].height(), 32, "eval: 22 rows pad to 32"); + assert_eq!(mains[6].height(), 16, "uint-add: 7 two-row blocks pad to 8"); traces.check(); assert_balanced(&traces, &mut rng); @@ -153,9 +153,9 @@ fn op_dedup_collapses_repeated_nodes() { // Two recorded add ops (r once, w once), not three. let mains = traces.mains(); - assert_eq!(mains[7].height(), 4, "uint-add: exactly two two-row blocks"); + assert_eq!(mains[6].height(), 4, "uint-add: exactly two two-row blocks"); // r's single row carries out_mult 2 (consumed twice by w). - let eval = mains[5]; + let eval = mains[4]; let r_row = (0..eval.height()) .find(|row| { eval.values[row * EVAL_NUM_MAIN_COLS + COL_IS_ADD] == Felt::ONE @@ -261,12 +261,12 @@ fn forged_result_ptr_unbalances() { let mut rng = StdRng::seed_from_u64(0xf043_0001); let traces = mul_statement(&mut rng); - let mut tampered = traces.mains()[5].clone(); + let mut tampered = traces.mains()[4].clone(); let row = find_op_row(&tampered, COL_IS_MUL); tampered.values[row * EVAL_NUM_MAIN_COLS + COL_PTR] += Felt::ONE; let mut mains = traces.mains(); - mains[5] = &tampered; + mains[4] = &tampered; let [alpha, beta] = random_challenges(&mut rng); let challenges = Challenges::new(alpha, beta, MAX_MESSAGE_WIDTH, NUM_BUS_IDS); let residual = session_stack_residual(&mains, &[], &challenges); @@ -296,7 +296,7 @@ fn reencoded_op_id_passes_constraints_but_unbalances() { let root = session.assert_and_fold([claim]); let traces = session.finish(root); - let mut tampered = traces.mains()[5].clone(); + let mut tampered = traces.mains()[4].clone(); let row = find_op_row(&tampered, COL_IS_ADD); tampered.values[row * EVAL_NUM_MAIN_COLS + COL_IS_ADD] = Felt::ZERO; tampered.values[row * EVAL_NUM_MAIN_COLS + COL_IS_SUB] = Felt::ONE; @@ -308,7 +308,7 @@ fn reencoded_op_id_passes_constraints_but_unbalances() { // …but the bus refuses the re-encoded cap + re-wired relation. let mut mains = traces.mains(); - mains[5] = &tampered; + mains[4] = &tampered; let [alpha, beta] = random_challenges(&mut rng); let challenges = Challenges::new(alpha, beta, MAX_MESSAGE_WIDTH, NUM_BUS_IDS); let residual = session_stack_residual(&mains, &[], &challenges); diff --git a/crates/precompiles-prover/src/tests/vm_uint.rs b/crates/precompiles-prover/src/tests/vm_uint.rs index 284b00600f..78e6ebbb23 100644 --- a/crates/precompiles-prover/src/tests/vm_uint.rs +++ b/crates/precompiles-prover/src/tests/vm_uint.rs @@ -73,7 +73,7 @@ fn pin_claim_rows_commit_pin_ptr_but_vm_uint_rows_commit_bound_ptr() { let root = session.assert_and(root1, eq); let traces = session.finish(root); - let eval = traces.mains()[5]; + let eval = traces.mains()[4]; let row_value = |row: usize, col: usize| eval.values[row * EVAL_NUM_MAIN_COLS + col]; let pin_row = (0..eval.height()) diff --git a/crates/precompiles-prover/src/transcript/eval/mod.rs b/crates/precompiles-prover/src/transcript/eval/mod.rs index 27210020aa..5a3c826386 100644 --- a/crates/precompiles-prover/src/transcript/eval/mod.rs +++ b/crates/precompiles-prover/src/transcript/eval/mod.rs @@ -3,13 +3,13 @@ //! //! The narrow, central hasher + binder for the transcript DAG. Each //! active row evaluates one node: it hashes the node's preimage on -//! Poseidon2 and settles the node's `Binding`-bus tuple. The eval chip is -//! the sole provider of the `Binding` bus, except `KeccakNodeAir`, which -//! fuses its own terminal keccak `True` (there is no transient Keccak — -//! see the design notes). Domain chiplets (the `UintStore`, -//! `UintAdd` / `UintMul`, EC store/add/MSM chiplets) stay ptr-only and never -//! touch `Binding`; this chip hashes their DAG nodes and ptr-references their -//! relations. +//! Poseidon2 and settles the node's `Binding`-bus tuple. The eval chip +//! is the sole provider of the `Binding` bus, except the Keccak-node +//! band of `ChunkNodeSpongeAir`, which fuses its own terminal keccak +//! `True` (there is no transient Keccak — see the design notes). Domain +//! chiplets (the `UintStore`, `UintAdd` / `UintMul`, EC store/add/MSM +//! chiplets) stay ptr-only and never touch `Binding`; this chip hashes +//! their DAG nodes and ptr-references their relations. //! //! Node kinds are dispatched by a uniform one-hot `is_and + is_zero + //! is_uint_leaf + Σ op-flags = act`: the **Transcript AND-combinator** @@ -134,11 +134,10 @@ pub const COL_H_END: usize = COL_H_BEGIN + DIGEST_WIDTH; /// consumes — the row provides `Binding(0, True)` only. Boolean. pub const COL_IS_ZERO: usize = COL_H_END; /// Provide multiplicity for this node's `Binding(h, True)` = number of -/// parents that consume it (DAG sharing / dedup, mirroring -/// `KeccakNodeAir`'s `out_mult`). A plain count pinned to the -/// consumer count by `Binding` bus balance — not range-checked (see -/// the design notes); `0` on the root (no parent) and on -/// inactive rows. +/// parents that consume it (DAG sharing / dedup, mirroring the +/// Keccak-node band's `out_mult`). A plain count pinned to the consumer +/// count by `Binding` bus balance — not range-checked (see the design +/// notes); `0` on the root (no parent) and on inactive rows. pub const COL_OUT_MULT: usize = COL_IS_ZERO + 1; // ================================================================ From ca29898710fa68f4a52b7854cba17edb77643d17 Mon Sep 17 00:00:00 2001 From: Robin Salen <30937548+Nashtare@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:52:02 +0400 Subject: [PATCH 05/17] fix(mast-package): dedupe dependencies in PackageManifest Arbitrary impl (#3539) --- crates/mast-package/src/package/manifest.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/mast-package/src/package/manifest.rs b/crates/mast-package/src/package/manifest.rs index d95910feff..3e6e4de3d9 100644 --- a/crates/mast-package/src/package/manifest.rs +++ b/crates/mast-package/src/package/manifest.rs @@ -55,6 +55,10 @@ pub struct PackageManifest { pub(super) modules: BTreeMap, PackageModule>, /// The libraries (packages) linked against by this package, which must be provided when /// executing the program. + #[cfg_attr( + any(test, feature = "arbitrary"), + proptest(strategy = "arbitrary_dependencies()") + )] pub(super) dependencies: Vec, /// The (optional) entrypoint function for this package, if it is executable #[cfg_attr(any(test, feature = "arbitrary"), proptest(value = "None"))] @@ -673,6 +677,19 @@ impl fmt::Debug for TypeExport { } } +#[cfg(any(test, feature = "arbitrary"))] +fn arbitrary_dependencies() -> impl Strategy> { + proptest::collection::vec(any::(), 0..10).prop_filter( + "package dependencies must have unique ids", + |dependencies| { + use alloc::collections::BTreeSet; + + let mut seen = BTreeSet::new(); + dependencies.iter().all(|dependency| seen.insert(dependency.id().clone())) + }, + ) +} + fn normalize_export(export: &mut PackageExport) -> Result<(), ManifestValidationError> { let canonical_path = canonicalize_export_path(export.path().as_ref())?; From 5a32077c4bc0ebbca2b795653ffb172cea833c4a Mon Sep 17 00:00:00 2001 From: Moritz Zielke Date: Sun, 9 Aug 2026 03:50:44 +0800 Subject: [PATCH 06/17] feat(assembly): add `trace` for generating read-only events (#3478) --- CHANGELOG.md | 3 + core/src/events/sys_events.rs | 15 +- core/src/operations/mod.rs | 3 +- .../src/ast/instruction/mod.rs | 4 + .../src/ast/instruction/print.rs | 12 ++ crates/assembly-syntax/src/ast/tests.rs | 60 ++++++++ crates/assembly-syntax/src/ast/visit.rs | 8 +- .../src/parser/cst/instructions.rs | 31 ++-- crates/assembly-syntax/src/parser/tests.rs | 3 + .../src/sema/passes/const_eval.rs | 12 +- crates/assembly/src/instruction/mod.rs | 23 +++ crates/assembly/src/tests.rs | 138 +++++++++++++++++- docs/src/user_docs/assembly/events.md | 27 ++-- .../assembly/instruction_reference.md | 2 + .../operations/decorators/trace_events.rs | 118 ++++++++++----- .../tests/integration/operations/sys_ops.rs | 16 +- processor/src/host/handlers.rs | 14 +- processor/src/host/mod.rs | 4 +- .../src/test_utils/non_tracing_test_host.rs | 7 +- processor/src/test_utils/test_host.rs | 11 +- processor/src/tests/mod.rs | 18 +-- processor/tests/async_compat.rs | 11 +- 22 files changed, 417 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc70e8a7af..387d708dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## v0.30.0 (Unreleased) +#### Features + +- Added `trace`, `trace.CONST`, and `trace.event("...")` assembly as syntactic sugar for emitting optional read-only trace events ([#3478](https://github.com/0xMiden/miden-vm/pull/3478)). #### Changes - [BREAKING] Reduced the precompile STARK relation from 12 AIRs to 10 by merging the chunk/node/sponge and EC point/group stores ([#3464](https://github.com/0xMiden/miden-vm/pull/3464)). diff --git a/core/src/events/sys_events.rs b/core/src/events/sys_events.rs index 1d523a06f4..2f5d1f7466 100644 --- a/core/src/events/sys_events.rs +++ b/core/src/events/sys_events.rs @@ -413,13 +413,14 @@ pub enum SystemEvent { // -------------------------------------------------------------------------------------------- /// Signals an optional, read-only trace event to the host. /// - /// When `emit` observes this system event ID at stack position 0, the VM forwards the user - /// trace event ID at stack position 1 to the host's trace handler. This is typically emitted - /// as `push. push. emit`. Trace handlers can observe - /// the processor state, but cannot mutate VM state or the advice provider. If no handler is - /// registered for the user trace event ID, the event is a no-op. - /// - /// Hosts are expected to not raise an error if they encounter a `user_trace_id` for which no + /// Assembly programs emit trace events with `trace`, `trace.CONST`, or `trace.event("...")`. + /// When the underlying `emit` observes this system event ID at stack position 0, the VM + /// forwards the user trace event ID at stack position 1 to the host's trace handler. The + /// immediate form lowers to `push. push. emit drop drop`. + /// Trace handlers can observe the processor state, but cannot mutate VM state or the advice + /// provider. If no handler is registered for the user trace event ID, the event is a no-op. + /// + /// Hosts are expected not to raise an error if they encounter a `user_trace_id` for which no /// trace handler is registered. /// /// Inputs: diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index f3df6736d2..36afb0f112 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -174,7 +174,8 @@ pub enum Operation { /// assembly (`emit.event("...")` or `emit.CONST` where `CONST=event("...")`). /// - System events are identified by reserved [`SystemEvent`](crate::events::SystemEvent) IDs. /// Most are handled by the VM; `SystemEvent::TraceEvent` triggers the host's optional - /// read-only trace handler for the trace event id at stack position 1. + /// read-only trace handler for the trace event id at stack position 1. Assembly exposes this + /// through `trace`, `trace.CONST`, and `trace.event("...")`. /// - Any non system event ID is forwarded to the host's regular event handler. /// /// This operation does not change the state of the user stack aside from reading the value. diff --git a/crates/assembly-syntax/src/ast/instruction/mod.rs b/crates/assembly-syntax/src/ast/instruction/mod.rs index 604af74f63..d1beb07ba6 100644 --- a/crates/assembly-syntax/src/ast/instruction/mod.rs +++ b/crates/assembly-syntax/src/ast/instruction/mod.rs @@ -287,6 +287,10 @@ pub enum Instruction { // ----- event decorators -------------------------------------------------------------------- Emit, EmitImm(ImmFelt), + + // ----- traces (read-only events) ----------------------------------------------------------- + Trace, + TraceImm(ImmFelt), } impl Instruction { diff --git a/crates/assembly-syntax/src/ast/instruction/print.rs b/crates/assembly-syntax/src/ast/instruction/print.rs index 394252eec2..00c6b8073c 100644 --- a/crates/assembly-syntax/src/ast/instruction/print.rs +++ b/crates/assembly-syntax/src/ast/instruction/print.rs @@ -337,6 +337,18 @@ impl PrettyPrint for Instruction { Self::Emit => const_text("emit"), Self::EmitImm(value) => inst_with_felt_imm("emit", value), + // ----- traces (read-only events) ---------------------------------------------------- + Self::Trace => const_text("trace"), + // `trace.` is invalid syntax, so to support a `print -> parse` round trip + // we print the equivalent `push. trace drop` sequence instead. + Self::TraceImm(value) => { + let value = match value { + Immediate::Value(value) => display(*value), + Immediate::Constant(name) => text(name), + }; + flatten(const_text("push") + const_text(".") + value + const_text(" trace drop")) + }, + // Handled by the early return for !has_textual_representation() Self::DebugVar(_) => unreachable!(), } diff --git a/crates/assembly-syntax/src/ast/tests.rs b/crates/assembly-syntax/src/ast/tests.rs index 68cfc78b2f..ba2e3ae6b2 100644 --- a/crates/assembly-syntax/src/ast/tests.rs +++ b/crates/assembly-syntax/src/ast/tests.rs @@ -1,5 +1,6 @@ use alloc::{string::ToString, vec::Vec}; +use miden_core::events::EventId; use miden_debug_types::{SourceSpan, Span}; use miden_utils_diagnostics::Report; use pretty_assertions::assert_eq; @@ -1622,6 +1623,65 @@ end assert_eq!(&formatted, expected); } +/// `TraceImm` is printed as the equivalent `push. trace drop` sequence, since `trace.` +/// is not valid syntax. +#[test] +fn test_trace_roundtrip_formatting() { + let trace_name = "test::trace::roundtrip"; + let trace_id = EventId::from_name(trace_name).as_felt(); + + let source = format!( + "\ +begin + push.1 + trace + drop + trace.event(\"{trace_name}\") +end +" + ); + + let context = SyntaxTestContext::default(); + let source = source_file!(&context, source); + let module = context.parse_program_source_file(source).unwrap_or_else(|err| panic!("{err}")); + let formatted = module.to_string(); + let expected = format!( + "\ +namespace $exec + +begin + push.1 + trace + drop + push.{trace_id} trace drop +end +" + ); + assert_eq!(&formatted, &expected); + + // The printed output must parse back. + let source = source_file!(&context, &expected); + let reparsed = context.parse_program_source_file(source).unwrap_or_else(|err| panic!("{err}")); + let expanded = format!( + "\ +namespace $exec + +begin + push.1 + trace + drop + push.{trace_id} + trace + drop +end +" + ); + let source = source_file!(&context, &expanded); + let expanded_module = + context.parse_program_source_file(source).unwrap_or_else(|err| panic!("{err}")); + assert_eq!(reparsed, expanded_module); +} + #[test] fn cannot_mem_store_word() { let context = SyntaxTestContext::default(); diff --git a/crates/assembly-syntax/src/ast/visit.rs b/crates/assembly-syntax/src/ast/visit.rs index 8eab36a781..ee80ff943e 100644 --- a/crates/assembly-syntax/src/ast/visit.rs +++ b/crates/assembly-syntax/src/ast/visit.rs @@ -476,7 +476,7 @@ where | MemStoreImm(imm) | MemStoreWBeImm(imm) | MemStoreWLeImm(imm) => visitor.visit_immediate_u32(imm), - EmitImm(imm) => visitor.visit_immediate_felt(imm), + EmitImm(imm) | TraceImm(imm) => visitor.visit_immediate_felt(imm), SysEvent(sys_event) => visitor.visit_system_event(Span::new(span, sys_event)), Exec(target) => visitor.visit_exec(target), Call(target) => visitor.visit_call(target), @@ -505,7 +505,7 @@ where | MemStoreWLe | MemStream | AdvPipe | AdvPush | AdvPushW | AdvLoadW | Hash | HMerge | HPerm | MTreeGet | MTreeSet | MTreeMerge | MTreeVerify | FriExt2Fold4 | DynExec | DynCall | DebugVar(_) | HornerBase | HornerExt | CryptoStream | EvalCircuit - | LogDeferred | Emit => ControlFlow::Continue(()), + | LogDeferred | Emit | Trace => ControlFlow::Continue(()), } } @@ -1040,7 +1040,7 @@ where | MemStoreImm(imm) | MemStoreWBeImm(imm) | MemStoreWLeImm(imm) => visitor.visit_mut_immediate_u32(imm), - EmitImm(imm) => visitor.visit_mut_immediate_felt(imm), + EmitImm(imm) | TraceImm(imm) => visitor.visit_mut_immediate_felt(imm), SysEvent(sys_event) => visitor.visit_mut_system_event(Span::new(span, sys_event)), Exec(target) => visitor.visit_mut_exec(target), Call(target) => visitor.visit_mut_call(target), @@ -1069,7 +1069,7 @@ where | MemStoreWLe | MemStream | AdvPipe | AdvPush | AdvPushW | AdvLoadW | Hash | HMerge | HPerm | MTreeGet | MTreeSet | MTreeMerge | MTreeVerify | FriExt2Fold4 | DynExec | DynCall | DebugVar(_) | HornerBase | HornerExt | EvalCircuit | CryptoStream - | LogDeferred | Emit => ControlFlow::Continue(()), + | LogDeferred | Emit | Trace => ControlFlow::Continue(()), } } diff --git a/crates/assembly-syntax/src/parser/cst/instructions.rs b/crates/assembly-syntax/src/parser/cst/instructions.rs index efdd2ffa36..49782ec7b2 100644 --- a/crates/assembly-syntax/src/parser/cst/instructions.rs +++ b/crates/assembly-syntax/src/parser/cst/instructions.rs @@ -377,6 +377,10 @@ static PRIMITIVE_SPECS: &[PrimitiveSpec] = &[ spelling: "emit", build: || Instruction::Emit, }, + PrimitiveSpec { + spelling: "trace", + build: || Instruction::Trace, + }, PrimitiveSpec { spelling: "eval_circuit", build: || Instruction::EvalCircuit, @@ -1207,7 +1211,12 @@ fn lower_extended_instruction( lower_invocation_instruction(context, span, &tokens, build) }, - ExtendedInstructionKind::Emit => lower_emit_instruction(context, span, &tokens), + ExtendedInstructionKind::Emit => { + lower_event_imm_instruction(context, span, &tokens, "emit", Instruction::EmitImm) + }, + ExtendedInstructionKind::Trace => { + lower_event_imm_instruction(context, span, &tokens, "trace", Instruction::TraceImm) + }, ExtendedInstructionKind::ErrorCode(build) => { lower_error_code_instruction(context, span, &tokens, spec.keyword, build) }, @@ -1224,6 +1233,7 @@ enum ExtendedInstructionKind { Push, Invocation(fn(ast::InvocationTarget) -> Instruction), Emit, + Trace, ErrorCode(fn(ast::ErrorMsg) -> Instruction), } @@ -1252,6 +1262,10 @@ static EXTENDED_INSTRUCTION_SPECS: &[ExtendedInstructionSpec] = &[ keyword: "emit", kind: ExtendedInstructionKind::Emit, }, + ExtendedInstructionSpec { + keyword: "trace", + kind: ExtendedInstructionKind::Trace, + }, ExtendedInstructionSpec { keyword: "assert", kind: ExtendedInstructionKind::ErrorCode(Instruction::AssertWithError), @@ -1343,15 +1357,17 @@ fn lower_invocation_instruction( Ok(Some(vec![inst_op(instruction_span, build(target))])) } -/// Lowers `emit.` and `emit.event("name")`. -fn lower_emit_instruction( +/// Lowers `emit.` / `emit.event("name")` and `trace.` / `trace.event("name")`. +fn lower_event_imm_instruction( context: &mut LoweringContext<'_>, instruction_span: SourceSpan, tokens: &[SyntaxToken], + keyword: &str, + builder: fn(ast::ImmFelt) -> Instruction, ) -> Result>, ParsingError> { if tokens.len() < 3 || tokens[0].kind() != SyntaxKind::Ident - || tokens[0].text() != "emit" + || tokens[0].text() != keyword || tokens[1].kind() != SyntaxKind::Dot { return Ok(None); @@ -1360,10 +1376,7 @@ fn lower_emit_instruction( match &tokens[2..] { [name] if name.kind() == SyntaxKind::Ident && name.text() != "event" => { let name = context.lower_constant_ident_token(name)?; - Ok(Some(vec![inst_op( - instruction_span, - Instruction::EmitImm(Immediate::Constant(name)), - )])) + Ok(Some(vec![inst_op(instruction_span, builder(Immediate::Constant(name)))])) }, [event, lparen, string, rparen] if event.kind() == SyntaxKind::Ident @@ -1376,7 +1389,7 @@ fn lower_emit_instruction( let event_id = EventId::from_name(value.as_ref()).as_felt(); Ok(Some(vec![inst_op( instruction_span, - Instruction::EmitImm(Immediate::Value(Span::new(instruction_span, event_id))), + builder(Immediate::Value(Span::new(instruction_span, event_id))), )])) }, _ => Ok(None), diff --git a/crates/assembly-syntax/src/parser/tests.rs b/crates/assembly-syntax/src/parser/tests.rs index 1ea91baa00..6f80e8a635 100644 --- a/crates/assembly-syntax/src/parser/tests.rs +++ b/crates/assembly-syntax/src/parser/tests.rs @@ -640,6 +640,7 @@ begin adv.insert_hdword adv.push_mapvaln emit + trace mem_load u32div add.1 @@ -751,6 +752,8 @@ begin procref.foo::bar emit.EVENT_ID emit.event(\"abc\") + trace.EVENT_ID + trace.event(\"abc\") assert.err=\"oops\" u32assert.err=ERR_CODE end diff --git a/crates/assembly-syntax/src/sema/passes/const_eval.rs b/crates/assembly-syntax/src/sema/passes/const_eval.rs index 746316ed12..db315e5f39 100644 --- a/crates/assembly-syntax/src/sema/passes/const_eval.rs +++ b/crates/assembly-syntax/src/sema/passes/const_eval.rs @@ -143,20 +143,23 @@ where } fn visit_mut_inst(&mut self, inst: &mut Span) -> ControlFlow<()> { use crate::ast::Instruction; - if let Instruction::EmitImm(Immediate::Constant(name)) = &**inst { + if let Instruction::EmitImm(Immediate::Constant(name)) + | Instruction::TraceImm(Immediate::Constant(name)) = &**inst + { let span = name.span(); match self.env.get(name) { Ok(Some( CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Event, _)) | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Event, _)), )) => { - // CHANGE: allow `emit.EVENT` when `EVENT` was defined via + // CHANGE: allow `emit.EVENT` / `trace.EVENT` when `EVENT` was defined via // const.EVENT = event("...") // NOTE: This function only validates the kind; the actual resolution to a Felt // happens below in `visit_mut_immediate_felt` just like other Felt immediates. // Enabled syntax: // const.EVT = event("...") // emit.EVT + // trace.EVT }, Ok(Some(CachedConstantValue::Miss(expr @ ConstantExpr::Var(_)))) => { // A reference to another constant was used, try to evaluate the expression @@ -181,12 +184,15 @@ where } }, Ok(Some(_)) => { - // CHANGE: disallow `emit.CONST` unless CONST is defined via `event("...")`. + // CHANGE: disallow `emit.CONST` / `trace.CONST` unless CONST is defined via + // `event("...")`. // Examples which now error: // const.BAD = 42 // emit.BAD + // trace.BAD // const.W = word("foo") // emit.W + // trace.W self.errors.push( ConstEvalError::InvalidConstant { span, diff --git a/crates/assembly/src/instruction/mod.rs b/crates/assembly/src/instruction/mod.rs index fe062421ff..53409222ec 100644 --- a/crates/assembly/src/instruction/mod.rs +++ b/crates/assembly/src/instruction/mod.rs @@ -6,6 +6,7 @@ use miden_assembly_syntax::{ }; use miden_core::{ Felt, WORD_SIZE, ZERO, + events::SystemEvent, operations::{AssemblyOp, Operation}, }; @@ -601,6 +602,28 @@ impl Assembler { let event_id_value = event_id.expect_value(); block_builder.push_ops([Push(event_id_value), Emit, Drop]); }, + + // trace: reads the trace ID from the top of the stack and expands to + // `push., emit, drop`, leaving the stack unchanged. + Instruction::Trace => { + // The trace ID is already on the stack. In addition we need the system event which + // triggers traces. + let sys_event_id = SystemEvent::TraceEvent.event_id().as_felt(); + block_builder.push_ops([Push(sys_event_id), Emit, Drop]); + }, + // trace.: expands to + // `push., push., emit, drop, drop`, leaving the stack unchanged. + Instruction::TraceImm(trace_id) => { + let trace_id_value = trace_id.expect_value(); + let sys_event_id = SystemEvent::TraceEvent.event_id().as_felt(); + block_builder.push_ops([ + Push(trace_id_value), + Push(sys_event_id), + Emit, + Drop, + Drop, + ]); + }, } Ok(None) diff --git a/crates/assembly/src/tests.rs b/crates/assembly/src/tests.rs index 2a103ab87a..ca9e210e23 100644 --- a/crates/assembly/src/tests.rs +++ b/crates/assembly/src/tests.rs @@ -3855,7 +3855,7 @@ pub proc foo exec.::test::mod1::bar end" Ok(()) } -// EMIT EVENT SYNTAX VALIDATION +// EVENT SYNTAX VALIDATION // ================================================================================================ #[test] @@ -3897,6 +3897,45 @@ fn emit_const_must_be_event_hash() { .expect_err(r#"emit.CONST should require const defined via event("...")"#); } +#[test] +fn trace_u32_immediate_is_rejected() { + let context = TestContext::new(); + let program_source = r#" + begin + trace.32 + end + "#; + context + .assemble(program_source) + .expect_err(r#"trace. should be rejected; only event("...") is allowed"#); +} + +#[test] +fn trace_const_must_be_event_hash() { + let context = TestContext::new(); + // CONST defined as plain number should not be accepted by trace.CONST + let program_source = r#" + const BAD = 100 + begin + trace.BAD + end + "#; + context + .assemble(program_source) + .expect_err(r#"trace.CONST should require const defined via event("...")"#); + + // CONST defined via word("...") should also be rejected by trace.CONST + let program_source = r#" + const BADW = word("foo") + begin + trace.BADW + end + "#; + context + .assemble(program_source) + .expect_err(r#"trace.CONST should require const defined via event("...")"#); +} + #[test] #[should_panic(expected = "expected 3 lines, but got 1")] fn assert_diagnostic_lines_rejects_missing_actual_lines() { @@ -4140,6 +4179,43 @@ fn emit_instruction_digest() { assert_ne!(procedure_digests[1], procedure_digests[2]); } +/// Ensures that the arguments of `trace` do indeed modify the digest of a basic block. +#[test] +fn trace_instruction_digest() { + let context = TestContext::new(); + + let program_source = r#" + const EVT1 = event("miden::test::trace_one") + const EVT2 = event("miden::test::trace_two") + + proc foo + trace.EVT1 + end + + proc bar + trace.EVT2 + end + + begin + # specific impl irrelevant + exec.foo + exec.bar + end + "#; + + let program = context.assemble(program_source).unwrap(); + + let procedure_digests: Vec = program.mast_forest().procedure_digests().collect(); + + // foo, bar and entrypoint + assert_eq!(3, procedure_digests.len()); + + // Ensure that foo, bar and entrypoint all have different digests + assert_ne!(procedure_digests[0], procedure_digests[1]); + assert_ne!(procedure_digests[0], procedure_digests[2]); + assert_ne!(procedure_digests[1], procedure_digests[2]); +} + /// Tests that emitting events with immediate values has the same MAST representation /// regardless of whether using emit.value or push.value emit syntax #[test] @@ -4190,6 +4266,66 @@ fn emit_syntax_equivalence() { assert_eq!(program3.num_procedures(), 1); } +/// Tests that trace events have the same MAST representation regardless of whether the trace ID +/// is provided as a constant, inline event name, stack value, or fully expanded event sequence. +#[test] +fn trace_syntax_equivalence() { + let context = TestContext::new(); + + // First program uses a constant. + let program1_source = r#" + const EVT = event("miden::test::trace_equiv") + begin + trace.EVT + end + "#; + + // Second program uses inline trace.event("..."). + let program2_source = r#" + begin + trace.event("miden::test::trace_equiv") + end + "#; + + // Third program provides the trace ID on the stack. + let program3_source = r#" + const EVT = event("miden::test::trace_equiv") + begin + push.EVT + trace + drop + end + "#; + + // Fourth program uses the fully expanded trace event sequence. + let program4_source = r#" + const EVT = event("miden::test::trace_equiv") + const SYS_TRACE = event("sys::trace_event") + begin + push.EVT + push.SYS_TRACE + emit + drop + drop + end + "#; + + let program1 = context.assemble(program1_source).unwrap(); + let program2 = context.assemble(program2_source).unwrap(); + let program3 = context.assemble(program3_source).unwrap(); + let program4 = context.assemble(program4_source).unwrap(); + + let digest1 = program1.hash(); + assert_eq!(digest1, program2.hash(), "constant and inline trace forms differ"); + assert_eq!(digest1, program3.hash(), "immediate and stack trace forms differ"); + assert_eq!(digest1, program4.hash(), "trace and expanded emit forms differ"); + + assert_eq!(program1.num_procedures(), 1); + assert_eq!(program2.num_procedures(), 1); + assert_eq!(program3.num_procedures(), 1); + assert_eq!(program4.num_procedures(), 1); +} + /// Since `foo` and `bar` have the same body, we only expect them to be added once to the program. #[test] fn duplicate_procedure() { diff --git a/docs/src/user_docs/assembly/events.md b/docs/src/user_docs/assembly/events.md index dcc2275e87..00b4133bf1 100644 --- a/docs/src/user_docs/assembly/events.md +++ b/docs/src/user_docs/assembly/events.md @@ -15,6 +15,8 @@ Event names should be as unique as possible to avoid collisions with other libra - **`emit`** - Interrupts execution, hands control to host (1 cycle) - **`emit.`** - Expands to `push. emit drop` (3 cycles). Immediate IDs must come from `event("...")` constants or inline `event("...")`. +- **`trace`** - Emits the trace ID at the top of the stack as an optional, read-only trace event without consuming it (3 cycles) +- **`trace.`** - Emits a stack-neutral trace event (5 cycles). Immediate IDs must come from `event("...")` constants or inline `trace.event("...")`. ```miden # Using a constant @@ -36,27 +38,26 @@ push. emit drop ### Trace Events (optional read-only events) -Trace events are a special class of optional, read-only events. Unlike regular custom events, they cannot mutate the advice provider, and emitting one for which the host has no handler registered should not result in an error. +Trace events are a special class of optional, read-only events. Unlike regular custom events, they cannot mutate the advice provider, and a trace event with no registered handler is a no-op. -A trace event is emitted by pushing the user trace event ID and then the `sys::trace_event` system event ID before `emit`. +Use the `trace` instruction to emit a trace event. Like `emit`, the bare form reads an ID from the top of the stack without consuming it. The immediate forms accept IDs derived from event names, either through an `event("...")` constant or inline. ```miden +# Using a constant const MY_TRACE = event("miden_debug::println") -const SYS_EVENT = event("sys::trace_event") - -push.MY_TRACE -push.SYS_EVENT -emit -drop -drop +trace.MY_TRACE -# Since `emit.` expands to `push., emit, drop`, this can be shortened too: +# Inline form +trace.event("miden_debug::println") -push.MY_TRACE -emit.SYS_EVENT +# Stack form (any Felt - not validated) +push. +trace drop ``` -When the host trace handler runs, `sys::trace_event` is at stack position 0 and `MY_TRACE` is at stack position 1. Both sequences above are stack-neutral and take 5 cycles. +The bare `trace` instruction takes 3 cycles and leaves the stack unchanged. `trace.MY_TRACE` and `trace.event("...")` are stack-neutral and take 5 cycles. + +As an implementation detail, `trace.` lowers to `push. push. emit drop drop`. Plain `trace` lowers to `push. emit drop`. Thus, when the host trace handler runs, `sys::trace_event` is at stack position 0 and the user trace ID is at stack position 1. On the Rust side, hosts can register trace handlers via `DefaultHost::register_trace_handler`, or implement `SyncHost::on_trace` / `Host::on_trace`. Hosts that do not implement `on_trace` still execute programs containing trace events: the default implementation is a no-op, and trace events are not routed to the regular `on_event` handler. diff --git a/docs/src/user_docs/assembly/instruction_reference.md b/docs/src/user_docs/assembly/instruction_reference.md index 22db570164..854a25b62b 100644 --- a/docs/src/user_docs/assembly/instruction_reference.md +++ b/docs/src/user_docs/assembly/instruction_reference.md @@ -377,6 +377,8 @@ Instructions for communicating with the host through events. | ------------------ | ----------------- | ----------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emit.` | `[...]` | `[...]` | 3 | Emits an event with the specified `event_id` to the host. The net effect on the operand stack is no change (internally expands to `push. emit drop`). Immediate `event_id` must be defined via `const.ID=event("...")` or inlined as `emit.event("...")`. Events allow programs to communicate contextual information to the host for triggering appropriate actions. Example: `emit.event("foo")` or `emit.MY_EVENT` | | `emit` | `[event_id, ...]` | `[event_id, ...]` | 1 | Emits an event using the `event_id` from the top of the stack. The stack remains unchanged as the event_id is read without consuming it. This instruction reads the event ID from the stack but does not modify the stack depth. Example: with `push.1230` on stack, `emit` reads the event ID 1230 and executes the corresponding event handler. Defined system events are reserved and use names in the `sys::` namespace. | +| `trace.` | `[...]` | `[...]` | 5 | Emits an optional, read-only trace event with the specified `trace_id`. Expands to `push. push. emit drop drop`. Immediate `trace_id` must be defined via `const.ID=event("...")` or inlined as `trace.event("...")`. The instruction is stack-neutral. Example: `trace.event("foo")` or `trace.MY_TRACE`. If no handler is registered for the trace ID, the event is a no-op. | +| `trace` | `[trace_id, ...]` | `[trace_id, ...]` | 3 | Emits an optional, read-only trace event using the `trace_id` at the top of the stack without consuming it. Expands to `push. emit drop`. Trace handlers can inspect processor state but cannot mutate VM state or the advice provider. If no handler is registered for the trace ID, the event is a no-op. | | `log_deferred` | `[_, STMNT, _, ...]` | `[ROOT_NEW, OUT_RATE1, OUT_CAP, ...]` | 1 | Folds `STMNT` from `stack[4..8]` into the VM's rolling deferred root via `ROOT_NEW = rate0(Poseidon2([ROOT_PREV, STMNT, [1,0,0,0]]))`, using the internally maintained previous root and the `Tag::AND` capacity word `[1, 0, 0, 0]`. `STMNT` must be a registered statement for a precompile claim that evaluates to `TRUE`. Writes three output words, normally dropped by wrappers. Core-library facades and internal support code normally wrap this low-level opcode when precompile claims need to be logged. | ## Debugging Operations diff --git a/miden-vm/tests/integration/operations/decorators/trace_events.rs b/miden-vm/tests/integration/operations/decorators/trace_events.rs index 0da66de6b3..30774e0c78 100644 --- a/miden-vm/tests/integration/operations/decorators/trace_events.rs +++ b/miden-vm/tests/integration/operations/decorators/trace_events.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use miden_assembly::Assembler; use miden_processor::{ - DefaultHost, ExecutionOptions, ProcessorState, Program, StackInputs, StackOutputs, + DefaultHost, ExecutionOptions, Felt, ProcessorState, Program, StackInputs, StackOutputs, advice::AdviceInputs, event::{EventName, SystemEvent, TraceError}, }; @@ -11,7 +11,10 @@ use super::TestHost; #[test] fn test_trace_event_handling() { - let trace_event_id = SystemEvent::TraceEvent.event_id().as_u64(); + let first_trace_name = "test::trace::first"; + let second_trace_name = "test::trace::second"; + let first_trace_id = EventName::new(first_trace_name).to_event_id().as_u64(); + let second_trace_id = EventName::new(second_trace_name).to_event_id().as_u64(); // Interleaving events and trace events to verify each get forwarded to the expected handler. let source = format!( @@ -20,25 +23,17 @@ fn test_trace_event_handling() { push.3000 emit drop - push.1000 - push.{trace_event_id} - emit - drop - drop + trace.event(\"{first_trace_name}\") push.4000 emit drop - push.2000 - push.{trace_event_id} - emit - drop - drop + trace.event(\"{second_trace_name}\") swapw dropw end" ); let program: Program = Assembler::default() - .assemble_program("program", &source) + .assemble_program("program", source) .unwrap() .unwrap_program(); let mut host = TestHost::default(); @@ -52,20 +47,15 @@ fn test_trace_event_handling() { .unwrap(); assert_eq!(host.event_handler, vec![3000, 4000]); - assert_eq!(host.trace_handler, vec![1000, 2000]); + assert_eq!(host.trace_handler, vec![first_trace_id, second_trace_id]); } /// Assembles a program that emits a single trace event. -fn trace_emit_program(trace_id_value: &str) -> String { - let trace_event_id = SystemEvent::TraceEvent.event_id().as_u64(); +fn trace_emit_program(trace_name: &str) -> String { format!( "\ begin - push.{trace_id_value} - push.{trace_event_id} - emit - drop - drop + trace.event(\"{trace_name}\") end" ) } @@ -73,8 +63,9 @@ fn trace_emit_program(trace_id_value: &str) -> String { /// An unhandled trace event must not abort execution. #[test] fn test_unhandled_trace_does_not_raise_error() { + let trace_name = "test::trace::unhandled"; let program: Program = Assembler::default() - .assemble_program("program", trace_emit_program("12345")) + .assemble_program("program", trace_emit_program(trace_name)) .unwrap() .unwrap_program(); @@ -94,28 +85,19 @@ fn test_unhandled_trace_does_not_raise_error() { #[test] fn test_trace_handler_registry() { - let trace_name = EventName::new("test::trace::going_through_registry"); - let trace_id = trace_name.to_event_id().as_u64(); + let trace_name = "test::trace::going_through_registry"; + let trace_id = EventName::new(trace_name).to_event_id().as_u64(); // Emit the same registered trace id twice. - let trace_event_id = SystemEvent::TraceEvent.event_id().as_u64(); let source = format!( "\ begin - push.{trace_id} - push.{trace_event_id} - emit - drop - drop - push.{trace_id} - push.{trace_event_id} - emit - drop - drop + trace.event(\"{trace_name}\") + trace.event(\"{trace_name}\") end" ); let program: Program = Assembler::default() - .assemble_program("program", &source) + .assemble_program("program", source) .unwrap() .unwrap_program(); @@ -129,7 +111,8 @@ fn test_trace_handler_registry() { }; let mut host = DefaultHost::default(); - host.register_trace_handler(trace_name, Arc::new(recorder)).unwrap(); + host.register_trace_handler(EventName::new(trace_name), Arc::new(recorder)) + .unwrap(); miden_processor::execute_sync( &program, @@ -143,3 +126,64 @@ fn test_trace_handler_registry() { let recorded = recorded.lock().unwrap(); assert_eq!(*recorded, vec![trace_id, trace_id]); } + +/// A trace event generated via the `trace` instruction, reading the trace ID from the stack. +#[test] +fn test_trace_event_from_stack() { + let source = "\ + begin + trace + end"; + let program: Program = Assembler::default() + .assemble_program("program", source) + .unwrap() + .unwrap_program(); + + let mut host = TestHost::default(); + let output = miden_processor::execute_sync( + &program, + StackInputs::new(&[Felt::from_u32(1000)]).unwrap(), + AdviceInputs::default(), + &mut host, + ExecutionOptions::default(), + ) + .unwrap(); + + assert_eq!(host.trace_handler, vec![1000]); + assert!(host.event_handler.is_empty()); + assert_eq!(output.stack.get_element(0).unwrap().as_canonical_u64(), 1000); +} + +/// A trace event generated manually by pushing the trace ID and the `sys::trace_event` system +/// event ID onto the stack before `emit`, without using the `trace` instruction. +#[test] +fn test_trace_event_manual_emit() { + let trace_event_id = SystemEvent::TraceEvent.event_id().as_u64(); + let source = format!( + "\ + begin + push.1000 + push.{trace_event_id} + emit + drop + drop + end" + ); + let program: Program = Assembler::default() + .assemble_program("program", source) + .unwrap() + .unwrap_program(); + + let mut host = TestHost::default(); + miden_processor::execute_sync( + &program, + StackInputs::default(), + AdviceInputs::default(), + &mut host, + ExecutionOptions::default(), + ) + .unwrap(); + + assert_eq!(host.trace_handler, vec![1000]); + assert!(host.event_handler.is_empty()); +} diff --git a/miden-vm/tests/integration/operations/sys_ops.rs b/miden-vm/tests/integration/operations/sys_ops.rs index eae5ba937c..9823eefb1f 100644 --- a/miden-vm/tests/integration/operations/sys_ops.rs +++ b/miden-vm/tests/integration/operations/sys_ops.rs @@ -1,6 +1,6 @@ use miden_processor::{ ExecutionError, ProcessorState, ZERO, - event::{EventName, NoopEventHandler, SystemEvent}, + event::{EventName, NoopEventHandler}, mast, operation::OperationError, }; @@ -98,23 +98,19 @@ fn emit() { #[test] fn emit_trace_event_without_handler() { - let trace_name = EventName::new("test::emit_trace::no_handler"); - let trace_id = trace_name.to_event_id().as_felt(); - let trace_sys_event_id = SystemEvent::TraceEvent.event_id(); + let trace_name = "test::emit_trace::no_handler"; - let source = format!("push.{trace_id} push.{trace_sys_event_id} emit drop drop"); + let source = format!("trace.event(\"{trace_name}\")"); let test = build_op_test!(&source, &[0, 0, 0, 0]); test.check_constraints(); } #[test] fn emit_trace_event_with_handler() { - let trace_name = EventName::new("test::emit_trace::handler"); - let trace_id = trace_name.to_event_id(); - let trace_sys_event_id = SystemEvent::TraceEvent.event_id(); + let trace_name = "test::emit_trace::handler"; - let source = format!("push.{trace_id} push.{trace_sys_event_id} emit drop drop"); + let source = format!("trace.event(\"{trace_name}\")"); let test = build_op_test!(&source, &[0, 0, 0, 0]) - .with_trace_handler(trace_name, |_: &ProcessorState| Ok(())); + .with_trace_handler(EventName::new(trace_name), |_: &ProcessorState| Ok(())); test.check_constraints(); } diff --git a/processor/src/host/handlers.rs b/processor/src/host/handlers.rs index 8c605eed07..cdebb6043c 100644 --- a/processor/src/host/handlers.rs +++ b/processor/src/host/handlers.rs @@ -180,10 +180,16 @@ impl Debug for EventHandlerRegistry { /// Handles an optional, read-only trace event emitted by the VM. /// -/// Trace events are emitted by pushing the user trace event ID and then -/// [`SystemEvent::TraceEvent`] onto the stack before `emit`. When the handler runs, -/// [`SystemEvent::TraceEvent`] is at stack position 0 and the user trace event ID is at position -/// 1. The handler receives a read-only [`ProcessorState`] and cannot return advice mutations. +/// Assembly programs emit trace events with `trace`, `trace.CONST`, or `trace.event("...")`. When +/// the handler runs, [`SystemEvent::TraceEvent`] is at stack position 0 and the user trace event ID +/// is at position 1. The handler receives a read-only [`ProcessorState`] and cannot return advice +/// mutations. +/// +/// The instruction expansions are: +/// +/// - `trace` expands to `push. emit drop`. +/// - `trace.CONST` and `trace.event("...")` expand to `push. push. emit +/// drop drop`. pub trait TraceHandler: Send + Sync + 'static { /// Handles the trace event when triggered. fn on_trace(&self, process: &ProcessorState) -> Result<(), TraceError>; diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index 2db7517959..d78b225d2a 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -129,7 +129,7 @@ pub trait SyncHost: BaseHost { /// Trace events are optional, read-only events. [`SystemEvent::TraceEvent`] is at stack /// position 0 and the user trace event ID is at position 1 when this handler is called. The /// handler cannot mutate the advice provider. Hosts that do not care about trace events can use - /// this default no-op implementation. Hosts are expected to not raise an error on encountering + /// this default no-op implementation. Hosts are expected not to raise an error on encountering /// a trace event for which no handler is registered. /// /// Return errors without event names or IDs - the caller will enrich them via @@ -176,7 +176,7 @@ pub trait Host: BaseHost { /// Trace events are optional, read-only events. [`SystemEvent::TraceEvent`] is at stack /// position 0 and the user trace event ID is at position 1 when this handler is called. The /// handler cannot mutate the advice provider. Hosts that do not care about trace events can use - /// this default no-op implementation. Hosts are expected to nat raise an error on encountering + /// this default no-op implementation. Hosts are expected not to raise an error on encountering /// a trace event for which no handler is registered. /// /// Return errors without event names or IDs - the caller will enrich them via diff --git a/processor/src/test_utils/non_tracing_test_host.rs b/processor/src/test_utils/non_tracing_test_host.rs index df8a637b30..ec45bff81b 100644 --- a/processor/src/test_utils/non_tracing_test_host.rs +++ b/processor/src/test_utils/non_tracing_test_host.rs @@ -71,7 +71,7 @@ mod tests { use miden_assembly::Assembler; use super::NonTracingTestHost; - use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs, event::SystemEvent}; + use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs}; /// A host which does not implement `on_trace` should still execute trace events gracefully via /// the default no-op implementation, and trace events must not be routed to `on_event`. @@ -80,7 +80,6 @@ mod tests { const REGULAR_EVENT_ID_1: u64 = 3000; const REGULAR_EVENT_ID_2: u64 = 4000; const TRACE_ID: u32 = 1000; - let trace_sys_event_id = SystemEvent::TraceEvent.event_id().as_u64(); let source = format!( "\ @@ -89,9 +88,7 @@ mod tests { emit drop push.{TRACE_ID} - push.{trace_sys_event_id} - emit - drop + trace drop push.{REGULAR_EVENT_ID_2} emit diff --git a/processor/src/test_utils/test_host.rs b/processor/src/test_utils/test_host.rs index 3327f8e246..9d51f23ccd 100644 --- a/processor/src/test_utils/test_host.rs +++ b/processor/src/test_utils/test_host.rs @@ -211,26 +211,21 @@ mod tests { use miden_assembly::Assembler; use super::TestHost; - use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs, event::SystemEvent}; + use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs}; #[test] fn test_host_records_trace_and_snapshot() { const TRACE_ID_1: u64 = 100; const TRACE_ID_2: u64 = 200; - let trace_sys_event_id = SystemEvent::TraceEvent.event_id().as_u64(); let source = format!( "\ begin push.{TRACE_ID_1} - push.{trace_sys_event_id} - emit - drop + trace drop push.{TRACE_ID_2} - push.{trace_sys_event_id} - emit - drop + trace drop end" ); diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index c4f2cba554..31f42a4663 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -17,7 +17,7 @@ use crate::{ BaseHost, DefaultHost, FastProcessor, KernelDescriptor, LoadedMastForest, ONE, ProcessorState, Program, StackInputs, SyncHost, Word, ZERO, advice::{AdviceInputs, AdviceMap, AdviceMutation}, - event::{EventError, EventHandler, EventName, SystemEvent, TraceError, TraceHandler}, + event::{EventError, EventHandler, EventName, TraceError, TraceHandler}, operation::Operation, }; @@ -284,18 +284,15 @@ fn test_diagnostic_host_event_error_uses_emit_location() { } #[test] -fn test_diagnostic_host_trace_error_uses_emit_location() { +fn test_diagnostic_host_trace_error_uses_trace_location() { let trace = EventName::new("test::host_trace_error"); let trace_id = trace.to_event_id(); - let trace_sys_event_id = SystemEvent::TraceEvent.event_id(); let source_manager = Arc::new(DefaultSourceManager::default()); let source = format!( " begin - push.{trace_id} - push.{trace_sys_event_id} - emit + trace.event(\"{trace}\") end" ); let package = Assembler::new(source_manager.clone()) @@ -319,11 +316,10 @@ fn test_diagnostic_host_trace_error_uses_emit_location() { // Name and id of the user defined trace event are shown format!(" x error during processing of event '{trace}' (ID: {trace_id})"), " `-> dummy host trace failure", - regex!(r#",-\[.*:5:13\]"#), - format!(" 4 | push.{trace_sys_event_id}"), - " 5 | emit", - regex!(r#":\s+\^\^\^"#), - " 6 | end", + regex!(r#",-\[.*:3:13\]"#), + r#" 3 | trace.event("test::host_trace_error")"#, + regex!(r#":\s+\^+"#), + " 4 | end", " `----" ); } diff --git a/processor/tests/async_compat.rs b/processor/tests/async_compat.rs index bfc376e5aa..39706ce365 100644 --- a/processor/tests/async_compat.rs +++ b/processor/tests/async_compat.rs @@ -6,7 +6,7 @@ use miden_processor::{ BaseHost, DefaultHost, ExecutionOptions, FastProcessor, Felt, FutureMaybeSend, Host, LoadedMastForest, ProcessorState, StackInputs, Word, advice::{AdviceInputs, AdviceMutation}, - event::{EventError, EventName, SystemEvent, TraceError}, + event::{EventError, EventName, TraceError}, }; struct YieldingAsyncHost { @@ -76,15 +76,10 @@ fn simple_program() -> miden_processor::Program { } fn emit_trace_program() -> miden_processor::Program { - let trace_name = EventName::new("test::async::trace_emit"); - let trace_id = trace_name.to_event_id().as_u64(); - let trace_sys_event_id = SystemEvent::TraceEvent.event_id(); + let trace_name = "test::async::trace_emit"; Assembler::default() - .assemble_program( - "program", - format!("begin push.{trace_id} push.{trace_sys_event_id} emit drop drop end"), - ) + .assemble_program("program", format!("begin trace.event(\"{trace_name}\") end")) .expect("program should compile") .unwrap_program() } From 6bb01077d8195d518a86ca80204db3d7a7e7ff2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:38:26 -0400 Subject: [PATCH 07/17] feat: AdviceInputs constructors and ProgramExecutor trait (#3540) * feat(core/advice): add AdviceInputs::new and From constructors Add a full constructor that assembles AdviceInputs from owned parts, and a From impl that defaults the stack and store. These complement the existing builder-style accessors (with_advice_stack, with_map, with_merkle_store). * refactor(processor)!: remove free execute()/execute_sync() functions No downstream consumer calls these. The only callers were in-repo tests and the miden-vm re-export. The prover and miden-base use FastProcessor directly because they need the trace or package-debug path. Removed the free functions and the miden-vm re-export, and moved every caller onto FastProcessor::new_with_options(...).execute[_sync](...). BREAKING CHANGE: use FastProcessor::new_with_options(...) followed by execute()/execute_sync() instead of the free functions. Refs #3538 * feat(processor): add ProgramExecutor trait with FastProcessor default impl Introduces a pluggable program-execution abstraction in miden-processor so that alternative execution backends (debuggers, instrumented executors, or other VMs) can be swapped in without touching the surrounding executor wiring. The trait is byte-for-byte faithful to the one already in miden-tx (which carries a TODO to move it into miden-vm), so downstream can later delete its local copy and re-export this one. FastProcessor is the default implementation: - new() wraps FastProcessor::new_with_options(), panicking on invalid advice inputs (matching the existing infallible-from-the-trait contract). - execute() delegates to FastProcessor::execute(). - execute_with_package_debug_info() routes to the package-debug entrypoints, selecting the source-node-anchored variant when an entrypoint node is given and falling back to the package-rooted variant otherwise. Documented on new() that, for the concrete FastProcessor type, the trait constructor is shadowed by the inherent 1-arg FastProcessor::new and so must be invoked via fully-qualified syntax ::new. Added three trait-dispatch tests: the default execute() path, the no-entrypoint-node fallback in execute_with_package_debug_info(), and the Some(entrypoint_source_node) arm routed through the trait (mirroring the existing package_source_debug_execution_uses_manifest_entrypoint_source_node inherent-method test). Refs #3538 * chore: Changelog * docs(miden-vm): fix README doctest after execute_sync removal The execution example doctest imported the free execute_sync from miden_vm and called it directly. That function was removed when the free execute()/ execute_sync() entrypoints were dropped in favor of FastProcessor. Rewrite the example to construct a FastProcessor via new_with_options and call execute_sync on it, matching the new API. Fixes the doc-tests CI failure on #3540. * refactor: address PR review feedback * chore: fix nightly formatting --- CHANGELOG.md | 8 + core/src/advice/mod.rs | 96 +++++++--- crates/lib/core/tests/crypto/dsa.rs | 2 +- crates/lib/core/tests/debug.rs | 24 ++- .../precompiles_bench/input_generation.rs | 2 +- crates/test-utils/src/test_builders.rs | 6 +- miden-vm/README.md | 70 +++---- miden-vm/src/internal.rs | 2 +- miden-vm/src/lib.rs | 6 +- miden-vm/tests/integration/exec.rs | 21 +- .../operations/decorators/advice.rs | 12 +- .../operations/decorators/events.rs | 10 +- .../operations/decorators/trace_events.rs | 33 ++-- .../src/execution/operations/io_ops/tests.rs | 6 +- processor/src/executor.rs | 180 ++++++++++++++++++ processor/src/fast/mod.rs | 13 +- processor/src/fast/tests/mod.rs | 49 ++++- processor/src/host/advice/mod.rs | 8 +- processor/src/lib.rs | 50 +---- .../src/test_utils/non_tracing_test_host.rs | 8 +- processor/src/test_utils/test_host.rs | 8 +- processor/tests/advice_public_api.rs | 4 +- processor/tests/async_compat.rs | 21 +- processor/tests/issue_2456_test.rs | 9 +- verifier/src/recursive/mod.rs | 32 ++-- 25 files changed, 473 insertions(+), 207 deletions(-) create mode 100644 processor/src/executor.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 387d708dd6..1412e939da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ #### Changes - [BREAKING] Reduced the precompile STARK relation from 12 AIRs to 10 by merging the chunk/node/sponge and EC point/group stores ([#3464](https://github.com/0xMiden/miden-vm/pull/3464)). +#### Features + +- Added `AdviceInputs::new` constructor and `From` impl, complementing the existing builder-style accessors for assembling advice inputs from their parts ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). +- Added the `ProgramExecutor` trait to `miden-processor`, with `FastProcessor` as the default implementation, so alternative execution engines can be plugged in without changing the surrounding executor wiring ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). + +#### Changes + +- [BREAKING] Removed the free `execute()` and `execute_sync()` functions from `miden-vm`/`miden-processor`. Use `FastProcessor::new_with_options(...)` followed by `execute()`/`execute_sync()` instead ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). ## v0.29.0 (2026-08-04) diff --git a/core/src/advice/mod.rs b/core/src/advice/mod.rs index f26d2e5d37..efc09290ea 100644 --- a/core/src/advice/mod.rs +++ b/core/src/advice/mod.rs @@ -28,24 +28,39 @@ pub use stack::AdviceStack; /// with Merkle trees. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct AdviceInputs { - advice_stack: AdviceStack, - pub map: AdviceMap, - pub store: MerkleStore, + stack: AdviceStack, + map: AdviceMap, + store: MerkleStore, } impl AdviceInputs { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Replaces the advice stack with the provided typed stack. - pub fn with_advice_stack(mut self, stack: AdviceStack) -> Self { - self.advice_stack = stack; + /// Creates a new advice inputs container from the provided stack, map, and Merkle store. + pub fn new(stack: AdviceStack, map: AdviceMap, store: MerkleStore) -> Self { + Self { stack, map, store } + } + + /// Replaces the stack with the provided typed stack. + pub fn with_stack(mut self, stack: AdviceStack) -> Self { + self.stack = stack; self } - /// Returns the advice stack as a typed stack. - pub fn advice_stack(&self) -> AdviceStack { - self.advice_stack.clone() + /// Returns the advice stack. + pub fn stack(&self) -> AdviceStack { + self.stack.clone() + } + + /// Returns the advice map. + pub fn map(&self) -> &AdviceMap { + &self.map + } + + /// Returns the Merkle store. + pub fn store(&self) -> &MerkleStore { + &self.store } /// Extends the map of values with the given argument, replacing previously inserted items. @@ -68,21 +83,31 @@ impl AdviceInputs { /// Extends the contents of this instance with the contents of the other instance. pub fn extend(&mut self, other: Self) { - self.advice_stack.append_elements(other.advice_stack.into_elements()); + self.stack.append_elements(other.stack.into_elements()); self.map.extend(other.map); self.store.extend(other.store.inner_nodes()); } /// Consumes this instance and returns its parts. pub fn into_parts(self) -> (AdviceStack, AdviceMap, MerkleStore) { - (self.advice_stack, self.map, self.store) + (self.stack, self.map, self.store) + } +} + +impl From for AdviceInputs { + fn from(map: AdviceMap) -> Self { + Self { + stack: AdviceStack::default(), + map, + store: MerkleStore::default(), + } } } impl Serializable for AdviceInputs { fn write_into(&self, target: &mut W) { - let Self { advice_stack, map, store } = self; - let stack: Vec = advice_stack.iter().copied().collect(); + let Self { stack, map, store } = self; + let stack: Vec = stack.iter().copied().collect(); stack.write_into(target); map.write_into(target); store.write_into(target); @@ -94,7 +119,7 @@ impl Deserializable for AdviceInputs { let stack = Vec::::read_from(source)?; let map = AdviceMap::read_from(source)?; let store = MerkleStore::read_from(source)?; - Ok(Self { advice_stack: stack.into(), map, store }) + Ok(Self { stack: stack.into(), map, store }) } } @@ -105,9 +130,10 @@ impl Deserializable for AdviceInputs { mod tests { use alloc::vec::Vec; - use super::{AdviceInputs, AdviceStack}; + use super::{AdviceInputs, AdviceMap, AdviceStack}; use crate::{ Felt, Word, + crypto::merkle::MerkleStore, serde::{Deserializable, Serializable}, }; @@ -118,24 +144,48 @@ mod tests { assert_eq!(advice1, advice2); - let advice1 = AdviceInputs::default() - .with_advice_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); - let advice2 = AdviceInputs::default() - .with_advice_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); + let advice1 = + AdviceInputs::default().with_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); + let advice2 = + AdviceInputs::default().with_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); assert_eq!(advice1, advice2); } #[test] fn test_advice_inputs_serialization() { - let advice1 = AdviceInputs::default() - .with_advice_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); + let advice1 = + AdviceInputs::default().with_stack(AdviceStack::try_from_values([1, 2, 3]).unwrap()); let bytes = advice1.to_bytes(); let advice2 = AdviceInputs::read_from_bytes(&bytes).unwrap(); assert_eq!(advice1, advice2); } + #[test] + fn advice_inputs_new_assembles_parts() { + let stack = AdviceStack::try_from_values([1, 2, 3]).unwrap(); + let map = AdviceMap::from_iter([(Word::default(), vec![Felt::new_unchecked(7)])]); + let store = MerkleStore::default(); + + let advice = AdviceInputs::new(stack.clone(), map.clone(), store.clone()); + + assert_eq!(advice.stack(), stack); + assert_eq!(advice.map, map); + assert_eq!(advice.store, store); + } + + #[test] + fn advice_inputs_from_advice_map_defaults_other_parts() { + let map = AdviceMap::from_iter([(Word::default(), vec![Felt::new_unchecked(7)])]); + + let advice = AdviceInputs::from(map.clone()); + + assert_eq!(advice.stack(), AdviceStack::default()); + assert_eq!(advice.map, map); + assert_eq!(advice.store, MerkleStore::default()); + } + #[test] fn advice_inputs_accept_typed_advice_stack() { let mut stack = AdviceStack::new(); @@ -150,9 +200,9 @@ mod tests { .into(), ); - let advice = AdviceInputs::default().with_advice_stack(stack.clone()); + let advice = AdviceInputs::default().with_stack(stack.clone()); - assert_eq!(advice.advice_stack(), stack); + assert_eq!(advice.stack(), stack); } #[test] diff --git a/crates/lib/core/tests/crypto/dsa.rs b/crates/lib/core/tests/crypto/dsa.rs index ad10bf7902..95281b91ae 100644 --- a/crates/lib/core/tests/crypto/dsa.rs +++ b/crates/lib/core/tests/crypto/dsa.rs @@ -348,7 +348,7 @@ fn run_core_program_with_advice( advice_stack.append_elements(advice.iter().copied()); let processor = FastProcessor::new_with_options( StackInputs::default(), - AdviceInputs::default().with_advice_stack(advice_stack), + AdviceInputs::default().with_stack(advice_stack), ExecutionOptions::default(), ) .expect("processor construction"); diff --git a/crates/lib/core/tests/debug.rs b/crates/lib/core/tests/debug.rs index b8d7584cef..ed21e51d5d 100644 --- a/crates/lib/core/tests/debug.rs +++ b/crates/lib/core/tests/debug.rs @@ -20,16 +20,28 @@ use miden_core_lib::{ }, }; use miden_processor::{ - DefaultHost, ExecutionError, ExecutionOptions, ExecutionOutput, HostLibrary, MemoryError, - StackInputs, + DefaultHost, ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, HostLibrary, + MemoryError, Program, StackInputs, SyncHost, advice::{AdviceInputs, AdviceStack}, event::{EventHandler, EventName}, - execute_sync, }; // HARNESS // ================================================================================================ +/// Runs `program` against `host`, constructing a [`FastProcessor`] the way production callers do. +fn execute_sync( + program: &Program, + stack_inputs: StackInputs, + advice_inputs: AdviceInputs, + host: &mut impl SyncHost, + options: ExecutionOptions, +) -> Result { + let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, options) + .expect("failed to construct FastProcessor"); + processor.execute_sync(program, host) +} + /// A [`fmt::Write`] that appends into a shared, thread-safe string buffer. #[derive(Clone)] struct SharedBuf(Arc>); @@ -407,7 +419,7 @@ fn print_mem_rejects_full_range() { fn print_adv_stack_all_outputs_advice_stack() { let mut stack = AdviceStack::new(); stack.append_elements([Felt::new_unchecked(7), Felt::new_unchecked(8), Felt::new_unchecked(9)]); - let advice = AdviceInputs::default().with_advice_stack(stack); + let advice = AdviceInputs::default().with_stack(stack); let source = " use miden::core::debug begin @@ -431,7 +443,7 @@ fn print_adv_stack_outputs_range() { Felt::new_unchecked(9), Felt::new_unchecked(10), ]); - let advice = AdviceInputs::default().with_advice_stack(stack); + let advice = AdviceInputs::default().with_stack(stack); let source = " use miden::core::debug begin @@ -571,7 +583,7 @@ fn debug_handlers_compose_with_default_core_handlers() { "; let mut stack = AdviceStack::new(); stack.append_element(Felt::new_unchecked(7)); - let advice = AdviceInputs::default().with_advice_stack(stack); + let advice = AdviceInputs::default().with_stack(stack); let core_lib = CoreLibrary::default(); let assembler = Assembler::default() diff --git a/crates/precompiles/benches/precompiles_bench/input_generation.rs b/crates/precompiles/benches/precompiles_bench/input_generation.rs index 50a4d0e5bd..f156c8c10c 100644 --- a/crates/precompiles/benches/precompiles_bench/input_generation.rs +++ b/crates/precompiles/benches/precompiles_bench/input_generation.rs @@ -44,7 +44,7 @@ pub(crate) fn generate_advice_inputs(workload: PrecompileWorkload) -> AdviceInpu } assert_eq!(advice_stack.len(), workload.ecdsas * 40, "unexpected ECDSA advice length"); - AdviceInputs::default().with_advice_stack(advice_stack) + AdviceInputs::default().with_stack(advice_stack) } fn ecdsa_message(index: u64) -> Word { diff --git a/crates/test-utils/src/test_builders.rs b/crates/test-utils/src/test_builders.rs index 85d8ae8895..9b009462b3 100644 --- a/crates/test-utils/src/test_builders.rs +++ b/crates/test-utils/src/test_builders.rs @@ -237,7 +237,7 @@ macro_rules! build_test_by_mode { let advice_stack = $crate::advice_stack_from(&$advice_stack).unwrap(); let store = $crate::crypto::MerkleStore::new(); let advice_inputs = $crate::AdviceInputs::default() - .with_advice_stack(advice_stack) + .with_stack(advice_stack) .with_merkle_store(store); let name = format!("test{}", line!()); let source_manager = ::alloc::sync::Arc::new($crate::DefaultSourceManager::default()); @@ -269,7 +269,7 @@ macro_rules! build_test_by_mode { let stack_inputs = $crate::stack_inputs_from_ints(stack_inputs); let advice_stack = $crate::advice_stack_from(&$advice_stack).unwrap(); let advice_inputs = $crate::AdviceInputs::default() - .with_advice_stack(advice_stack) + .with_stack(advice_stack) .with_merkle_store($advice_merkle_store); let name = format!("test{}", line!()); let source_manager = ::alloc::sync::Arc::new($crate::DefaultSourceManager::default()); @@ -302,7 +302,7 @@ macro_rules! build_test_by_mode { let stack_inputs = $crate::stack_inputs_from_ints(stack_inputs); let advice_stack = $crate::advice_stack_from(&$advice_stack).unwrap(); let advice_inputs = $crate::AdviceInputs::default() - .with_advice_stack(advice_stack) + .with_stack(advice_stack) .with_merkle_store($advice_merkle_store) .with_map($advice_map); let name = format!("test{}", line!()); diff --git a/miden-vm/README.md b/miden-vm/README.md index 59a9370b76..272eec8366 100644 --- a/miden-vm/README.md +++ b/miden-vm/README.md @@ -22,7 +22,7 @@ Currently, there are 3 ways to get values onto the stack: 1. You can use `push` instruction to push values onto the stack. These values become a part of the program itself, and, therefore, cannot be changed between program executions. You can think of them as constants. 2. The stack can be initialized to some set of values at the beginning of the program. These inputs are public and must be shared with the verifier for them to verify a proof of the correct execution of a Miden program. At most 16 values could be provided for the stack initialization, attempts to provide more than 16 values will cause an error. -3. The program may request nondeterministic advice inputs from the prover. These inputs are secret inputs. This means that the prover does not need to share them with the verifier. There are three types of advice inputs: (1) a single advice stack which can contain any number of elements; (2) a key-mapped element lists which can be pushed onto the advice stack; (3) a Merkle store, which is used to provide nondeterministic inputs for instructions which work with Merkle trees. There are no restrictions on the number of advice inputs a program can request. +3. The program may request nondeterministic advice inputs from the prover. These inputs are secret inputs. This means that the prover does not need to share them with the verifier. Advice can come from a stack, a map of element lists, or a Merkle store used by instructions that work with Merkle trees. There are no restrictions on the number of advice inputs a program can request. The stack is provided to Miden VM via `StackInputs` struct. These are public inputs of the execution, and should also be provided to the verifier. The secret inputs for the program are provided via the `Host` interface. The default implementation of the host relies on in-memory advice provider (`AdviceProvider`) that can be commonly used for operations that won't require persistence. @@ -39,11 +39,11 @@ Miden crate exposes several functions which can be used to execute programs, gen To execute a program on Miden VM, you can use `execute()`. The sync `execute_sync()` variant is also available for sync callers. These functions take the following arguments: -- `program: &Program` - a reference to a Miden program to be executed. -- `stack_inputs: StackInputs` - a set of public inputs with which to execute the program. -- `advice_inputs: AdviceInputs` - the private inputs used to build the advice provider; use `AdviceInputs::default()` when no private inputs are needed. -- `host` - an instance of `Host` for `execute()` or `SyncHost` for `execute_sync()`, used to supply non-deterministic inputs to the VM and receive messages from the VM. -- `options: ExecutionOptions` - a set of options for executing the specified program (e.g., max allowed number of cycles). +- `program: &Program` is a reference to the Miden program. +- `stack_inputs: StackInputs` contains the public inputs. +- `advice_inputs: AdviceInputs` contains the private inputs used to build the advice provider. Use `AdviceInputs::default()` when no private inputs are needed. +- `host` is a `Host` for `execute()` or a `SyncHost` for `execute_sync()`. It supplies nondeterministic inputs to the VM and receives messages from it. +- `options: ExecutionOptions` controls execution settings such as the maximum cycle count. The function returns a `Result` which will contain the final stack state and other execution outputs if the execution was successful, or an error if the execution @@ -56,7 +56,7 @@ For example: ```rust use miden_vm::{ advice::AdviceInputs, - Assembler, execute_sync, ExecutionOptions, DefaultHost, StackInputs + Assembler, DefaultHost, ExecutionOptions, FastProcessor, StackInputs }; // instantiate the assembler @@ -81,8 +81,14 @@ let mut host = DefaultHost::default(); let exec_options = ExecutionOptions::default(); // execute the program with no inputs -let output = - execute_sync(&program.unwrap_program(), stack_inputs, advice_inputs.clone(), &mut host, exec_options).unwrap(); +let output = FastProcessor::new_with_options( + stack_inputs, + advice_inputs.clone(), + exec_options, +) +.unwrap() +.execute_sync(&program.unwrap_program(), &mut host) +.unwrap(); ``` ### Proving program execution @@ -91,17 +97,17 @@ To execute a program on Miden VM and generate a proof that the program was execu can use the `prove_sync()` function. The async `prove()` variant is also available for async callers. `prove_sync()` takes the following arguments: -- `program: &Program` - a reference to a Miden program to be executed. -- `stack_inputs: StackInputs` - a set of public inputs with which to execute the program. -- `advice_inputs: AdviceInputs` - the initial nondeterministic inputs available to the VM. -- `host: Host` - an instance of a `Host` which can be used to supply non-deterministic inputs to the VM and receive messages from the VM. -- `execution_options: ExecutionOptions` - VM execution parameters such as cycle limits and trace fragmentation. -- `options: ProvingOptions` - proof-generation parameters. The default options target 96-bit security level. +- `program: &Program` is a reference to the Miden program. +- `stack_inputs: StackInputs` contains the public inputs. +- `advice_inputs: AdviceInputs` contains the initial nondeterministic inputs available to the VM. +- `host: Host` supplies nondeterministic inputs to the VM and receives messages from it. +- `execution_options: ExecutionOptions` controls VM execution parameters such as cycle limits and trace fragmentation. +- `options: ProvingOptions` controls proof generation. The default targets a 96-bit security level. If the program is executed successfully, the function returns a tuple with 2 elements: -- `outputs: StackOutputs` - the outputs generated by the program. -- `proof: ExecutionProof` - proof of program execution. `ExecutionProof` can be easily serialized and deserialized using `to_bytes()` and `from_bytes()` functions respectively. +- `outputs: StackOutputs` contains the outputs generated by the program. +- `proof: ExecutionProof` proves program execution. It can be serialized and deserialized with `to_bytes()` and `from_bytes()`. #### Proof generation example @@ -142,8 +148,8 @@ assert_eq!(8, outputs.first().unwrap().as_canonical_u64()); To verify program execution, use `Verifier::new().verify(...)`. The verifier takes the following parameters: -- `proof: ExecutionProof` - the proof generated during program execution. -- `claim: ExecutionClaim` - the claimed program information, stack inputs, and stack outputs. +- `proof: ExecutionProof` is the proof generated during program execution. +- `claim: ExecutionClaim` contains the claimed program information, stack inputs, and stack outputs. Stack inputs are expected to be ordered as if they would be pushed onto the stack one by one. Thus, their expected order on the stack will be the reverse of the order in which they are provided, and the last value in the `stack_inputs` is expected to be the value at the top of the stack. @@ -153,7 +159,7 @@ The verifier returns `Result` which will be `Ok(security > If a program with the provided hash is executed against some secret inputs and the provided public inputs, it will produce the provided outputs. -Notice how the verifier needs to know only the hash of the program - not what the actual program was. +The verifier needs only the program hash. It does not need the program itself. #### Proof verification example @@ -225,7 +231,7 @@ let program = assembler.assemble_program("prg", &source).unwrap(); let mut host = DefaultHost::default(); // initialize the stack with values 0 and 1 -let stack_inputs = StackInputs::try_from_ints([1, 0]).unwrap(); +let stack_inputs = StackInputs::new(&[1_u32.into(), 0_u32.into()]).unwrap(); // execute the program let (outputs, proof) = miden_vm::prove_sync( @@ -245,7 +251,7 @@ let stack = outputs.get_num_elements(1); assert_eq!(12586269025, stack[0].as_canonical_u64()); ``` -Above, we used public inputs to initialize the stack rather than using `push` operations. This makes the program a bit simpler, and also allows us to run the program from arbitrary starting points without changing program hash. +Above, we used public inputs to initialize the stack. This keeps the program simpler and lets us run it from arbitrary starting points without changing its hash. ## CLI interface @@ -295,12 +301,12 @@ Once the executable has been compiled, you can run Miden VM like so: Currently, Miden VM can be executed with the following subcommands: -- `run` - this will execute a Miden assembly program and output the result, but will not generate a proof of execution. -- `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution. -- `verify` - this will verify a previously generated proof of execution for a given program. -- `compile` - this will compile a Miden assembly program and outputs stats about the compilation process. -- `debug` - this will instantiate a CLI debugger against the specified Miden assembly program and inputs. -- `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution. +- `run` executes a Miden assembly program and outputs the result without generating a proof. +- `prove` executes a Miden assembly program and generates a STARK proof. +- `verify` verifies a previously generated proof for a given program. +- `compile` compiles a Miden assembly program and reports compilation statistics. +- `debug` starts a CLI debugger for the specified Miden assembly program and inputs. +- `analyze` runs a Miden assembly program against specific inputs and reports execution statistics. All of the above subcommands require various parameters to be provided. To get more detailed help on what is needed for a given subcommand, you can run the following: @@ -328,10 +334,10 @@ This will run the example code to completion and will output the top element rem Miden VM can be compiled with the following features: -- `std` - enabled by default and relies on the Rust standard library. -- `concurrent` - implies `std` and also enables multi-threaded proof generation. -- `executable` - required for building Miden VM binary as described above. Implies `std`. -- `metal` - enables [Metal]()-based acceleration of proof generation (for recursive proofs) on supported platforms (e.g., Apple silicon). +- `std` is enabled by default and relies on the Rust standard library. +- `concurrent` implies `std` and enables multithreaded proof generation. +- `executable` is required for building the Miden VM binary as described above. It implies `std`. +- `metal` enables [Metal]()-based acceleration of proof generation for recursive proofs on supported platforms such as Apple silicon. - `no_std` does not rely on the Rust standard library and enables compilation to WebAssembly. - Only the `wasm32-unknown-unknown` and `wasm32-wasip1` targets are officially supported. diff --git a/miden-vm/src/internal.rs b/miden-vm/src/internal.rs index 3eb6b7c4fd..faa3574f6f 100644 --- a/miden-vm/src/internal.rs +++ b/miden-vm/src/internal.rs @@ -113,7 +113,7 @@ impl InputFile { .parse_advice_stack() .map_err(|e| format!("failed to parse advice provider: {e}"))?; let stack = AdviceStack::try_from_values(stack).map_err(|e| e.to_string())?; - advice_inputs = advice_inputs.with_advice_stack(stack); + advice_inputs = advice_inputs.with_stack(stack); if let Some(map) = self .parse_advice_map() diff --git a/miden-vm/src/lib.rs b/miden-vm/src/lib.rs index 14de04b46e..4518115fa3 100644 --- a/miden-vm/src/lib.rs +++ b/miden-vm/src/lib.rs @@ -13,13 +13,11 @@ pub use miden_core::{ program::ExecutionClaim, proof::{DeferredProof, ExecutionProof, HashFunction, StarkProof}, }; -#[cfg(not(target_family = "wasm"))] -pub use miden_processor::execute_sync; pub use miden_processor::{ BaseHost, DefaultHost, ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, FutureMaybeSend, Host, KernelDescriptor, Program, ProgramInfo, StackInputs, SyncHost, - TraceBuildInputs, TraceGenerationContext, ZERO, advice, crypto, execute, field, - operation::Operation, serde, trace, trace::ExecutionTrace, utils, + TraceBuildInputs, TraceGenerationContext, ZERO, advice, crypto, field, operation::Operation, + serde, trace, trace::ExecutionTrace, utils, }; pub use miden_prover::{InputError, ProvingOptions, StackOutputs, TraceProvingInputs, Word, prove}; #[cfg(not(target_family = "wasm"))] diff --git a/miden-vm/tests/integration/exec.rs b/miden-vm/tests/integration/exec.rs index 387029b71b..fa8d8b9295 100644 --- a/miden-vm/tests/integration/exec.rs +++ b/miden-vm/tests/integration/exec.rs @@ -4,7 +4,7 @@ use core::assert_matches; use miden_assembly::{Assembler, DefaultSourceManager}; use miden_core::{ONE, Word, advice::AdviceMap, program::Program}; use miden_processor::{ - ExecutionOptions, StackInputs, + ExecutionOptions, FastProcessor, StackInputs, advice::{AdviceError, AdviceInputs}, mast::MastForest, }; @@ -25,16 +25,17 @@ fn advice_map_loaded_before_execution() { .unwrap() .unwrap_program(); - // Test `miden_processor::execute_sync` fails if no advice map provided with the program + // Test `FastProcessor::execute_sync` fails if no advice map provided with the program let mut host = DefaultHost::default().with_source_manager(Arc::new(DefaultSourceManager::default())); - match miden_processor::execute_sync( - &program_without_advice_map, + match FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), - ) { + ) + .expect("failed to construct FastProcessor") + .execute_sync(&program_without_advice_map, &mut host) + { Ok(_) => panic!("Expected error"), Err(e) => { assert_matches!( @@ -47,7 +48,7 @@ fn advice_map_loaded_before_execution() { }, } - // Test `miden_processor::execute_sync` works if advice map provided with the program + // Test `FastProcessor::execute_sync` works if advice map provided with the program let mast_forest: MastForest = (**program_without_advice_map.mast_forest()).clone(); let key = Word::new([ONE, ONE, ONE, ONE]); @@ -58,12 +59,12 @@ fn advice_map_loaded_before_execution() { Program::new(mast_forest.into(), program_without_advice_map.entrypoint()); let mut host = DefaultHost::default(); - miden_processor::execute_sync( - &program_with_advice_map, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program_with_advice_map, &mut host) .unwrap(); } diff --git a/miden-vm/tests/integration/operations/decorators/advice.rs b/miden-vm/tests/integration/operations/decorators/advice.rs index f3862350d4..7b0f32af7d 100644 --- a/miden-vm/tests/integration/operations/decorators/advice.rs +++ b/miden-vm/tests/integration/operations/decorators/advice.rs @@ -1,6 +1,6 @@ use miden_assembly::Assembler; use miden_core::Felt; -use miden_processor::{ExecutionOptions, StackInputs, advice::AdviceInputs}; +use miden_processor::{ExecutionOptions, FastProcessor, StackInputs, advice::AdviceInputs}; use miden_prover::Word; use miden_utils_testing::{build_test, crypto::MerkleStore}; @@ -489,12 +489,8 @@ fn run_insert_mem_with_max_size( let mut host = super::TestHost::default(); let options = ExecutionOptions::default().with_max_adv_map_value_size(max_adv_map_value_size); - miden_processor::execute_sync( - &program, - StackInputs::default(), - AdviceInputs::default(), - &mut host, - options, - )?; + FastProcessor::new_with_options(StackInputs::default(), AdviceInputs::default(), options) + .map_err(miden_processor::ExecutionError::advice_error_no_context)? + .execute_sync(&program, &mut host)?; Ok(()) } diff --git a/miden-vm/tests/integration/operations/decorators/events.rs b/miden-vm/tests/integration/operations/decorators/events.rs index 472614096c..434f31248c 100644 --- a/miden-vm/tests/integration/operations/decorators/events.rs +++ b/miden-vm/tests/integration/operations/decorators/events.rs @@ -1,5 +1,7 @@ use miden_assembly::Assembler; -use miden_processor::{ExecutionOptions, Program, StackInputs, advice::AdviceInputs}; +use miden_processor::{ + ExecutionOptions, FastProcessor, Program, StackInputs, advice::AdviceInputs, +}; use super::TestHost; @@ -22,13 +24,13 @@ fn test_event_handling() { .unwrap() .unwrap_program(); let mut host = TestHost::default(); - miden_processor::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .unwrap(); // make sure events were handled correctly diff --git a/miden-vm/tests/integration/operations/decorators/trace_events.rs b/miden-vm/tests/integration/operations/decorators/trace_events.rs index 30774e0c78..468b9090fa 100644 --- a/miden-vm/tests/integration/operations/decorators/trace_events.rs +++ b/miden-vm/tests/integration/operations/decorators/trace_events.rs @@ -2,7 +2,8 @@ use std::sync::{Arc, Mutex}; use miden_assembly::Assembler; use miden_processor::{ - DefaultHost, ExecutionOptions, Felt, ProcessorState, Program, StackInputs, StackOutputs, + DefaultHost, ExecutionOptions, FastProcessor, Felt, ProcessorState, Program, StackInputs, + StackOutputs, advice::AdviceInputs, event::{EventName, SystemEvent, TraceError}, }; @@ -37,13 +38,13 @@ fn test_trace_event_handling() { .unwrap() .unwrap_program(); let mut host = TestHost::default(); - miden_processor::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .unwrap(); assert_eq!(host.event_handler, vec![3000, 4000]); @@ -71,13 +72,13 @@ fn test_unhandled_trace_does_not_raise_error() { // No trace handler is registered on this host. let mut host = DefaultHost::default(); - let output = miden_processor::execute_sync( - &program, + let output = FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .expect("emitting an unhandled trace event must not abort execution"); assert_eq!(output.stack, StackOutputs::default()); @@ -114,13 +115,13 @@ fn test_trace_handler_registry() { host.register_trace_handler(EventName::new(trace_name), Arc::new(recorder)) .unwrap(); - miden_processor::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .unwrap(); let recorded = recorded.lock().unwrap(); @@ -140,13 +141,13 @@ fn test_trace_event_from_stack() { .unwrap_program(); let mut host = TestHost::default(); - let output = miden_processor::execute_sync( - &program, + let output = FastProcessor::new_with_options( StackInputs::new(&[Felt::from_u32(1000)]).unwrap(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .unwrap() + .execute_sync(&program, &mut host) .unwrap(); assert_eq!(host.trace_handler, vec![1000]); @@ -175,13 +176,13 @@ fn test_trace_event_manual_emit() { .unwrap_program(); let mut host = TestHost::default(); - miden_processor::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .unwrap() + .execute_sync(&program, &mut host) .unwrap(); assert_eq!(host.trace_handler, vec![1000]); diff --git a/processor/src/execution/operations/io_ops/tests.rs b/processor/src/execution/operations/io_ops/tests.rs index 55307fe93f..10bbf932ab 100644 --- a/processor/src/execution/operations/io_ops/tests.rs +++ b/processor/src/execution/operations/io_ops/tests.rs @@ -24,7 +24,7 @@ fn test_op_advpop() { // popping from the advice stack should push the value onto the operand stack let mut advice_stack = AdviceStack::new(); advice_stack.append_element(Felt::new_unchecked(3)); - let advice_inputs = AdviceInputs::default().with_advice_stack(advice_stack); + let advice_inputs = AdviceInputs::default().with_stack(advice_stack); let mut processor = FastProcessor::new(StackInputs::default()) .with_advice(advice_inputs) .expect("advice inputs should fit advice map limits"); @@ -57,7 +57,7 @@ fn test_op_advpopw() { ] .into(), ); - let advice_inputs = AdviceInputs::default().with_advice_stack(advice_stack); + let advice_inputs = AdviceInputs::default().with_stack(advice_stack); let mut processor = FastProcessor::new(StackInputs::default()) .with_advice(advice_inputs) .expect("advice inputs should fit advice map limits"); @@ -416,7 +416,7 @@ fn test_op_pipe() { .into(); let mut advice_stack = AdviceStack::new(); advice_stack.append_dword([advice_word1, advice_word2]); - let advice_inputs = AdviceInputs::default().with_advice_stack(advice_stack); + let advice_inputs = AdviceInputs::default().with_stack(advice_stack); let mut processor = FastProcessor::new(StackInputs::default()) .with_advice(advice_inputs) .expect("advice inputs should fit advice map limits"); diff --git a/processor/src/executor.rs b/processor/src/executor.rs new file mode 100644 index 0000000000..7d70056710 --- /dev/null +++ b/processor/src/executor.rs @@ -0,0 +1,180 @@ +use miden_mast_package::debug_info::{DebugSourceNodeId, PackageDebugInfo}; + +use crate::{ + ExecutionError, ExecutionOptions, ExecutionOutput, FastProcessor, FutureMaybeSend, Host, + Program, StackInputs, + advice::{AdviceError, AdviceInputs}, +}; + +// PROGRAM EXECUTOR +// ================================================================================================ + +/// A pluggable program executor used to run a [`Program`] against a [`Host`]. +/// +/// Defaults to [`FastProcessor`]. Alternative implementations can wrap execution in a debugger, +/// add instrumentation, or redirect to a different backend, while leaving the surrounding +/// executor wiring untouched. +pub trait ProgramExecutor { + /// Creates a new executor configured with the provided inputs and options. + /// + /// In generic code (`E: ProgramExecutor`) this resolves normally. For the concrete + /// [`FastProcessor`] type, however, the inherent + /// [`FastProcessor::new`](crate::FastProcessor::new) (which takes only stack inputs) + /// shadows this trait method by name, so invoke the trait constructor with fully-qualified + /// syntax: `::new(stack_inputs, advice_inputs, options)`. + fn new( + stack_inputs: StackInputs, + advice_inputs: AdviceInputs, + options: ExecutionOptions, + ) -> Result + where + Self: Sized; + + /// Configures package-owned source and debug information for execution. + fn with_debug_info(self, package_debug_info: PackageDebugInfo) -> Self; + + /// Configures the source node at which execution begins. + fn with_entrypoint_source_node(self, entrypoint_source_node: Option) + -> Self; + + /// Executes the provided program against the given host. + fn execute( + self, + program: &Program, + host: &mut H, + ) -> impl FutureMaybeSend>; +} + +impl ProgramExecutor for FastProcessor { + fn new( + stack_inputs: StackInputs, + advice_inputs: AdviceInputs, + options: ExecutionOptions, + ) -> Result { + FastProcessor::new_with_options(stack_inputs, advice_inputs, options) + } + + fn with_debug_info(mut self, package_debug_info: PackageDebugInfo) -> Self { + self.package_debug_info = Some(package_debug_info); + self + } + + fn with_entrypoint_source_node( + mut self, + entrypoint_source_node: Option, + ) -> Self { + self.entrypoint_source_node = entrypoint_source_node; + self + } + + fn execute( + self, + program: &Program, + host: &mut H, + ) -> impl FutureMaybeSend> { + async move { + match (self.package_debug_info.clone(), self.entrypoint_source_node) { + (Some(package_debug_info), Some(entrypoint_source_node)) => { + FastProcessor::execute_with_package_debug_info_at_source_node( + self, + program, + &package_debug_info, + entrypoint_source_node, + host, + ) + .await + }, + (Some(package_debug_info), None) => { + FastProcessor::execute_with_package_debug_info( + self, + program, + &package_debug_info, + host, + ) + .await + }, + (None, _) => FastProcessor::execute(self, program, host).await, + } + } + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_assembly::Assembler; + + use super::*; + use crate::{DefaultHost, StackInputs}; + + #[tokio::test(flavor = "current_thread")] + async fn program_executor_default_impl_runs_via_trait() { + let program = Assembler::default() + .assemble_program("program", "begin push.3 swap drop end") + .unwrap() + .unwrap_program(); + + // Drive execution entirely through the trait, defaulting to `FastProcessor`. + let processor = ::new( + StackInputs::default(), + AdviceInputs::default(), + ExecutionOptions::default(), + ) + .unwrap(); + let output = ::execute( + processor, + &program, + &mut DefaultHost::default(), + ) + .await + .unwrap(); + + // push.3 leaves 3 on top; `swap drop` restores the operand stack to its + // fixed depth of 16 so the program ends with a well-formed output stack. + assert_eq!(output.stack.get_element(0), Some(crate::Felt::from_u32(3))); + } + + #[test] + fn program_executor_reports_invalid_advice_inputs() { + let advice_inputs = + AdviceInputs::default().with_map([(crate::Word::default(), vec![crate::Felt::ONE])]); + let options = ExecutionOptions::default().with_max_adv_map_elements(0); + + let result = + ::new(StackInputs::default(), advice_inputs, options); + + assert!(result.is_err()); + } + + #[tokio::test(flavor = "current_thread")] + async fn program_executor_default_falls_back_when_no_source_node() { + let program = Assembler::default() + .assemble_program("program", "begin push.3 swap drop end") + .unwrap() + .unwrap_program(); + + // `execute_with_package_debug_info` overrides only to route to the package-debug path; + // without an entrypoint node it still executes and returns the same stack. + let processor = ::new( + StackInputs::default(), + AdviceInputs::default(), + ExecutionOptions::default(), + ) + .unwrap(); + let processor = ::with_debug_info( + processor, + PackageDebugInfo::default(), + ); + let output = ::execute( + processor, + &program, + &mut DefaultHost::default(), + ) + .await + .unwrap(); + + assert_eq!(output.stack.get_element(0), Some(crate::Felt::from_u32(3))); + } +} diff --git a/processor/src/fast/mod.rs b/processor/src/fast/mod.rs index 7bdb2bb98a..2403a5ebd6 100644 --- a/processor/src/fast/mod.rs +++ b/processor/src/fast/mod.rs @@ -9,7 +9,10 @@ use miden_core::{ program::{MIN_STACK_DEPTH, Program, StackInputs, StackOutputs}, utils::range, }; -use miden_mast_package::Package; +use miden_mast_package::{ + Package, + debug_info::{DebugSourceNodeId, PackageDebugInfo}, +}; use crate::{ AdviceInputs, AdviceProvider, ContextId, ExecutionError, ExecutionOptions, ProcessorState, @@ -146,6 +149,12 @@ pub struct FastProcessor { /// Deferred witness accumulated during execution and returned for verifier rehydration. deferred_state: DeferredState, + + /// Package debug information configured through [`ProgramExecutor`](crate::ProgramExecutor). + pub(crate) package_debug_info: Option, + + /// Entrypoint source node configured through [`ProgramExecutor`](crate::ProgramExecutor). + pub(crate) entrypoint_source_node: Option, } impl FastProcessor { @@ -284,6 +293,8 @@ impl FastProcessor { options.max_deferred_elements(), ) .map_err(AdviceError::DeferredStateInitializationFailed)?, + package_debug_info: None, + entrypoint_source_node: None, options, }) } diff --git a/processor/src/fast/tests/mod.rs b/processor/src/fast/tests/mod.rs index 0afa8c51e3..d35aacde05 100644 --- a/processor/src/fast/tests/mod.rs +++ b/processor/src/fast/tests/mod.rs @@ -32,7 +32,8 @@ use rstest::rstest; use super::*; use crate::{ - AdviceInputs, BaseHost, DefaultHost, LoadedMastForest, ProcessorState, SyncHost, + AdviceInputs, BaseHost, DefaultHost, LoadedMastForest, ProcessorState, ProgramExecutor, + SyncHost, advice::AdviceMutation, event::EventError, operation::OperationError, @@ -814,6 +815,52 @@ fn package_source_debug_execution_uses_manifest_entrypoint_source_node() { ); } +/// Covers the `Some(entrypoint_source_node)` arm of +/// [`ProgramExecutor::execute_with_package_debug_info`], so the trait-level dispatch to +/// [`FastProcessor::execute_with_package_debug_info_at_source_node`] is not left untested. It +/// mirrors [`package_source_debug_execution_uses_manifest_entrypoint_source_node`] but drives +/// execution through the trait instead of the inherent method. +#[tokio::test(flavor = "current_thread")] +async fn program_executor_routes_package_debug_to_entrypoint_source_node() { + let fixture = + same_digest_entrypoint_fixture(vec![Operation::Assert(Felt::from_u32(9))], "assert"); + assert!( + fixture + .debug_info + .unique_source_root_for_exec_node(fixture.program.entrypoint()) + .is_err(), + "debug info alone cannot pick the manifest-selected same-digest entrypoint" + ); + + let mut host = DefaultHost::default().with_source_manager(fixture.source_manager); + let processor = ::new( + StackInputs::default(), + AdviceInputs::default(), + ExecutionOptions::default(), + ) + .unwrap(); + let processor = + ::with_debug_info(processor, fixture.debug_info.clone()); + let processor = ::with_entrypoint_source_node( + processor, + Some(fixture.entrypoint_source_node_id), + ); + let err = ::execute(processor, &fixture.program, &mut host) + .await + .unwrap_err(); + + assert_matches!( + err, + ExecutionError::OperationError { + label, + source_file: Some(actual_source_file), + err: OperationError::FailedAssertion { err_code, .. }, + } if label == SourceSpan::new(fixture.source_file.id(), 9u32..17) + && actual_source_file.id() == fixture.source_file.id() + && err_code == Felt::from_u32(9) + ); +} + #[test] fn package_source_debug_trace_and_step_use_manifest_entrypoint_source_node() { let fixture = same_digest_entrypoint_fixture(vec![Operation::Add], "add"); diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 3436496702..71b63bd119 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -675,9 +675,9 @@ impl AdviceProvider { /// Extends the contents of this instance with the contents of an `AdviceInputs`. pub fn extend_from_inputs(&mut self, inputs: &AdviceInputs) -> Result<(), AdviceError> { - self.extend_advice_stack(inputs.advice_stack())?; - self.extend_merkle_store(inputs.store.inner_nodes())?; - self.extend_map(&inputs.map) + self.extend_advice_stack(inputs.stack())?; + self.extend_merkle_store(inputs.store().inner_nodes())?; + self.extend_map(inputs.map()) } /// Consumes `self` and return its parts (stack, map, store). @@ -791,7 +791,7 @@ mod tests { let mut mutation_stack = AdviceStack::new(); mutation_stack.append_elements([Felt::new_unchecked(1), Felt::new_unchecked(2)]); let mut provider = AdviceProvider::new( - AdviceInputs::default().with_advice_stack(initial_stack), + AdviceInputs::default().with_stack(initial_stack), &Default::default(), ) .unwrap(); diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 6ea781e913..bdf50e4653 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -21,6 +21,7 @@ mod continuation_stack; mod errors; mod execution; mod execution_options; +mod executor; mod fast; mod host; mod processor; @@ -34,7 +35,7 @@ use miden_core::{ use crate::{ advice::{AdviceInputs, AdviceProvider}, continuation_stack::ContinuationStack, - errors::{MapExecErr, MapExecErrNoCtx}, + errors::MapExecErr, processor::{Processor, SystemInterface}, trace::RowIndex, }; @@ -57,6 +58,7 @@ pub use errors::{ procedure_not_found_with_package_source_context, }; pub use execution_options::{ExecutionOptions, ExecutionOptionsError}; +pub use executor::ProgramExecutor; pub use fast::{BreakReason, ExecutionOutput, FastProcessor, ResumeContext}; pub use host::{ BaseHost, FutureMaybeSend, Host, LoadedMastForest, MastForestStore, MemMastForestStore, @@ -100,52 +102,6 @@ pub mod operation { pub mod trace; -// EXECUTORS -// ================================================================================================ - -/// Executes the provided program against the provided inputs and returns the resulting execution -/// output. -/// -/// The `host` parameter is used to provide the external environment to the program being executed, -/// such as access to the advice provider and libraries that the program depends on. -/// -/// # Errors -/// Returns an error if program execution fails for any reason. -#[tracing::instrument("execute_program", skip_all)] -pub async fn execute( - program: &Program, - stack_inputs: StackInputs, - advice_inputs: AdviceInputs, - host: &mut impl Host, - options: ExecutionOptions, -) -> Result { - let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, options) - .map_exec_err_no_ctx()?; - processor.execute(program, host).await -} - -/// Synchronous wrapper for the async `execute()` function. -/// -/// This method is only available on non-wasm32 targets. On wasm32, use the async `execute()` -/// method directly since wasm32 runs in the browser's event loop. -/// -/// # Panics -/// Panics if called from within an existing Tokio runtime. Use the async `execute()` method -/// instead in async contexts. -#[cfg(not(target_family = "wasm"))] -#[tracing::instrument("execute_program_sync", skip_all)] -pub fn execute_sync( - program: &Program, - stack_inputs: StackInputs, - advice_inputs: AdviceInputs, - host: &mut impl SyncHost, - options: ExecutionOptions, -) -> Result { - let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, options) - .map_exec_err_no_ctx()?; - processor.execute_sync(program, host) -} - // PROCESSOR STATE // =============================================================================================== diff --git a/processor/src/test_utils/non_tracing_test_host.rs b/processor/src/test_utils/non_tracing_test_host.rs index ec45bff81b..f557ea9fd3 100644 --- a/processor/src/test_utils/non_tracing_test_host.rs +++ b/processor/src/test_utils/non_tracing_test_host.rs @@ -71,7 +71,7 @@ mod tests { use miden_assembly::Assembler; use super::NonTracingTestHost; - use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs}; + use crate::{AdviceInputs, ExecutionOptions, FastProcessor, Program, StackInputs}; /// A host which does not implement `on_trace` should still execute trace events gracefully via /// the default no-op implementation, and trace events must not be routed to `on_event`. @@ -100,13 +100,13 @@ mod tests { .unwrap() .unwrap_program(); let mut host = NonTracingTestHost::default(); - crate::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .unwrap(); assert_eq!(host.events, vec![REGULAR_EVENT_ID_1, REGULAR_EVENT_ID_2]); diff --git a/processor/src/test_utils/test_host.rs b/processor/src/test_utils/test_host.rs index 9d51f23ccd..b38ae8ba09 100644 --- a/processor/src/test_utils/test_host.rs +++ b/processor/src/test_utils/test_host.rs @@ -211,7 +211,7 @@ mod tests { use miden_assembly::Assembler; use super::TestHost; - use crate::{AdviceInputs, ExecutionOptions, Program, StackInputs}; + use crate::{AdviceInputs, ExecutionOptions, FastProcessor, Program, StackInputs}; #[test] fn test_host_records_trace_and_snapshot() { @@ -234,13 +234,13 @@ mod tests { .unwrap() .unwrap_program(); let mut host = TestHost::default(); - crate::execute_sync( - &program, + FastProcessor::new_with_options( StackInputs::default(), AdviceInputs::default(), - &mut host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host) .unwrap(); // Each trace id is recorded, in emission order. diff --git a/processor/tests/advice_public_api.rs b/processor/tests/advice_public_api.rs index 62b19a149c..14c043fb0a 100644 --- a/processor/tests/advice_public_api.rs +++ b/processor/tests/advice_public_api.rs @@ -9,7 +9,7 @@ fn advice_stack_is_available_through_processor_advice_facade() { let value = Felt::new_unchecked(1); stack.append_element(value); - let advice_inputs = AdviceInputs::default().with_advice_stack(stack); + let advice_inputs = AdviceInputs::default().with_stack(stack); - assert_eq!(advice_inputs.advice_stack().into_elements(), vec![value]); + assert_eq!(advice_inputs.stack().into_elements(), vec![value]); } diff --git a/processor/tests/async_compat.rs b/processor/tests/async_compat.rs index 39706ce365..d556606a7e 100644 --- a/processor/tests/async_compat.rs +++ b/processor/tests/async_compat.rs @@ -91,25 +91,22 @@ async fn execute_async_matches_execute() { let advice_inputs = AdviceInputs::default(); let mut sync_host = DefaultHost::default(); - let sync_output = miden_processor::execute_sync( - &program, + let sync_output = FastProcessor::new_with_options( stack_inputs, advice_inputs.clone(), - &mut sync_host, ExecutionOptions::default(), ) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut sync_host) .unwrap(); let mut async_host = DefaultHost::default(); - let async_output = miden_processor::execute( - &program, - stack_inputs, - advice_inputs, - &mut async_host, - ExecutionOptions::default(), - ) - .await - .unwrap(); + let async_output = + FastProcessor::new_with_options(stack_inputs, advice_inputs, ExecutionOptions::default()) + .expect("failed to construct FastProcessor") + .execute(&program, &mut async_host) + .await + .unwrap(); assert_eq!(sync_output.stack, async_output.stack); } diff --git a/processor/tests/issue_2456_test.rs b/processor/tests/issue_2456_test.rs index 956ea2d130..d5f774d4ad 100644 --- a/processor/tests/issue_2456_test.rs +++ b/processor/tests/issue_2456_test.rs @@ -1,7 +1,9 @@ /// Test case for issue #2456: statically linked library calls should preserve valid MAST /// structure when copying nodes between forests. use miden_assembly::Assembler; -use miden_processor::{DefaultHost, ExecutionOptions, StackInputs, advice::AdviceInputs}; +use miden_processor::{ + DefaultHost, ExecutionOptions, FastProcessor, StackInputs, advice::AdviceInputs, +}; #[test] fn test_issue_2456_statically_linked_library_call() { @@ -47,7 +49,8 @@ fn test_issue_2456_statically_linked_library_call() { let mut host = DefaultHost::default(); let options = ExecutionOptions::default(); - let result = - miden_processor::execute_sync(&program, stack_inputs, advice_inputs, &mut host, options); + let result = FastProcessor::new_with_options(stack_inputs, advice_inputs, options) + .expect("failed to construct FastProcessor") + .execute_sync(&program, &mut host); assert!(result.is_ok(), "Execution should succeed but got error: {:?}", result.err()); } diff --git a/verifier/src/recursive/mod.rs b/verifier/src/recursive/mod.rs index 9a98841820..56291f1b1b 100644 --- a/verifier/src/recursive/mod.rs +++ b/verifier/src/recursive/mod.rs @@ -33,7 +33,7 @@ use miden_air::{ }; use miden_core::{ Felt, Word, - advice::AdviceInputs, + advice::{AdviceInputs, AdviceStack}, crypto::merkle::{MerklePath, MerkleStore, PartialMerkleTree}, deferred::{DEFAULT_MAX_DEFERRED_ELEMENTS, DeferredState, IntegrityError, TRUE_DIGEST}, field::QuadFelt, @@ -110,10 +110,9 @@ impl RecursiveVerifierInputs { /// `proof_request_key(verifier_root, claim_commitment)`, leaving the advice stack empty. fn into_request_package(mut self, verifier_root: Word) -> Self { let key = proof_request_key(verifier_root, self.claim_commitment); - let (proof_stream, map, store) = self.advice.into_parts(); - self.advice = AdviceInputs::default().with_merkle_store(store); - self.advice.map = map; - self.advice.map.insert(key, proof_stream.into_elements()); + let (proof_stream, mut map, store) = self.advice.into_parts(); + map.insert(key, proof_stream.into_elements()); + self.advice = AdviceInputs::new(AdviceStack::default(), map, store); self } } @@ -138,12 +137,14 @@ fn build_verifier_inputs( let mut inputs = build_from_proof_bytes(stark.bytes(), &pub_inputs, claim_commitment)?; // The MASM verifier authenticates this preimage against the caller-provided commitment. - inputs.advice.map.insert(claim_commitment, claim.to_elements().to_vec()); + inputs.advice = core::mem::take(&mut inputs.advice) + .with_map([(claim_commitment, claim.to_elements().to_vec())]); let kernel = claim.kernel(); // The MASM verifier derives the procedure count from the value length. let kernel_witness = Word::words_as_elements(kernel.proc_hashes()).to_vec(); - inputs.advice.map.insert(kernel.commitment(), kernel_witness); + inputs.advice = + core::mem::take(&mut inputs.advice).with_map([(kernel.commitment(), kernel_witness)]); Ok(inputs) } @@ -316,7 +317,7 @@ fn build_advice( let (store, advice_map) = build_merkle_data(config, stark, &heights.proof_order)?; let advice = AdviceInputs::default() - .with_advice_stack(advice_stack.into()) + .with_stack(advice_stack.into()) .with_map(advice_map) .with_merkle_store(store); @@ -539,25 +540,22 @@ mod tests { let store: MerkleStore = [merkle_node].into_iter().collect(); let advice = AdviceInputs::default() - .with_advice_stack(proof_stream.clone().into()) + .with_stack(proof_stream.clone().into()) .with_map([query_entry.clone()]) .with_merkle_store(store.clone()); let inputs = RecursiveVerifierInputs { advice, claim_commitment }; let package = inputs.into_request_package(verifier_root); - assert!( - package.advice().advice_stack().is_empty(), - "the proof must leave the advice stack" - ); + assert!(package.advice().stack().is_empty(), "the proof must leave the advice stack"); assert_eq!(package.claim_commitment(), claim_commitment); - assert_eq!(&package.advice().store, &store); - assert_eq!(package.advice().map.len(), 2, "existing entries stay, proof entry added"); - assert_eq!(package.advice().map.get(&query_entry.0).unwrap().as_ref(), query_entry.1); + assert_eq!(package.advice().store(), &store); + assert_eq!(package.advice().map().len(), 2, "existing entries stay, proof entry added"); + assert_eq!(package.advice().map().get(&query_entry.0).unwrap().as_ref(), query_entry.1); assert_eq!( package .advice() - .map + .map() .get(&proof_request_key(verifier_root, claim_commitment)) .unwrap() .as_ref(), From 36a578e04e90c9e08df0b55b5f02f76252f27580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:49:26 -0400 Subject: [PATCH 08/17] fix: require Plonky3 0.6.3 (#3569) * fix: require Plonky3 0.6.3 * docs: update changelog for Plonky3 minimum --- CHANGELOG.md | 2 ++ Cargo.toml | 44 ++++++++++++------------- tools/miden-serde-utils-fuzz/Cargo.toml | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1412e939da..a3e7ec0e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ #### Changes - [BREAKING] Reduced the precompile STARK relation from 12 AIRs to 10 by merging the chunk/node/sponge and EC point/group stores ([#3464](https://github.com/0xMiden/miden-vm/pull/3464)). +- Raised the minimum supported Plonky3 version to 0.6.3 to match the `num-bigint` 0.5 types used by `miden-field` ([#3569](https://github.com/0xMiden/miden-vm/pull/3569)). + #### Features - Added `AdviceInputs::new` constructor and `From` impl, complementing the existing builder-style accessors for assembling advice inputs from their parts ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). diff --git a/Cargo.toml b/Cargo.toml index e14cc4709b..dc70c28586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,28 +174,28 @@ windows-sys = { version = "0.61.2" } x25519-dalek = { version = "3.0.0", default-features = false } zerocopy = { version = "0.8", default-features = false, features = ["derive"] } -p3-air = { version = "0.6", default-features = false } -p3-batch-stark = { version = "0.6", default-features = false } -p3-blake3 = { version = "0.6", default-features = false } -p3-blake3-air = { version = "0.6", default-features = false } -p3-bn254 = { version = "0.6", default-features = false } -p3-challenger = { version = "0.6", default-features = false } -p3-commit = { version = "0.6", default-features = false } -p3-dft = { version = "0.6", default-features = false } -p3-field = { version = "0.6", default-features = false } -p3-fri = { version = "0.6", default-features = false } -p3-goldilocks = { version = "0.6", default-features = false } -p3-keccak = { version = "0.6", default-features = false } -p3-keccak-air = { version = "0.6", default-features = false } -p3-lookup = { version = "0.6", default-features = false } -p3-matrix = { version = "0.6", default-features = false } -p3-maybe-rayon = { version = "0.6", default-features = false } -p3-merkle-tree = { version = "0.6", default-features = false } -p3-mersenne-31 = { version = "0.6", default-features = false } -p3-poseidon2-air = { version = "0.6", default-features = false } -p3-symmetric = { version = "0.6", default-features = false } -p3-uni-stark = { version = "0.6", default-features = false } -p3-util = { version = "0.6", default-features = false } +p3-air = { version = "0.6.3", default-features = false } +p3-batch-stark = { version = "0.6.3", default-features = false } +p3-blake3 = { version = "0.6.3", default-features = false } +p3-blake3-air = { version = "0.6.3", default-features = false } +p3-bn254 = { version = "0.6.3", default-features = false } +p3-challenger = { version = "0.6.3", default-features = false } +p3-commit = { version = "0.6.3", default-features = false } +p3-dft = { version = "0.6.3", default-features = false } +p3-field = { version = "0.6.3", default-features = false } +p3-fri = { version = "0.6.3", default-features = false } +p3-goldilocks = { version = "0.6.3", default-features = false } +p3-keccak = { version = "0.6.3", default-features = false } +p3-keccak-air = { version = "0.6.3", default-features = false } +p3-lookup = { version = "0.6.3", default-features = false } +p3-matrix = { version = "0.6.3", default-features = false } +p3-maybe-rayon = { version = "0.6.3", default-features = false } +p3-merkle-tree = { version = "0.6.3", default-features = false } +p3-mersenne-31 = { version = "0.6.3", default-features = false } +p3-poseidon2-air = { version = "0.6.3", default-features = false } +p3-symmetric = { version = "0.6.3", default-features = false } +p3-uni-stark = { version = "0.6.3", default-features = false } +p3-util = { version = "0.6.3", default-features = false } loom = "0.7" miette = { package = "miden-miette", version = "8.0", default-features = false } diff --git a/tools/miden-serde-utils-fuzz/Cargo.toml b/tools/miden-serde-utils-fuzz/Cargo.toml index 33bd2b29c6..d31e8a091f 100644 --- a/tools/miden-serde-utils-fuzz/Cargo.toml +++ b/tools/miden-serde-utils-fuzz/Cargo.toml @@ -11,7 +11,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" -p3-goldilocks = { default-features = false, version = "0.6" } +p3-goldilocks = { default-features = false, version = "0.6.3" } [dependencies.miden-serde-utils] path = "../../crates/serde-utils" From 2d08ea71d3342658c29f9ce7b108070af5b53411 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 10 Aug 2026 20:32:00 +0200 Subject: [PATCH 09/17] chore: take proof by reference in `verify` (#3469) * chore: take proof by reference in `verify` * chore: address review comments --- CHANGELOG.md | 2 ++ benches/blake3-bench/src/lib.rs | 2 +- .../benches/synthetic_bench.rs | 19 ++++++------------ .../benches/precompiles_bench/support.rs | 2 +- crates/test-utils/src/lib.rs | 4 ++-- miden-vm/README.md | 6 +++--- miden-vm/src/cli/verify.rs | 2 +- miden-vm/src/lib.rs | 2 +- miden-vm/tests/integration/prove_verify.rs | 20 +++++++++---------- processor/src/host/advice/mod.rs | 8 ++++---- processor/src/host/mod.rs | 12 +++++------ verifier/README.md | 2 +- verifier/src/lib.rs | 12 +++++------ 13 files changed, 44 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e7ec0e30..99e3c8f85d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ #### Changes - [BREAKING] Removed the free `execute()` and `execute_sync()` functions from `miden-vm`/`miden-processor`. Use `FastProcessor::new_with_options(...)` followed by `execute()`/`execute_sync()` instead ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). +- [BREAKING] `verify`, `Verifier::verify`, and `Verifier::verify_partial` now borrow the proof and the claim instead of consuming them. +- [BREAKING] Renamed the `AdviceMutation::ExtendMap` field `other` to `map` and the `AdviceMutation::ExtendMerkleStore` field `infos` to `inner_nodes`. ## v0.29.0 (2026-08-04) diff --git a/benches/blake3-bench/src/lib.rs b/benches/blake3-bench/src/lib.rs index 56919a90f2..dda09bf244 100644 --- a/benches/blake3-bench/src/lib.rs +++ b/benches/blake3-bench/src/lib.rs @@ -165,7 +165,7 @@ pub fn prove_and_verify_once(fixture: &Blake3Fixture) { stack_outputs, ); Verifier::new() - .verify(proof, claim) + .verify(&proof, &claim) .expect("failed to verify Blake3 benchmark proof"); } diff --git a/benches/synthetic-bench/benches/synthetic_bench.rs b/benches/synthetic-bench/benches/synthetic_bench.rs index 06602c20d4..445c99ee50 100644 --- a/benches/synthetic-bench/benches/synthetic_bench.rs +++ b/benches/synthetic-bench/benches/synthetic_bench.rs @@ -352,7 +352,6 @@ fn bench_one_scenario( if axes.contains("verify") { // Reuse a proof from the `prove` axis when it ran for this scenario. This keeps prove time // out of the verify measurement without forcing an extra proof in all-axes runs. - let program_info = ProgramInfo::from(program.clone()); let (stack_outputs, proof) = cached_proof.borrow().clone().unwrap_or_else(|| { let mut host = DefaultHost::default(); prove_sync( @@ -365,19 +364,13 @@ fn bench_one_scenario( ) .expect("prove for verify setup") }); + let claim = ExecutionClaim::from_program_info( + ProgramInfo::from(program.clone()), + StackInputs::default(), + stack_outputs, + ); group.bench_function("verify", |b| { - b.iter_batched( - || (program_info.clone(), StackInputs::default(), stack_outputs, proof.clone()), - |(program_info, stack_inputs, stack_outputs, proof)| { - let claim = ExecutionClaim::from_program_info( - program_info, - stack_inputs, - stack_outputs, - ); - black_box(Verifier::new().verify(proof, claim).expect("verify")); - }, - BatchSize::SmallInput, - ); + b.iter(|| black_box(Verifier::new().verify(&proof, &claim).expect("verify"))); }); } diff --git a/crates/precompiles/benches/precompiles_bench/support.rs b/crates/precompiles/benches/precompiles_bench/support.rs index 9b6223633f..c65ca15ae3 100644 --- a/crates/precompiles/benches/precompiles_bench/support.rs +++ b/crates/precompiles/benches/precompiles_bench/support.rs @@ -113,6 +113,6 @@ pub fn verify_once( stack_outputs, ); Verifier::new() - .verify(proof, claim) + .verify(&proof, &claim) .expect("failed to verify precompile benchmark proof"); } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index e73ef0d6d3..00a9317a1c 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -696,11 +696,11 @@ impl Test { StackOutputs::new(&elements).expect("stack outputs should fit the VM stack"); let claim = ExecutionClaim::from_program_info(program_info, stack_inputs, stack_outputs); - assert!(verify(proof, claim).is_err()); + assert!(verify(&proof, &claim).is_err()); } else { let claim = ExecutionClaim::from_program_info(program_info, stack_inputs, stack_outputs); - let result = verify(proof, claim); + let result = verify(&proof, &claim); assert!(result.is_ok(), "error: {result:?}"); } } diff --git a/miden-vm/README.md b/miden-vm/README.md index 272eec8366..b287346a61 100644 --- a/miden-vm/README.md +++ b/miden-vm/README.md @@ -148,8 +148,8 @@ assert_eq!(8, outputs.first().unwrap().as_canonical_u64()); To verify program execution, use `Verifier::new().verify(...)`. The verifier takes the following parameters: -- `proof: ExecutionProof` is the proof generated during program execution. -- `claim: ExecutionClaim` contains the claimed program information, stack inputs, and stack outputs. +- `proof: &ExecutionProof` is the proof generated during program execution. +- `claim: &ExecutionClaim` contains the claimed program information, stack inputs, and stack outputs. Stack inputs are expected to be ordered as if they would be pushed onto the stack one by one. Thus, their expected order on the stack will be the reverse of the order in which they are provided, and the last value in the `stack_inputs` is expected to be the value at the top of the stack. @@ -178,7 +178,7 @@ let claim = ExecutionClaim::from_program_info( ); // Verify the execution claim. -match Verifier::new().verify(proof, claim) { +match Verifier::new().verify(&proof, &claim) { Ok(_) => println!("Execution verified!"), Err(err) => eprintln!("Verification failed: {err}"), } diff --git a/miden-vm/src/cli/verify.rs b/miden-vm/src/cli/verify.rs index 85d6133b2a..05bdaad03a 100644 --- a/miden-vm/src/cli/verify.rs +++ b/miden-vm/src/cli/verify.rs @@ -77,7 +77,7 @@ impl VerifyCmd { let stack_outputs = outputs_data.stack_outputs().map_err(Report::msg)?; let claim = miden_vm::ExecutionClaim::from_program_info(program_info, stack_inputs, stack_outputs); - miden_vm::verify(proof, claim) + miden_vm::verify(&proof, &claim) .into_diagnostic() .wrap_err("Program failed verification!")?; diff --git a/miden-vm/src/lib.rs b/miden-vm/src/lib.rs index 4518115fa3..30e7bd595d 100644 --- a/miden-vm/src/lib.rs +++ b/miden-vm/src/lib.rs @@ -34,6 +34,6 @@ pub mod internal; /// /// Wire-backed deferred proofs are partial/delegable proof material and are rejected here; use /// [`Verifier::verify_partial`] to verify and hydrate wire-backed partial proofs. -pub fn verify(proof: ExecutionProof, claim: ExecutionClaim) -> Result { +pub fn verify(proof: &ExecutionProof, claim: &ExecutionClaim) -> Result { miden_verifier::verify(proof, claim) } diff --git a/miden-vm/tests/integration/prove_verify.rs b/miden-vm/tests/integration/prove_verify.rs index dcd75b4752..7bd17c9993 100644 --- a/miden-vm/tests/integration/prove_verify.rs +++ b/miden-vm/tests/integration/prove_verify.rs @@ -56,7 +56,7 @@ fn assert_prove_verify( println!("Verifying proof..."); let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs); - let security_level = verify(proof, claim).expect("Verification failed"); + let security_level = verify(&proof, &claim).expect("Verification failed"); println!("Verification successful! Security level: {security_level}"); } @@ -338,7 +338,7 @@ mod fast_parallel { // Verify the proof let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, fast_stack_outputs); - verify(proof, claim).expect("Verification failed"); + verify(&proof, &claim).expect("Verification failed"); } #[test] @@ -368,7 +368,7 @@ mod fast_parallel { .expect("prove_from_trace_sync failed"); let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs); - verify(proof, claim).expect("Verification failed"); + verify(&proof, &claim).expect("Verification failed"); } #[test] @@ -401,7 +401,7 @@ mod fast_parallel { assert_eq!(proof.deferred_proof().as_wire(), Some(&expected_wire)); let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs); let (_, pending) = miden_verifier::Verifier::new() - .verify_partial(proof, claim) + .verify_partial(&proof, &claim) .expect("partial verification failed"); assert_ne!(pending.root(), miden_core::deferred::TRUE_DIGEST); let _state = pending.into_state(); @@ -463,18 +463,18 @@ fn prove_partial_fixture() -> (ExecutionClaim, ExecutionProof) { fn test_partial_obligation_flow() { // the default prover emits final deferred material: `verify` accepts it directly let (claim, proof) = prove_fixture(); - verify(proof, claim).expect("final verification should pass"); + verify(&proof, &claim).expect("final verification should pass"); // a partial (wire-backed) package is refused by final verification... let (claim, partial) = prove_partial_fixture(); assert!(matches!( - verify(partial.clone(), claim.clone()), + verify(&partial, &claim), Err(VerificationError::UnsupportedDeferredProof) )); // ...and verified by the partial path, which returns the linear obligation let (_, pending) = Verifier::new() - .verify_partial(partial, claim) + .verify_partial(&partial, &claim) .expect("partial verification should pass"); assert_eq!(pending.root(), miden_core::deferred::TRUE_DIGEST); let _state = pending.into_state(); @@ -509,7 +509,7 @@ fn test_deferred_stark_proof_requires_exact_encoding_and_bound_root() { .expect("Proving failed"); let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs); - verify(proof.clone(), claim.clone()).expect("untampered deferred proof should verify"); + verify(&proof, &claim).expect("untampered deferred proof should verify"); // The proof encoding is exact: an otherwise-valid proof with a trailing byte is rejected. let stark = proof.miden_proof(); @@ -519,12 +519,12 @@ fn test_deferred_stark_proof_requires_exact_encoding_and_bound_root() { StarkProof::new(proof_bytes, stark.hash_fn()), proof.deferred_proof().clone(), ); - verify(trailing, claim.clone()).expect_err("trailing proof bytes must be rejected"); + verify(&trailing, &claim).expect_err("trailing proof bytes must be rejected"); // The deferred root is statement-bound: replacing it with TRUE must fail. let tampered = ExecutionProof::new( StarkProof::new(stark.bytes().to_vec(), stark.hash_fn()), DeferredProof::empty(), ); - assert!(verify(tampered, claim).is_err()); + assert!(verify(&tampered, &claim).is_err()); } diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 71b63bd119..ba56e22d5e 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -185,11 +185,11 @@ impl AdviceProvider { AdviceMutation::ExtendStack { stack } => { self.extend_advice_stack(stack)?; }, - AdviceMutation::ExtendMap { other } => { - self.extend_map(&other)?; + AdviceMutation::ExtendMap { map } => { + self.extend_map(&map)?; }, - AdviceMutation::ExtendMerkleStore { infos } => { - self.extend_merkle_store(infos)?; + AdviceMutation::ExtendMerkleStore { inner_nodes } => { + self.extend_merkle_store(inner_nodes)?; }, } Ok(()) diff --git a/processor/src/host/mod.rs b/processor/src/host/mod.rs index d78b225d2a..a7a1b2eb55 100644 --- a/processor/src/host/mod.rs +++ b/processor/src/host/mod.rs @@ -30,8 +30,8 @@ pub use mast_forest_store::{LoadedMastForest, MastForestStore, MemMastForestStor #[derive(Debug, PartialEq, Eq)] pub enum AdviceMutation { ExtendStack { stack: AdviceStack }, - ExtendMap { other: AdviceMap }, - ExtendMerkleStore { infos: Vec }, + ExtendMap { map: AdviceMap }, + ExtendMerkleStore { inner_nodes: Vec }, } impl AdviceMutation { @@ -39,12 +39,12 @@ impl AdviceMutation { Self::ExtendStack { stack } } - pub fn extend_map(other: AdviceMap) -> Self { - Self::ExtendMap { other } + pub fn extend_map(map: AdviceMap) -> Self { + Self::ExtendMap { map } } - pub fn extend_merkle_store(infos: impl IntoIterator) -> Self { - Self::ExtendMerkleStore { infos: Vec::from_iter(infos) } + pub fn extend_merkle_store(inner_nodes: impl IntoIterator) -> Self { + Self::ExtendMerkleStore { inner_nodes: Vec::from_iter(inner_nodes) } } } // HOST TRAIT diff --git a/verifier/README.md b/verifier/README.md index ae8d762b57..852fe03b80 100644 --- a/verifier/README.md +++ b/verifier/README.md @@ -5,7 +5,7 @@ While [Miden](../miden-vm) crate also contains verifier functionality, if a proj ## Usage -Use `verify(proof, claim)` to verify a final `ExecutionProof`. The `ExecutionClaim` contains the +Use `verify(&proof, &claim)` to verify a final `ExecutionProof`. The `ExecutionClaim` contains the program information and public stack inputs and outputs. The function returns the proof's security level, or a `VerificationError` if verification fails. diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index 524077f5ad..12d4d87527 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -90,8 +90,8 @@ impl Verifier { /// public root. pub fn verify( &self, - proof: ExecutionProof, - claim: ExecutionClaim, + proof: &ExecutionProof, + claim: &ExecutionClaim, ) -> Result { let miden_security_level = proof.security_level(); let (final_deferred_root, precompile_security_level) = @@ -122,8 +122,8 @@ impl Verifier { /// deferred root. pub fn verify_partial( &self, - proof: ExecutionProof, - claim: ExecutionClaim, + proof: &ExecutionProof, + claim: &ExecutionClaim, ) -> Result<(u32, Unsettled), VerificationError> { let security_level = proof.security_level(); let deferred_state = @@ -167,7 +167,7 @@ impl Unsettled { /// - The provided proof does not prove a correct execution of the claim. /// - The proof carries wire-backed deferred proof material, which is a partial/delegable form. /// - The proof's STARK-backed deferred proof, if present, does not verify against its public root. -pub fn verify(proof: ExecutionProof, claim: ExecutionClaim) -> Result { +pub fn verify(proof: &ExecutionProof, claim: &ExecutionClaim) -> Result { Verifier::default().verify(proof, claim) } @@ -212,7 +212,7 @@ fn stark_security_level(_proof: &StarkProof) -> u32 { } fn verify_stark( - claim: ExecutionClaim, + claim: &ExecutionClaim, final_deferred_root: Word, stark_proof: &StarkProof, ) -> Result<(), VerificationError> { From f5f1aa4e966d48cba40c7ed5cdf8d9691633f4fb Mon Sep 17 00:00:00 2001 From: serhat dolmaci Date: Mon, 10 Aug 2026 21:50:38 +0300 Subject: [PATCH 10/17] fix(crypto): add missing return in Polynomial::div for zero numerator (#3573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Polynomial::div discarded the result of Self::zero() when the numerator was zero because the return keyword was missing. Execution fell through to remainder.degree().unwrap() which panics on a zero polynomial. Add the missing return and a regression test. Closes #3534 Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ .../src/dsa/falcon512_poseidon2/math/polynomial.rs | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99e3c8f85d..84a9bd2439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ - [BREAKING] `verify`, `Verifier::verify`, and `Verifier::verify_partial` now borrow the proof and the claim instead of consuming them. - [BREAKING] Renamed the `AdviceMutation::ExtendMap` field `other` to `map` and the `AdviceMutation::ExtendMerkleStore` field `infos` to `inner_nodes`. +#### Fixes + +- Fixed `Polynomial::div` panicking when the numerator is zero by adding the missing `return` keyword ([#3534](https://github.com/0xMiden/miden-vm/issues/3534)). + ## v0.29.0 (2026-08-04) #### Changes diff --git a/crates/crypto/src/dsa/falcon512_poseidon2/math/polynomial.rs b/crates/crypto/src/dsa/falcon512_poseidon2/math/polynomial.rs index 471d074884..1f193c07ae 100644 --- a/crates/crypto/src/dsa/falcon512_poseidon2/math/polynomial.rs +++ b/crates/crypto/src/dsa/falcon512_poseidon2/math/polynomial.rs @@ -451,7 +451,7 @@ where panic!(); } if self.is_zero() { - Self::zero(); + return Self::zero(); } let mut remainder = self; let mut quotient = Polynomial::::zero(); @@ -653,6 +653,15 @@ mod tests { use super::{FalconFelt, N, Polynomial}; use crate::rand::test_utils::prng_array; + #[test] + fn div_zero_by_nonzero_returns_zero() { + use num::Zero; + let zero = Polynomial::::zero(); + let nonzero = Polynomial::new(vec![1, 2, 3]); + let result = zero / nonzero; + assert!(result.is_zero()); + } + #[test] fn test_negacyclic_reduction() { let coef1: [u8; N] = prng_array([0u8; 32]); From 8c461c3d5461607484846d5a04b6f782861f1bec Mon Sep 17 00:00:00 2001 From: Yusuf Can <135010814+Dusk1e@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:39:10 +0300 Subject: [PATCH 11/17] fix(crypto): validate LeafIndex depth on deserialization (#3560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(crypto): validate LeafIndex depth on deserialization `LeafIndex` guarantees, through both `new` and `TryFrom`, that the wrapped `NodeIndex` has depth `DEPTH` and a position addressable at that depth. The `Deserializable` impl built the struct straight from the decoded `NodeIndex` and enforced none of it, so a `LeafIndex<64>` could be decoded while carrying a depth-3 index. `NodeIndex::read_from` cannot catch this on its own, because `NodeIndex` has no knowledge of `DEPTH`. `read_from` now routes through `TryFrom` and maps the failure to `DeserializationError::InvalidValue`, keeping both construction paths on the same invariant. * fix(crypto): remove serde derives that bypass LeafIndex validation `LeafIndex` derived `serde::Deserialize`, which reconstructs the value from its fields without going through `TryFrom`, so it accepted the same depth-mismatched and below-minimum-depth indices that the `Deserializable` fix rejects. Removing the derive from `LeafIndex` cascades to the serde-deriving types that transitively contain it - `SmtLeaf`, `Smt`, and `PartialSmt` - so their derives are removed as well. This is the miden-crypto slice of the wider serde removal tracked in #3572. --------- Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com> --- CHANGELOG.md | 1 + crates/crypto/src/merkle/smt/full/leaf.rs | 1 - crates/crypto/src/merkle/smt/full/mod.rs | 1 - crates/crypto/src/merkle/smt/mod.rs | 44 +++++++++++++++++++-- crates/crypto/src/merkle/smt/partial/mod.rs | 1 - 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a9bd2439..a83f485742 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ #### Fixes +- [BREAKING] Validated `LeafIndex` on deserialization: `LeafIndex::read_from()` now routes through `TryFrom`, and the bypassing `serde` derives were removed from `LeafIndex`, `SmtLeaf`, `Smt`, and `PartialSmt` ([#3559](https://github.com/0xMiden/miden-vm/issues/3559)). - Fixed `Polynomial::div` panicking when the numerator is zero by adding the missing `return` keyword ([#3534](https://github.com/0xMiden/miden-vm/issues/3534)). ## v0.29.0 (2026-08-04) diff --git a/crates/crypto/src/merkle/smt/full/leaf.rs b/crates/crypto/src/merkle/smt/full/leaf.rs index de2018ef22..1c35b7c94b 100644 --- a/crates/crypto/src/merkle/smt/full/leaf.rs +++ b/crates/crypto/src/merkle/smt/full/leaf.rs @@ -15,7 +15,6 @@ const DOUBLE_WORD_LEN: usize = 8; /// /// A leaf can be empty, hold a single key-value pair, or multiple key-value pairs. #[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum SmtLeaf { /// An empty leaf at the specified index. Empty(LeafIndex), diff --git a/crates/crypto/src/merkle/smt/full/mod.rs b/crates/crypto/src/merkle/smt/full/mod.rs index 870efb6711..40cc97e717 100644 --- a/crates/crypto/src/merkle/smt/full/mod.rs +++ b/crates/crypto/src/merkle/smt/full/mod.rs @@ -97,7 +97,6 @@ type Leaves = super::Leaves; /// hash = H(key₁, value₁, key₂, value₂, ...) /// ``` #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Smt { root: Word, num_entries: usize, diff --git a/crates/crypto/src/merkle/smt/mod.rs b/crates/crypto/src/merkle/smt/mod.rs index 05616caa76..0be6c31c54 100644 --- a/crates/crypto/src/merkle/smt/mod.rs +++ b/crates/crypto/src/merkle/smt/mod.rs @@ -1,6 +1,6 @@ //! Sparse Merkle Tree (SMT) data structures. -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; use core::{ fmt::{self, Display}, hash::Hash, @@ -593,7 +593,6 @@ impl InnerNode { /// The index of a leaf, at a depth known at compile-time. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct LeafIndex { index: NodeIndex, } @@ -656,7 +655,11 @@ impl Serializable for LeafIndex { impl Deserializable for LeafIndex { fn read_from(source: &mut R) -> Result { - Ok(Self { index: source.read()? }) + // A `NodeIndex` is valid on its own terms without knowing `DEPTH`, so route through + // `TryFrom` to enforce that its depth matches this type's `DEPTH`. + let index: NodeIndex = source.read()?; + + Self::try_from(index).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -861,3 +864,38 @@ impl De }) } } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::{LeafIndex, NodeIndex, SMT_MAX_DEPTH}; + use crate::utils::{Deserializable, Serializable}; + + #[test] + fn leaf_index_read_from_rejects_depth_mismatch() { + // A depth-3 index is valid on its own terms, but wrong for a `LeafIndex`. + let mismatched = NodeIndex::new(3, 5).unwrap(); + assert!(LeafIndex::::try_from(mismatched).is_err()); + + assert!(LeafIndex::::read_from_bytes(&mismatched.to_bytes()).is_err()); + } + + #[test] + fn leaf_index_read_from_rejects_depth_below_minimum() { + assert!(LeafIndex::<0>::new(0).is_err()); + + let root = NodeIndex::new(0, 0).unwrap(); + assert!(LeafIndex::<0>::read_from_bytes(&root.to_bytes()).is_err()); + } + + #[test] + fn leaf_index_round_trips_at_matching_depth() { + let leaf = LeafIndex::::new(5).unwrap(); + + let decoded = LeafIndex::::read_from_bytes(&leaf.to_bytes()).unwrap(); + + assert_eq!(leaf, decoded); + } +} diff --git a/crates/crypto/src/merkle/smt/partial/mod.rs b/crates/crypto/src/merkle/smt/partial/mod.rs index cd2e6ae51c..1154d607c1 100644 --- a/crates/crypto/src/merkle/smt/partial/mod.rs +++ b/crates/crypto/src/merkle/smt/partial/mod.rs @@ -39,7 +39,6 @@ pub use serialization::{NodeValue, UniqueNodes}; /// Once a partial SMT has been constructed, its root is set in stone. All subsequently added proofs /// or merkle paths must match that root, otherwise an error is returned. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct PartialSmt { root: Word, num_entries: usize, From bfbcac990f522924ecd94501ecbf35034b4c70b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 15:52:27 -0400 Subject: [PATCH 12/17] build(deps): bump the github-actions group with 4 updates (#3565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 4 updates: [taiki-e/install-action](https://github.com/taiki-e/install-action), [CodSpeedHQ/action](https://github.com/codspeedhq/action), [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action). Updates `taiki-e/install-action` from 2.85.2 to 2.85.7 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/41049aa56687c35e0afa74eed4f09cec4f9afabf...67729d5c413db75907f0ad1e39bb04b9c868ff60) Updates `CodSpeedHQ/action` from 4.19.1 to 5.0.2 - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/f22792bfac16f3e14eb9fbea76f4a48e9cc22b93...0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1) Updates `peter-evans/repository-dispatch` from 831550845b9842397c9b90c366a610d53f2b846b to 73ca9d9f4f806f6470bf0a2c4245a7d14df16b37 - [Release notes](https://github.com/peter-evans/repository-dispatch/releases) - [Commits](https://github.com/peter-evans/repository-dispatch/compare/831550845b9842397c9b90c366a610d53f2b846b...73ca9d9f4f806f6470bf0a2c4245a7d14df16b37) Updates `zizmorcore/zizmor-action` from 0.6.1 to 0.6.2 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/6fc4b006235f201fdab3722e17240ab420d580e5...3dc1ecc9bcb9e94e9b2c709687979e1298497054) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.85.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: CodSpeedHQ/action dependency-version: 5.0.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: peter-evans/repository-dispatch dependency-version: 73ca9d9f4f806f6470bf0a2c4245a7d14df16b37 dependency-type: direct:production dependency-group: github-actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/codspeed.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 6 +++--- .github/workflows/trigger-deploy-docs.yml | 2 +- .github/workflows/zizmor.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48f069763f..76ae84c10b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: rustup toolchain install --no-self-update rustup target add wasm32-wasip1 - name: Install cargo-binstall - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2.85.2 + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 with: tool: cargo-binstall - name: Install wasmtime diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index d0a8c8416f..6e2bba450e 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -67,7 +67,7 @@ jobs: cargo codspeed build --measurement-mode walltime --profile optimized -p ${{ matrix.package }} --bench ${{ matrix.bench }} - name: Run benchmarks - uses: CodSpeedHQ/action@f22792bfac16f3e14eb9fbea76f4a48e9cc22b93 # v4.19.1 + uses: CodSpeedHQ/action@0ca9cbbf4623b599a6c3ed4fc8a922942705d9f1 # v5.0.2 with: mode: walltime run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3fb6831382..a19183ece8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -48,7 +48,7 @@ jobs: # install-action commit and set tool explicitly, or zizmor's # impostor-commit audit can flag stale shortcut-tag hashes. - name: Install cargo-binstall - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2.85.2 + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 with: tool: cargo-binstall - name: Install workspace inheritance checker diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a9a359e17..0819d33bda 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: # install-action commit and set tool explicitly, or zizmor's # impostor-commit audit can flag stale shortcut-tag hashes. - name: Install nextest - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2.85.2 + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 with: tool: cargo-nextest - uses: WarpBuilds/rust-cache@9d0cc3090d9c87de74ea67617b246e978735b1a1 # v2.9.1 @@ -47,7 +47,7 @@ jobs: - name: Cleanup large tools for build space uses: ./.github/actions/cleanup-runner - name: Install nextest - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2.85.2 + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 with: tool: cargo-nextest - uses: WarpBuilds/rust-cache@9d0cc3090d9c87de74ea67617b246e978735b1a1 # v2.9.1 @@ -166,7 +166,7 @@ jobs: # Keep this on a released install-action commit with an explicit tool; see # the nextest install above for the zizmor/tool-shortcut rationale. - name: Install cargo-hack - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2.85.2 + uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 with: tool: cargo-hack - name: Check all feature combinations diff --git a/.github/workflows/trigger-deploy-docs.yml b/.github/workflows/trigger-deploy-docs.yml index 0e3b622111..965c0c81be 100644 --- a/.github/workflows/trigger-deploy-docs.yml +++ b/.github/workflows/trigger-deploy-docs.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Send repository_dispatch to aggregator - uses: peter-evans/repository-dispatch@831550845b9842397c9b90c366a610d53f2b846b + uses: peter-evans/repository-dispatch@73ca9d9f4f806f6470bf0a2c4245a7d14df16b37 with: # PAT (Personal Access Token) that grants permission to trigger the rebuild workflow at the docs repository token: ${{ secrets.DOCS_REPO_TOKEN }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index b9c76a4f86..a5339e4fc2 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -47,7 +47,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1 + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 with: advanced-security: false config: zizmor.yml From dfff228049ed5c217d283d00dd8a526aeec21d51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:57:39 -0400 Subject: [PATCH 13/17] build(deps): bump proptest-derive in / (#3566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [proptest-derive](https://github.com/proptest-rs/proptest) in `/` from 0.7.0 to 0.8.0. Updates `proptest-derive` from 0.7.0 to 0.8.0 - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md) - [Commits](https://github.com/proptest-rs/proptest/compare/proptest-derive-0.7.0...proptest-derive-0.8.0) --- updated-dependencies: - dependency-name: proptest-derive dependency-version: 0.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: cargo ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32d7a5ac07..e21822a6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3706,9 +3706,9 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" +checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index dc70c28586..69c8caa6a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,7 @@ num-complex = { version = "0.4", default-features = false, features = ["libm once_cell = { version = "1.21", default-features = false } paste = { version = "1.0", default-features = false } proptest = { version = "1.8", default-features = false, features = ["no_std", "alloc"] } -proptest-derive = { version = "0.7", default-features = false } +proptest-derive = { version = "0.8", default-features = false } rand = { version = "0.10", default-features = false } rayon = "1.10" rocksdb = { version = "0.24", default-features = false } From 5deb424f35ecbfe35af364311d0622477ccadb4c Mon Sep 17 00:00:00 2001 From: Moritz Zielke Date: Tue, 11 Aug 2026 09:05:00 +0800 Subject: [PATCH 14/17] chore: minor follow-ups for 3478 (#3564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: extend changelog entry and mark it `BREAKING` * chore: simplify code --------- Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com> --- CHANGELOG.md | 3 ++- crates/assembly-syntax/src/ast/instruction/print.rs | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83f485742..5e223f5984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ #### Features -- Added `trace`, `trace.CONST`, and `trace.event("...")` assembly as syntactic sugar for emitting optional read-only trace events ([#3478](https://github.com/0xMiden/miden-vm/pull/3478)). +- [BREAKING] Added `trace`, `trace.CONST`, and `trace.event("...")` assembly as syntactic sugar for emitting optional read-only trace events. This adds variants `Trace` and `TraceImm` to the public enum `miden_assembly_syntax::ast::Instruction` ([#3478](https://github.com/0xMiden/miden-vm/pull/3478)). + #### Changes - [BREAKING] Reduced the precompile STARK relation from 12 AIRs to 10 by merging the chunk/node/sponge and EC point/group stores ([#3464](https://github.com/0xMiden/miden-vm/pull/3464)). diff --git a/crates/assembly-syntax/src/ast/instruction/print.rs b/crates/assembly-syntax/src/ast/instruction/print.rs index 00c6b8073c..0f43b2f436 100644 --- a/crates/assembly-syntax/src/ast/instruction/print.rs +++ b/crates/assembly-syntax/src/ast/instruction/print.rs @@ -342,11 +342,7 @@ impl PrettyPrint for Instruction { // `trace.` is invalid syntax, so to support a `print -> parse` round trip // we print the equivalent `push. trace drop` sequence instead. Self::TraceImm(value) => { - let value = match value { - Immediate::Value(value) => display(*value), - Immediate::Constant(name) => text(name), - }; - flatten(const_text("push") + const_text(".") + value + const_text(" trace drop")) + flatten(inst_with_felt_imm("push", value) + const_text(" trace drop")) }, // Handled by the early return for !has_textual_representation() From caf967a5837fdcfa8815216ec535f56ebe3d42d6 Mon Sep 17 00:00:00 2001 From: Mehmet Date: Tue, 11 Aug 2026 16:48:49 +0300 Subject: [PATCH 15/17] refactor(serde-utils): move bounded length helpers out of miden-core (#3541) `read_bounded_len` and `validate_bounded_len` lived in `miden-core::serde`, with a private copy of both in `miden-utils-indexing` because that crate cannot depend on `miden-core`. Now that `miden-crypto` has been merged into this workspace, both can live in `miden-serde-utils`, which is already a dependency of every crate involved. The helpers move to `miden-serde-utils` as public items, `miden-core::serde` re-exports them alongside the traits it already re-exports from the same crate, and the duplicates in `miden-utils-indexing` are removed. Behaviour is unchanged; tests covering the budget, remaining-input and overflow rejection paths are added next to the new definitions. Closes #3415 --- CHANGELOG.md | 1 + Cargo.lock | 1 + core/Cargo.toml | 2 + core/src/lib.rs | 42 +------------ crates/serde-utils/src/lib.rs | 100 +++++++++++++++++++++++++++++++ crates/utils-indexing/src/lib.rs | 40 +------------ tools/miden-core-fuzz/Cargo.lock | 1 + 7 files changed, 108 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e223f5984..1b6fc0bd5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [BREAKING] Validated `LeafIndex` on deserialization: `LeafIndex::read_from()` now routes through `TryFrom`, and the bypassing `serde` derives were removed from `LeafIndex`, `SmtLeaf`, `Smt`, and `PartialSmt` ([#3559](https://github.com/0xMiden/miden-vm/issues/3559)). - Fixed `Polynomial::div` panicking when the numerator is zero by adding the missing `return` keyword ([#3534](https://github.com/0xMiden/miden-vm/issues/3534)). +- Moved the `read_bounded_len` and `validate_bounded_len` helpers from `miden-core` to `miden-serde-utils`, where they are now public. `miden-core::serde` re-exports them unchanged, and the private duplicates in `miden-utils-indexing` were removed ([#3415](https://github.com/0xMiden/miden-vm/issues/3415)). ## v0.29.0 (2026-08-04) diff --git a/Cargo.lock b/Cargo.lock index e21822a6ff..f563bfa8cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2182,6 +2182,7 @@ dependencies = [ "miden-crypto", "miden-debug-types", "miden-formatting", + "miden-serde-utils", "miden-test-serde-macros", "miden-test-utils", "miden-utils-core-derive", diff --git a/core/Cargo.toml b/core/Cargo.toml index 8db1161d9c..f6a225c03f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -34,6 +34,7 @@ std = [ "miden-crypto/std", "miden-debug-types/std", "miden-formatting/std", + "miden-serde-utils/std", "miden-utils-indexing/std", "miden-utils-sync/std", "thiserror/std", @@ -53,6 +54,7 @@ fuzzing = [] miden-crypto.workspace = true miden-debug-types.workspace = true miden-formatting.workspace = true +miden-serde-utils.workspace = true miden-utils-core-derive.workspace = true miden-utils-indexing.workspace = true miden-utils-sync.workspace = true diff --git a/core/src/lib.rs b/core/src/lib.rs index ee6723d6b7..c94501e13c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -31,48 +31,10 @@ pub mod field { } pub mod serde { - pub use miden_crypto::utils::{ + pub use miden_serde_utils::{ BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, - SliceReader, + SliceReader, read_bounded_len, validate_bounded_len, }; - - /// Reads and validates a serialized length before it is used for allocation. - pub fn read_bounded_len( - source: &mut R, - label: &str, - min_element_size: usize, - ) -> Result { - let len = source.read_usize()?; - validate_bounded_len(source, label, len, min_element_size)?; - Ok(len) - } - - /// Validates that a serialized length fits both the reader budget and remaining input. - pub fn validate_bounded_len( - source: &R, - label: &str, - len: usize, - min_element_size: usize, - ) -> Result<(), DeserializationError> { - let max_len = source.max_alloc(min_element_size); - if len > max_len { - return Err(DeserializationError::InvalidValue(alloc::format!( - "{label} count {len} exceeds budget {max_len}" - ))); - } - - let min_bytes = len.checked_mul(min_element_size).ok_or_else(|| { - DeserializationError::InvalidValue(alloc::format!( - "{label} count {len} overflows minimum serialized size {min_element_size}" - )) - })?; - source.check_eor(min_bytes).map_err(|err| match err { - DeserializationError::UnexpectedEOF => DeserializationError::InvalidValue( - alloc::format!("{label} count {len} exceeds remaining input"), - ), - err => err, - }) - } } pub mod crypto { diff --git a/crates/serde-utils/src/lib.rs b/crates/serde-utils/src/lib.rs index 9895539fa7..9aeb0babd3 100644 --- a/crates/serde-utils/src/lib.rs +++ b/crates/serde-utils/src/lib.rs @@ -94,6 +94,63 @@ pub use byte_reader::{BudgetedReader, ByteReader, ReadManyIter, SliceReader}; mod byte_writer; pub use byte_writer::ByteWriter; +// BOUNDED LENGTH HELPERS +// ================================================================================================ + +/// Reads and validates a serialized length before it is used for allocation. +/// +/// `label` names the collection being read and is only used to build the error message. +/// `min_element_size` is the minimum number of bytes one element occupies once serialized. +/// +/// # Errors +/// Returns an error if the length cannot be read, or if it fails the checks described in +/// [`validate_bounded_len`]. +pub fn read_bounded_len( + source: &mut R, + label: &str, + min_element_size: usize, +) -> Result { + let len = source.read_usize()?; + validate_bounded_len(source, label, len, min_element_size)?; + Ok(len) +} + +/// Validates that a serialized length fits both the reader budget and remaining input. +/// +/// This guards against malicious length prefixes that would otherwise cause a compact payload +/// to be amplified into a large allocation. +/// +/// # Errors +/// Returns [`DeserializationError::InvalidValue`] if: +/// * `len` exceeds the number of elements the reader's remaining budget allows. +/// * `len * min_element_size` overflows. +/// * The source does not hold `len * min_element_size` more bytes. +pub fn validate_bounded_len( + source: &R, + label: &str, + len: usize, + min_element_size: usize, +) -> Result<(), DeserializationError> { + let max_len = source.max_alloc(min_element_size); + if len > max_len { + return Err(DeserializationError::InvalidValue(format!( + "{label} count {len} exceeds budget {max_len}" + ))); + } + + let min_bytes = len.checked_mul(min_element_size).ok_or_else(|| { + DeserializationError::InvalidValue(format!( + "{label} count {len} overflows minimum serialized size {min_element_size}" + )) + })?; + source.check_eor(min_bytes).map_err(|err| match err { + DeserializationError::UnexpectedEOF => DeserializationError::InvalidValue(format!( + "{label} count {len} exceeds remaining input" + )), + err => err, + }) +} + // SERIALIZABLE TRAIT // ================================================================================================ @@ -925,6 +982,49 @@ mod tests { assert!(matches!(result, Err(DeserializationError::InvalidValue(_)))); } + #[test] + fn read_bounded_len_accepts_a_length_backed_by_enough_input() { + let mut bytes = 3usize.to_bytes(); + bytes.extend_from_slice(&[0u8; 3]); + let mut source = SliceReader::new(&bytes); + + assert_eq!(read_bounded_len(&mut source, "elements", 1).unwrap(), 3); + } + + #[test] + fn read_bounded_len_rejects_a_length_exceeding_remaining_input() { + let mut bytes = 8usize.to_bytes(); + bytes.extend_from_slice(&[0u8; 3]); + let mut source = SliceReader::new(&bytes); + + assert!(matches!( + read_bounded_len(&mut source, "elements", 1), + Err(DeserializationError::InvalidValue(_)) + )); + } + + #[test] + fn read_bounded_len_rejects_a_length_exceeding_the_budget() { + let mut bytes = 64usize.to_bytes(); + bytes.extend_from_slice(&[0u8; 64]); + let mut source = BudgetedReader::new(SliceReader::new(&bytes), 16); + + assert!(matches!( + read_bounded_len(&mut source, "elements", 1), + Err(DeserializationError::InvalidValue(_)) + )); + } + + #[test] + fn validate_bounded_len_rejects_a_length_overflowing_the_element_size() { + let source = SliceReader::new(&[]); + + assert!(matches!( + validate_bounded_len(&source, "elements", usize::MAX, 2), + Err(DeserializationError::InvalidValue(_)) + )); + } + #[test] fn budgeted_vec_of_one_element_tuples_accepts_exact_budget() { let values = Vec::<(usize,)>::from([(0,), (1,), (2,)]); diff --git a/crates/utils-indexing/src/lib.rs b/crates/utils-indexing/src/lib.rs index 9319faa977..446a521e76 100644 --- a/crates/utils-indexing/src/lib.rs +++ b/crates/utils-indexing/src/lib.rs @@ -419,7 +419,7 @@ impl TryFrom> for IndexVec { // ================================================================================================ use miden_serde_utils::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, + ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, read_bounded_len, }; impl Serializable for IndexVec @@ -499,44 +499,6 @@ where } } -/// Reads and validates a serialized length before it is used for allocation. -fn read_bounded_len( - source: &mut R, - label: &str, - min_element_size: usize, -) -> Result { - let len = source.read_usize()?; - validate_bounded_len(source, label, len, min_element_size)?; - Ok(len) -} - -/// Validates that a serialized length fits both the reader budget and remaining input. -fn validate_bounded_len( - source: &R, - label: &str, - len: usize, - min_element_size: usize, -) -> Result<(), DeserializationError> { - let max_len = source.max_alloc(min_element_size); - if len > max_len { - return Err(DeserializationError::InvalidValue(alloc::format!( - "{label} count {len} exceeds budget {max_len}" - ))); - } - - let min_bytes = len.checked_mul(min_element_size).ok_or_else(|| { - DeserializationError::InvalidValue(alloc::format!( - "{label} count {len} overflows minimum serialized size {min_element_size}" - )) - })?; - source.check_eor(min_bytes).map_err(|err| match err { - DeserializationError::UnexpectedEOF => DeserializationError::InvalidValue(alloc::format!( - "{label} count {len} exceeds remaining input" - )), - err => err, - }) -} - /// Bounds speculative collection capacity by both the declared length and the reader's remaining /// budget expressed in bytes of the in-memory element type. /// diff --git a/tools/miden-core-fuzz/Cargo.lock b/tools/miden-core-fuzz/Cargo.lock index ec4e418bce..96833bd7cf 100644 --- a/tools/miden-core-fuzz/Cargo.lock +++ b/tools/miden-core-fuzz/Cargo.lock @@ -768,6 +768,7 @@ dependencies = [ "miden-crypto", "miden-debug-types", "miden-formatting", + "miden-serde-utils", "miden-utils-core-derive", "miden-utils-indexing", "miden-utils-sync", From 2163ca0a78b4283ed7a0bea753ee27d3c665503c Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Tue, 11 Aug 2026 23:30:32 +0400 Subject: [PATCH 16/17] Optimize Horner evaluation point reads (#3570) --- CHANGELOG.md | 1 + air/src/config.rs | 64 ++-- air/src/constraints/chiplets/memory.rs | 4 +- air/src/constraints/generated.rs | 330 ++++++++---------- .../lookup/buses/chiplet_requests.rs | 38 +- air/src/constraints/stack/crypto.rs | 6 +- ...__relation_digest_matches_current_air.snap | 38 +- .../asm/crypto/dsa/falcon512_poseidon2.masm | 26 +- .../lib/core/asm/sys/vm/constraints_eval.masm | 4 +- crates/lib/core/asm/sys/vm/mod.masm | 16 +- .../docs/crypto/dsa/falcon512_poseidon2.md | 2 +- docs/src/design/stack/crypto_ops.md | 42 +-- .../design/stack/crypto_ops/HORNERBASE.png | Bin 143537 -> 143685 bytes .../img/design/stack/crypto_ops/HORNEREXT.png | Bin 143626 -> 137436 bytes .../assembly/cryptographic_operations.md | 4 +- processor/src/errors.rs | 4 + .../execution/operations/crypto_ops/mod.rs | 85 ++--- .../execution/operations/crypto_ops/tests.rs | 72 +++- processor/src/fast/tests/masm_consistency.rs | 4 +- ...ations_67_vec__Operation__HornerBase_.snap | 40 +-- ...ations_67_vec__Operation__HornerBase_.snap | 40 +-- ...ations_67_vec__Operation__HornerBase_.snap | 40 +-- processor/src/trace/execution_tracer.rs | 14 - processor/src/trace/tests/chiplets/memory.rs | 47 ++- processor/src/tracer.rs | 31 +- 25 files changed, 465 insertions(+), 487 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b6fc0bd5b..696d7d06cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [BREAKING] Removed the free `execute()` and `execute_sync()` functions from `miden-vm`/`miden-processor`. Use `FastProcessor::new_with_options(...)` followed by `execute()`/`execute_sync()` instead ([#3540](https://github.com/0xMiden/miden-vm/pull/3540)). - [BREAKING] `verify`, `Verifier::verify`, and `Verifier::verify_partial` now borrow the proof and the claim instead of consuming them. - [BREAKING] Renamed the `AdviceMutation::ExtendMap` field `other` to `map` and the `AdviceMutation::ExtendMerkleStore` field `infos` to `inner_nodes`. +- [BREAKING] Changed `HORNERBASE` and `HORNEREXT` to read the evaluation point from an aligned, zero-padded word: `[alpha0, alpha1, 0, 0]`. This reduces the memory-chiplet trace for `HORNERBASE` from two rows to one and gives both operations the same memory layout ([#3570](https://github.com/0xMiden/miden-vm/pull/3570)). #### Fixes diff --git a/air/src/config.rs b/air/src/config.rs index 682c1ab992..acdeba915a 100644 --- a/air/src/config.rs +++ b/air/src/config.rs @@ -121,20 +121,20 @@ pub type RelationDigest = [Felt; 4]; /// Compile-time constant binding the Fiat-Shamir transcript to the Miden VM AIR. /// Must match the constants in `crates/lib/core/asm/sys/vm/mod.masm`. pub const RELATION_DIGEST: RelationDigest = [ - Felt::new_unchecked(6228634522968454696), - Felt::new_unchecked(9493741029039437490), - Felt::new_unchecked(16565065039104926463), - Felt::new_unchecked(1338979827357058143), + Felt::new_unchecked(5295691278992603729), + Felt::new_unchecked(14372903089468495231), + Felt::new_unchecked(14126114708199185335), + Felt::new_unchecked(1962580870545051767), ]; /// Root of the accepted ACE circuit registry. /// /// Active leaves are ACE circuit commitments indexed by `ProofOrder::tag()`. pub const ACE_CIRCUIT_REGISTRY_ROOT: [Felt; 4] = [ - Felt::new_unchecked(6703562205535399821), - Felt::new_unchecked(4902180974408534340), - Felt::new_unchecked(2376205887554034497), - Felt::new_unchecked(2131879092839069624), + Felt::new_unchecked(1518962395264624615), + Felt::new_unchecked(15784779067617049400), + Felt::new_unchecked(1581745292729420002), + Felt::new_unchecked(9887978513811317011), ]; /// Smallest ACE circuit registry depth covering every proof-order tag. @@ -155,40 +155,40 @@ const _: () = assert!( /// Inactive leaves are deterministic padding. pub const ACE_CIRCUIT_REGISTRY_LEAVES: &[[Felt; 4]] = &[ [ - Felt::new_unchecked(14950454962026649157), - Felt::new_unchecked(18381334423201801371), - Felt::new_unchecked(3505576435670816154), - Felt::new_unchecked(10492020312020072697), + Felt::new_unchecked(15173775376017517731), + Felt::new_unchecked(17060787883775236642), + Felt::new_unchecked(10312199614991066810), + Felt::new_unchecked(9392681623159778270), ], [ - Felt::new_unchecked(16360681022883134878), - Felt::new_unchecked(3383008486129604525), - Felt::new_unchecked(12128423521814793071), - Felt::new_unchecked(15484732731492441141), + Felt::new_unchecked(11196158180439972460), + Felt::new_unchecked(12611152378979260492), + Felt::new_unchecked(4182814750108927065), + Felt::new_unchecked(13193607488197256617), ], [ - Felt::new_unchecked(9558598998948809127), - Felt::new_unchecked(5625297958135351357), - Felt::new_unchecked(6045843798313457949), - Felt::new_unchecked(11084501094466476362), + Felt::new_unchecked(10860557104621841186), + Felt::new_unchecked(3094532602596976361), + Felt::new_unchecked(17159863587394592259), + Felt::new_unchecked(473648936676637663), ], [ - Felt::new_unchecked(7246951904958279967), - Felt::new_unchecked(9113637511529023284), - Felt::new_unchecked(6771609253107818884), - Felt::new_unchecked(9655557337986743765), + Felt::new_unchecked(15256300555678967793), + Felt::new_unchecked(4085454011309879133), + Felt::new_unchecked(7907865245912094959), + Felt::new_unchecked(18180782078565659964), ], [ - Felt::new_unchecked(5400103277155201926), - Felt::new_unchecked(13221982994882074493), - Felt::new_unchecked(4281571135509886317), - Felt::new_unchecked(8539761392286494695), + Felt::new_unchecked(17759138428963217527), + Felt::new_unchecked(16630927310815206805), + Felt::new_unchecked(11661497490269156220), + Felt::new_unchecked(9328352955914878064), ], [ - Felt::new_unchecked(15834849235453051024), - Felt::new_unchecked(14635731417693870212), - Felt::new_unchecked(2486581593759991827), - Felt::new_unchecked(2068667486060323890), + Felt::new_unchecked(11267126175745873149), + Felt::new_unchecked(9359003113379985808), + Felt::new_unchecked(3823810735281226285), + Felt::new_unchecked(1340512497265955003), ], [ Felt::new_unchecked(1422687632582465263), diff --git a/air/src/constraints/chiplets/memory.rs b/air/src/constraints/chiplets/memory.rs index 7f830ba3c1..028a6ad122 100644 --- a/air/src/constraints/chiplets/memory.rs +++ b/air/src/constraints/chiplets/memory.rs @@ -551,8 +551,8 @@ mod tests { } /// Repeating `(ctx, word_addr, clk)` is safe for any combination of read access sizes. This is - /// required when operations such as `horner_eval_base` or ACE read multiple elements from the - /// same word in one clock cycle. + /// required when operations such as ACE read multiple elements from the same word in one clock + /// cycle. #[test] fn same_key_memory_rows_allow_all_read_access_sizes() { for (local_size, next_size) in [ diff --git a/air/src/constraints/generated.rs b/air/src/constraints/generated.rs index 3a2cad5640..0b8ff520c8 100644 --- a/air/src/constraints/generated.rs +++ b/air/src/constraints/generated.rs @@ -2556,183 +2556,163 @@ pub fn eval_core>(builder: &mut AB) { let e2427 = e2426.dup() - h9.dup(); let e2429 = e2427.dup() * b902.dup(); let e2430 = e2381.dup() + e2429.dup(); - let e2431 = e1616.dup() * AB::Expr::from(h23); - let e2432 = e2415.dup() + e2431.dup(); - let e2433 = e2302.dup() + e2432.dup(); - let b2434 = h66 + h9.dup(); - let e2436 = h152.dup() * b2434.dup(); - let e2437 = e2436.dup() + AB::Expr::from(h2); - let e2438 = e2437.dup() + e2077.dup(); - let e2439 = e2438.dup() + e1835.dup(); - let e2440 = e2302.dup() + e2439.dup(); - let e2441 = e2433.dup() * e2440.dup(); - let e2442 = e2441.dup() - h9.dup(); - let e2444 = e2442.dup() * b168.dup(); - let e2445 = e2430.dup() + e2444.dup(); - let e2446 = e2131.dup() + e2415.dup(); - let e2447 = e1617.dup() * AB::Expr::from(h25); - let e2448 = e2431.dup() + e2447.dup(); - let e2449 = e1618.dup() * AB::Expr::from(h82); - let e2450 = e2448.dup() + e2449.dup(); - let e2451 = e1619.dup() * AB::Expr::from(h40); - let e2452 = e2450.dup() + e2451.dup(); - let e2453 = e2446.dup() + e2452.dup(); - let e2454 = e2453.dup() - h9.dup(); - let e2456 = e2454.dup() * b171.dup(); - let e2457 = e2445.dup() + e2456.dup(); - let e2459 = e1629.dup() * h73.dup(); - let e2460 = h151.dup() + e2459.dup(); - let e2461 = e2460.dup() + e2133.dup(); - let e2462 = e1615.dup() * AB::Expr::from(h42); - let e2463 = e2461.dup() + e2462.dup(); - let e2464 = e2463.dup() + e2303.dup(); - let e2465 = e2464.dup() - h9.dup(); - let b2466 = b602.dup() * b135.dup(); - let b2467 = b2466.dup() * b32.dup(); - let e2469 = e2465.dup() * b2467.dup(); - let e2470 = e2457.dup() + e2469.dup(); - let e2471 = e2460.dup() + h9.dup(); - let e2472 = e2471.dup() + e2133.dup(); - let e2473 = e2472.dup() + e2462.dup(); - let e2474 = e2473.dup() + e2303.dup(); - let e2475 = e2474.dup() - h9.dup(); - let b2476 = b2466.dup() * h16; - let e2478 = e2475.dup() * b2476.dup(); - let e2479 = e2470.dup() + e2478.dup(); + let e2431 = e2131.dup() + e2415.dup(); + let e2432 = e1616.dup() * AB::Expr::from(h23); + let e2433 = e1617.dup() * AB::Expr::from(h25); + let e2434 = e2432.dup() + e2433.dup(); + let e2435 = e2431.dup() + e2434.dup(); + let e2436 = e2435.dup() - h9.dup(); + let e2438 = e2436.dup() * b561.dup(); + let e2439 = e2430.dup() + e2438.dup(); + let e2441 = e1629.dup() * h73.dup(); + let e2442 = h151.dup() + e2441.dup(); + let e2443 = e2442.dup() + e2133.dup(); + let e2444 = e1615.dup() * AB::Expr::from(h42); + let e2445 = e2443.dup() + e2444.dup(); + let e2446 = e2445.dup() + e2303.dup(); + let e2447 = e2446.dup() - h9.dup(); + let b2448 = b602.dup() * b135.dup(); + let b2449 = b2448.dup() * b32.dup(); + let e2451 = e2447.dup() * b2449.dup(); + let e2452 = e2439.dup() + e2451.dup(); + let e2453 = e2442.dup() + h9.dup(); + let e2454 = e2453.dup() + e2133.dup(); + let e2455 = e2454.dup() + e2444.dup(); + let e2456 = e2455.dup() + e2303.dup(); + let e2457 = e2456.dup() - h9.dup(); + let b2458 = b2448.dup() * h16; + let e2460 = e2457.dup() * b2458.dup(); + let e2461 = e2452.dup() + e2460.dup(); let h177: AB::Expr = AB::Expr::from(Felt::from_u64(17)); - let e2482 = e1629.dup() * h177.dup(); - let e2483 = h151.dup() + e2482.dup(); - let e2484 = e2483.dup() + AB::Expr::from(h1); - let e2485 = h152.dup() * AB::Expr::from(h2); - let e2486 = e2484.dup() + e2485.dup(); - let e2487 = e2486.dup() + e2160.dup(); - let e2488 = e2487.dup() + e2162.dup(); - let e2489 = e2488.dup() + e2165.dup(); - let e2490 = e2489.dup() - h9.dup(); - let e2492 = e2490.dup() * b165.dup(); - let e2493 = e2479.dup() + e2492.dup(); - let e2494 = e1616.dup() * AB::Expr::from(h82); - let e2495 = e1934.dup() + e2494.dup(); - let e2496 = e1617.dup() * AB::Expr::from(h40); - let e2497 = e2495.dup() + e2496.dup(); - let e2498 = e1618.dup() * AB::Expr::from(h76); - let e2499 = e2497.dup() + e2498.dup(); - let e2500 = e2499.dup() + e2171.dup(); - let e2501 = e2500.dup() + e2174.dup(); - let e2502 = e2501.dup() + e2177.dup(); - let e2503 = e2502.dup() + e2180.dup(); - let e2504 = e2503.dup() + e1623.dup(); - let e2505 = e2159.dup() + e2504.dup(); - let e2506 = e2505.dup() * e2232.dup(); - let e2507 = e2506.dup() - h9.dup(); - let e2508 = e2507.dup() * b530.dup(); - let e2509 = e2493.dup() + e2508.dup(); - let e2510 = e2509.dup() * Into::::into(h157); - let e2511 = -AB::ExprEF::from(b141.dup()); - let e2512 = e2511.dup() - b206.dup(); - let e2513 = e2512.dup() - b144.dup(); - let e2514 = e2513.dup() - b137.dup(); - let e2515 = e2083.dup() * h85.dup(); - let e2516 = e2515.dup() - e2067.dup(); - let e2517 = e2516.dup() * b26.dup(); - let e2518 = e2514.dup() + e2517.dup(); - let e2519 = e2100.dup() * h85.dup(); - let e2520 = e2519.dup() - e2093.dup(); - let e2521 = e2520.dup() * b42.dup(); - let e2522 = e2518.dup() + e2521.dup(); - let e2523 = e2522.dup() - b152.dup(); - let e2524 = e2523.dup() - b49.dup(); - let e2525 = e2144.dup() * h85.dup(); - let e2526 = e2525.dup() - e2127.dup(); - let e2527 = e2526.dup() * b214.dup(); - let e2528 = e2524.dup() + e2527.dup(); - let e2529 = e2525.dup() - e2153.dup(); - let e2530 = e2529.dup() * e2083.dup(); - let e2531 = e2530.dup() - e2154.dup(); - let e2532 = e2531.dup() * b34.dup(); - let e2533 = e2528.dup() + e2532.dup(); - let e2534 = e2232.dup() * h85.dup(); - let e2535 = e2534.dup() - e2194.dup(); - let e2536 = e2535.dup() * b527.dup(); - let e2537 = e2533.dup() + e2536.dup(); - let e2538 = e2257.dup() * h85.dup(); - let e2539 = e2538.dup() - e2244.dup(); - let e2540 = e2539.dup() * b133.dup(); - let e2541 = e2537.dup() + e2540.dup(); - let e2542 = e2538.dup() - e2268.dup(); - let e2543 = e2542.dup() * e2286.dup(); - let e2544 = e2543.dup() - e2269.dup(); - let e2545 = e2544.dup() * e2293.dup(); - let e2546 = e2545.dup() - e2287.dup(); - let e2547 = e2546.dup() * b379.dup(); - let e2548 = e2541.dup() + e2547.dup(); - let e2549 = e2548.dup() - b2310.dup(); - let e2550 = e2549.dup() - b2318.dup(); - let e2551 = e2550.dup() - b2330.dup(); - let e2552 = e2551.dup() - b2345.dup(); - let e2553 = e2367.dup() * h85.dup(); - let e2554 = e2553.dup() - e2353.dup(); - let e2555 = e2554.dup() * b463.dup(); - let e2556 = e2552.dup() + e2555.dup(); - let e2557 = e2376.dup() * h85.dup(); - let e2558 = e2557.dup() - e2374.dup(); - let e2559 = e2558.dup() * b465.dup(); - let e2560 = e2556.dup() + e2559.dup(); - let e2561 = e2411.dup() * h85.dup(); - let e2562 = e2561.dup() - e2395.dup(); - let e2563 = e2562.dup() * e2417.dup(); - let e2564 = e2563.dup() - e2412.dup(); - let e2565 = e2564.dup() * e2425.dup(); - let e2566 = e2565.dup() - e2418.dup(); - let e2567 = e2566.dup() * b902.dup(); - let e2568 = e2560.dup() + e2567.dup(); - let e2569 = e2440.dup() * h85.dup(); - let e2570 = e2569.dup() - e2433.dup(); - let e2571 = e2570.dup() * b168.dup(); - let e2572 = e2568.dup() + e2571.dup(); - let e2573 = e2572.dup() - b171.dup(); - let e2574 = e2573.dup() - b2467.dup(); - let e2575 = e2574.dup() - b2476.dup(); - let e2576 = e2575.dup() - b165.dup(); - let e2577 = e2534.dup() - e2505.dup(); - let e2578 = e2577.dup() * b530.dup(); - let e2579 = e2576.dup() + e2578.dup(); - let e2580 = e2510.dup() - e2579.dup(); - builder.assert_zero_ext(e2580.dup()); + let e2464 = e1629.dup() * h177.dup(); + let e2465 = h151.dup() + e2464.dup(); + let e2466 = e2465.dup() + AB::Expr::from(h1); + let e2467 = h152.dup() * AB::Expr::from(h2); + let e2468 = e2466.dup() + e2467.dup(); + let e2469 = e2468.dup() + e2160.dup(); + let e2470 = e2469.dup() + e2162.dup(); + let e2471 = e2470.dup() + e2165.dup(); + let e2472 = e2471.dup() - h9.dup(); + let e2474 = e2472.dup() * b165.dup(); + let e2475 = e2461.dup() + e2474.dup(); + let e2476 = e1616.dup() * AB::Expr::from(h82); + let e2477 = e1934.dup() + e2476.dup(); + let e2478 = e1617.dup() * AB::Expr::from(h40); + let e2479 = e2477.dup() + e2478.dup(); + let e2480 = e1618.dup() * AB::Expr::from(h76); + let e2481 = e2479.dup() + e2480.dup(); + let e2482 = e2481.dup() + e2171.dup(); + let e2483 = e2482.dup() + e2174.dup(); + let e2484 = e2483.dup() + e2177.dup(); + let e2485 = e2484.dup() + e2180.dup(); + let e2486 = e2485.dup() + e1623.dup(); + let e2487 = e2159.dup() + e2486.dup(); + let e2488 = e2487.dup() * e2232.dup(); + let e2489 = e2488.dup() - h9.dup(); + let e2490 = e2489.dup() * b530.dup(); + let e2491 = e2475.dup() + e2490.dup(); + let e2492 = e2491.dup() * Into::::into(h157); + let e2493 = -AB::ExprEF::from(b141.dup()); + let e2494 = e2493.dup() - b206.dup(); + let e2495 = e2494.dup() - b144.dup(); + let e2496 = e2495.dup() - b137.dup(); + let e2497 = e2083.dup() * h85.dup(); + let e2498 = e2497.dup() - e2067.dup(); + let e2499 = e2498.dup() * b26.dup(); + let e2500 = e2496.dup() + e2499.dup(); + let e2501 = e2100.dup() * h85.dup(); + let e2502 = e2501.dup() - e2093.dup(); + let e2503 = e2502.dup() * b42.dup(); + let e2504 = e2500.dup() + e2503.dup(); + let e2505 = e2504.dup() - b152.dup(); + let e2506 = e2505.dup() - b49.dup(); + let e2507 = e2144.dup() * h85.dup(); + let e2508 = e2507.dup() - e2127.dup(); + let e2509 = e2508.dup() * b214.dup(); + let e2510 = e2506.dup() + e2509.dup(); + let e2511 = e2507.dup() - e2153.dup(); + let e2512 = e2511.dup() * e2083.dup(); + let e2513 = e2512.dup() - e2154.dup(); + let e2514 = e2513.dup() * b34.dup(); + let e2515 = e2510.dup() + e2514.dup(); + let e2516 = e2232.dup() * h85.dup(); + let e2517 = e2516.dup() - e2194.dup(); + let e2518 = e2517.dup() * b527.dup(); + let e2519 = e2515.dup() + e2518.dup(); + let e2520 = e2257.dup() * h85.dup(); + let e2521 = e2520.dup() - e2244.dup(); + let e2522 = e2521.dup() * b133.dup(); + let e2523 = e2519.dup() + e2522.dup(); + let e2524 = e2520.dup() - e2268.dup(); + let e2525 = e2524.dup() * e2286.dup(); + let e2526 = e2525.dup() - e2269.dup(); + let e2527 = e2526.dup() * e2293.dup(); + let e2528 = e2527.dup() - e2287.dup(); + let e2529 = e2528.dup() * b379.dup(); + let e2530 = e2523.dup() + e2529.dup(); + let e2531 = e2530.dup() - b2310.dup(); + let e2532 = e2531.dup() - b2318.dup(); + let e2533 = e2532.dup() - b2330.dup(); + let e2534 = e2533.dup() - b2345.dup(); + let e2535 = e2367.dup() * h85.dup(); + let e2536 = e2535.dup() - e2353.dup(); + let e2537 = e2536.dup() * b463.dup(); + let e2538 = e2534.dup() + e2537.dup(); + let e2539 = e2376.dup() * h85.dup(); + let e2540 = e2539.dup() - e2374.dup(); + let e2541 = e2540.dup() * b465.dup(); + let e2542 = e2538.dup() + e2541.dup(); + let e2543 = e2411.dup() * h85.dup(); + let e2544 = e2543.dup() - e2395.dup(); + let e2545 = e2544.dup() * e2417.dup(); + let e2546 = e2545.dup() - e2412.dup(); + let e2547 = e2546.dup() * e2425.dup(); + let e2548 = e2547.dup() - e2418.dup(); + let e2549 = e2548.dup() * b902.dup(); + let e2550 = e2542.dup() + e2549.dup(); + let e2551 = e2550.dup() - b561.dup(); + let e2552 = e2551.dup() - b2449.dup(); + let e2553 = e2552.dup() - b2458.dup(); + let e2554 = e2553.dup() - b165.dup(); + let e2555 = e2516.dup() - e2487.dup(); + let e2556 = e2555.dup() * b530.dup(); + let e2557 = e2554.dup() + e2556.dup(); + let e2558 = e2492.dup() - e2557.dup(); + builder.assert_zero_ext(e2558.dup()); let h178: AB::Expr = AB::Expr::from(Felt::from_u64(20)); - let e2583 = e1629.dup() * h178.dup(); - let e2584 = h151.dup() + e2583.dup(); - let e2585 = e2584.dup() + AB::Expr::from(h1); - let e2587 = h152.dup() * AB::Expr::from(h70); - let e2588 = e2585.dup() + e2587.dup(); - let e2589 = e1615.dup() * AB::Expr::from(h74); - let e2590 = e2588.dup() + e2589.dup(); - let e2591 = e2590.dup() - h9.dup(); - let e2593 = e2591.dup() * b614.dup(); - let e2594 = e2593.dup() + h9.dup(); - let e2595 = e2584.dup() + AB::Expr::from(h74); - let e2597 = h152.dup() * AB::Expr::from(h71); - let e2598 = e2595.dup() + e2597.dup(); - let e2599 = e1615.dup() * AB::Expr::from(h79); - let e2600 = e2598.dup() + e2599.dup(); - let e2601 = e2600.dup() - h9.dup(); - let b2602 = b605.dup() + b206.dup(); - let b2603 = b2602.dup() + b208.dup(); - let b2604 = b2603.dup() + b212.dup(); - let b2605 = b2604.dup() + b214.dup(); - let b2606 = b2605.dup() * b609.dup(); - let e2608 = e2601.dup() * b2606.dup(); - let e2609 = e2594.dup() + e2608.dup(); - let e2610 = e2598.dup() + e1692.dup(); - let e2611 = e2610.dup() - h9.dup(); - let b2612 = b34.dup() * b609.dup(); - let e2614 = e2611.dup() * b2612.dup(); - let e2615 = e2609.dup() + e2614.dup(); - let e2616 = e2615.dup() * Into::::into(h158); - let e2617 = AB::ExprEF::from(b614.dup()) - b2606.dup(); - let e2618 = e2617.dup() - b2612.dup(); - let e2619 = e2616.dup() - e2618.dup(); - builder.assert_zero_ext(e2619.dup()); + let e2561 = e1629.dup() * h178.dup(); + let e2562 = h151.dup() + e2561.dup(); + let e2563 = e2562.dup() + AB::Expr::from(h1); + let e2565 = h152.dup() * AB::Expr::from(h70); + let e2566 = e2563.dup() + e2565.dup(); + let e2567 = e1615.dup() * AB::Expr::from(h74); + let e2568 = e2566.dup() + e2567.dup(); + let e2569 = e2568.dup() - h9.dup(); + let e2571 = e2569.dup() * b614.dup(); + let e2572 = e2571.dup() + h9.dup(); + let e2573 = e2562.dup() + AB::Expr::from(h74); + let e2575 = h152.dup() * AB::Expr::from(h71); + let e2576 = e2573.dup() + e2575.dup(); + let e2577 = e1615.dup() * AB::Expr::from(h79); + let e2578 = e2576.dup() + e2577.dup(); + let e2579 = e2578.dup() - h9.dup(); + let b2580 = b605.dup() + b206.dup(); + let b2581 = b2580.dup() + b208.dup(); + let b2582 = b2581.dup() + b212.dup(); + let b2583 = b2582.dup() + b214.dup(); + let b2584 = b2583.dup() * b609.dup(); + let e2586 = e2579.dup() * b2584.dup(); + let e2587 = e2572.dup() + e2586.dup(); + let e2588 = e2576.dup() + e1692.dup(); + let e2589 = e2588.dup() - h9.dup(); + let b2590 = b34.dup() * b609.dup(); + let e2592 = e2589.dup() * b2590.dup(); + let e2593 = e2587.dup() + e2592.dup(); + let e2594 = e2593.dup() * Into::::into(h158); + let e2595 = AB::ExprEF::from(b614.dup()) - b2584.dup(); + let e2596 = e2595.dup() - b2590.dup(); + let e2597 = e2594.dup() - e2596.dup(); + builder.assert_zero_ext(e2597.dup()); } /// Generated globally-CSE'd evaluator for `MidenAir::Chiplets`. diff --git a/air/src/constraints/lookup/buses/chiplet_requests.rs b/air/src/constraints/lookup/buses/chiplet_requests.rs index 578bbb6acb..ecb4621ddb 100644 --- a/air/src/constraints/lookup/buses/chiplet_requests.rs +++ b/air/src/constraints/lookup/buses/chiplet_requests.rs @@ -510,44 +510,20 @@ pub(in crate::constraints::lookup) fn emit_chiplet_requests( ); // --- HORNERBASE / HORNEREXT --- - // Both ops read the evaluation point α from memory at `stack[13]`. HORNERBASE - // reads two base-field elements (α₀ at `addr`, α₁ at `addr + 1`); HORNEREXT - // reads a single word `[α₀, α₁, k₀, k₁]` at `addr`. α is held in helpers[0..2] - // for both ops (HORNEREXT additionally parks k₀, k₁ in helpers[2..4]). + // Both ops read the evaluation point alpha from the aligned word + // `[alpha0, alpha1, 0, 0]` at `stack[13]`. let alpha_ptr = stk.get(13); - g.batch( - "hornerbase", - op_flags.hornerbase(), - move |b| { - let addr0: LB::Expr = alpha_ptr.into(); - let addr1: LB::Expr = addr0.clone() + LB::Expr::from_u16(1); - let eval0: LB::Expr = user_helpers[0].into(); - let eval1: LB::Expr = user_helpers[1].into(); - b.remove( - "hornerbase_alpha0", - MemoryMsg::read_element(sys_ctx.into(), addr0, clk.into(), eval0), - Deg { v: 5, u: 6 }, - ); - b.remove( - "hornerbase_alpha1", - MemoryMsg::read_element(sys_ctx.into(), addr1, clk.into(), eval1), - Deg { v: 5, u: 6 }, - ); - }, - Deg { v: 6, u: 7 }, // (V, U) = (1 + 5, 2 + 5) - ); g.remove( - "hornerext", - op_flags.hornerext(), + "horner", + op_flags.hornerbase() + op_flags.hornerext(), move || { - let addr: LB::Expr = alpha_ptr.into(); let word: [LB::Expr; 4] = [ user_helpers[0].into(), user_helpers[1].into(), - user_helpers[2].into(), - user_helpers[3].into(), + LB::Expr::ZERO, + LB::Expr::ZERO, ]; - MemoryMsg::read_word(sys_ctx.into(), addr, clk.into(), word) + MemoryMsg::read_word(sys_ctx.into(), alpha_ptr.into(), clk.into(), word) }, Deg { v: 5, u: 6 }, ); diff --git a/air/src/constraints/stack/crypto.rs b/air/src/constraints/stack/crypto.rs index 180ac35163..ef6ab5e2ce 100644 --- a/air/src/constraints/stack/crypto.rs +++ b/air/src/constraints/stack/crypto.rs @@ -109,7 +109,7 @@ fn enforce_cryptostream_constraints( /// Stack layout: /// s[0..8] c0..c7 base-field coefficients (c0 = alpha^7 term, c7 = constant) /// s[8..13] (unused) not affected by this operation -/// s[13] alpha_ptr memory address of alpha +/// s[13] alpha_ptr address of the word [alpha0, alpha1, 0, 0] /// s[14..16] (acc0, acc1) accumulator (quadratic extension element) /// /// Preservation of s[0..14] is enforced by the general stack transition constraints @@ -183,12 +183,12 @@ fn enforce_hornerbase_constraints( /// s[4..6] (c2_0, c2_1) alpha term /// s[6..8] (c3_0, c3_1) constant term /// s[8..13] (unused) not affected by this operation -/// s[13] alpha_ptr memory address of alpha (word: [alpha0, alpha1, k0, k1]) +/// s[13] alpha_ptr address of the word [alpha0, alpha1, 0, 0] /// s[14..16] (acc0, acc1) accumulator (quadratic extension element) /// /// Helper registers: /// h[0..2] (alpha0, alpha1) evaluation point -/// h[2..4] k0, k1 padding from the alpha memory word (unused by constraints) +/// h[2..4] unused /// h[4..6] (tmp0, tmp1) intermediate result /// /// Horner steps: diff --git a/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap b/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap index e6633f4efe..daf7434a7b 100644 --- a/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap +++ b/air/src/snapshots/miden_air__config__tests__relation_digest_matches_current_air.snap @@ -4,32 +4,32 @@ expression: snapshot --- constraints_eval_core_then_chiplets_then_poseidon2_permutation: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [14950454962026649157, 18381334423201801371, 3505576435670816154, 10492020312020072697] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [15173775376017517731, 17060787883775236642, 10312199614991066810, 9392681623159778270] constraints_eval_core_then_poseidon2_permutation_then_chiplets: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [16360681022883134878, 3383008486129604525, 12128423521814793071, 15484732731492441141] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [11196158180439972460, 12611152378979260492, 4182814750108927065, 13193607488197256617] constraints_eval_chiplets_then_core_then_poseidon2_permutation: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [9558598998948809127, 5625297958135351357, 6045843798313457949, 11084501094466476362] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [10860557104621841186, 3094532602596976361, 17159863587394592259, 473648936676637663] constraints_eval_chiplets_then_poseidon2_permutation_then_core: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [7246951904958279967, 9113637511529023284, 6771609253107818884, 9655557337986743765] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [15256300555678967793, 4085454011309879133, 7907865245912094959, 18180782078565659964] constraints_eval_poseidon2_permutation_then_core_then_chiplets: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [5400103277155201926, 13221982994882074493, 4281571135509886317, 8539761392286494695] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [17759138428963217527, 16630927310815206805, 11661497490269156220, 9328352955914878064] constraints_eval_poseidon2_permutation_then_chiplets_then_core: num_inputs: 624 - num_eval_gates: 5176 - stream_len: 5760 - commitment: [15834849235453051024, 14635731417693870212, 2486581593759991827, 2068667486060323890] -relation_digest: [6228634522968454696, 9493741029039437490, 16565065039104926463, 1338979827357058143] + num_eval_gates: 5152 + stream_len: 5736 + commitment: [11267126175745873149, 9359003113379985808, 3823810735281226285, 1340512497265955003] +relation_digest: [5295691278992603729, 14372903089468495231, 14126114708199185335, 1962580870545051767] diff --git a/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm b/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm index 0741885396..8bfde09d5d 100644 --- a/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm +++ b/crates/lib/core/asm/crypto/dsa/falcon512_poseidon2.masm @@ -160,12 +160,13 @@ end #! Operand stack: [] #! Advice stack: [] #! -#! Cycles: 6780 -@locals(8) +#! Cycles: 6785 +@locals(12) pub proc load_h_s2_and_product # We will store in local addresses: # 1. 0..4 the public key, - # 2. 4..8 the inverse of the evaluation point tau as [tau_inv0, tau_inv1, tau0, tau1]. + # 2. 4..8 the inverse evaluation point as [tau_inv0, tau_inv1, 0, 0], + # 3. 8..12 the evaluation point as [tau0, tau1, 0, 0]. # 1) Set up the stack for loading the coefficients of the polynomials, evaluating and hashing them @@ -193,11 +194,13 @@ pub proc load_h_s2_and_product ## which simplifies to ## ## pi(tau_inv) * tau == h(tau_inv) * s2(tau_inv) + push.0.0 adv_push adv_push - # => [tau0, tau1, ...] - dup.1 dup.1 ext2inv # duplicate tau, compute inverse → [tau_inv0, tau_inv1, tau0, tau1, ...] - loc_storew_le.4 # store [tau_inv0, tau_inv1, tau0, tau1] - tau_inv first for Horner - # => [tau_inv0, tau_inv1, tau0, tau1, Y, 0, 0, 0, 0, ptr, tau_inv_ptr, acc0, acc1, ...] + # => [tau0, tau1, 0, 0, ...] + loc_storew_le.8 + ext2inv + loc_storew_le.4 + # => [tau_inv0, tau_inv1, 0, 0, Y, 0, 0, 0, 0, ptr, tau_inv_ptr, acc0, acc1, ...] # 2) Load the coefficients of the h polynomial and evaluate it at tau_inv repeat.64 @@ -289,10 +292,9 @@ pub proc load_h_s2_and_product ## b) Load from memory the challenge we got from the advice stack and assert equality between ## the two challenges - loc_loadw_le.4 # load [tau_inv0, tau_inv1, tau0, tau1] from local memory - # => [tau_inv0, tau_inv1, tau0, tau1, tau0(D), tau1(D), D[2], D[3], ptr, tau_inv_ptr, pi(tau)_0, pi(tau)_1, ...] - - drop drop # drop tau_inv, keep tau for comparison and later ext2mul + loc_loadw_le.8 + # => [tau0, tau1, 0, 0, tau0(D), tau1(D), D[2], D[3], ptr, tau_inv_ptr, pi(tau)_0, pi(tau)_1, ...] + movdn.3 movdn.3 drop drop # => [tau0, tau1, tau0(D), tau1(D), D[2], D[3], ptr, tau_inv_ptr, pi(tau)_0, pi(tau)_1, ...] @@ -632,7 +634,7 @@ pub proc verify locaddr.0 exec.load_h_s2_and_product - #=> [MSG, ...] (Cycles: 6780) + #=> [MSG, ...] (Cycles: 6785) # 2) Load the NONCE from the advice provider. This is encoded as 8 field elements diff --git a/crates/lib/core/asm/sys/vm/constraints_eval.masm b/crates/lib/core/asm/sys/vm/constraints_eval.masm index 25da4c1086..2a6c33f628 100644 --- a/crates/lib/core/asm/sys/vm/constraints_eval.masm +++ b/crates/lib/core/asm/sys/vm/constraints_eval.masm @@ -9,7 +9,7 @@ use miden::core::sys::vm::constraints_eval_inputs const NUM_INPUTS_CIRCUIT = 624 # Number of evaluation gates in the constraint evaluation circuit -const NUM_EVAL_GATES_CIRCUIT = 5176 +const NUM_EVAL_GATES_CIRCUIT = 5152 # Max cycle length for periodic columns const MAX_CYCLE_LEN_LOG = 4 @@ -50,7 +50,7 @@ proc load_and_authenticate_ace_circuit adv.push_mapval exec.constants::ace_circuit_stream_ptr padw padw padw - repeat.720 + repeat.717 adv_pipe exec.poseidon2::permute end diff --git a/crates/lib/core/asm/sys/vm/mod.masm b/crates/lib/core/asm/sys/vm/mod.masm index b086b4923d..0de78e3176 100644 --- a/crates/lib/core/asm/sys/vm/mod.masm +++ b/crates/lib/core/asm/sys/vm/mod.masm @@ -28,16 +28,16 @@ const FELTS_PER_KERNEL_DIGEST = 4 const MAX_NUM_KERNEL_PROCEDURES = 255 # RELATION_DIGEST = hash(PROTOCOL_ID, ACE_REGISTRY_ROOT). -const RELATION_DIGEST_0 = 6228634522968454696 -const RELATION_DIGEST_1 = 9493741029039437490 -const RELATION_DIGEST_2 = 16565065039104926463 -const RELATION_DIGEST_3 = 1338979827357058143 +const RELATION_DIGEST_0 = 5295691278992603729 +const RELATION_DIGEST_1 = 14372903089468495231 +const RELATION_DIGEST_2 = 14126114708199185335 +const RELATION_DIGEST_3 = 1962580870545051767 # Root of the accepted ACE circuit registry. Leaves are indexed by ORDER_TAG. -const ACE_REGISTRY_ROOT_0 = 6703562205535399821 -const ACE_REGISTRY_ROOT_1 = 4902180974408534340 -const ACE_REGISTRY_ROOT_2 = 2376205887554034497 -const ACE_REGISTRY_ROOT_3 = 2131879092839069624 +const ACE_REGISTRY_ROOT_0 = 1518962395264624615 +const ACE_REGISTRY_ROOT_1 = 15784779067617049400 +const ACE_REGISTRY_ROOT_2 = 1581745292729420002 +const ACE_REGISTRY_ROOT_3 = 9887978513811317011 #! Loads security parameters from the advice stack and stores them in memory. #! diff --git a/crates/lib/core/docs/crypto/dsa/falcon512_poseidon2.md b/crates/lib/core/docs/crypto/dsa/falcon512_poseidon2.md index e515f746a8..4edce70679 100644 --- a/crates/lib/core/docs/crypto/dsa/falcon512_poseidon2.md +++ b/crates/lib/core/docs/crypto/dsa/falcon512_poseidon2.md @@ -4,7 +4,7 @@ | ----------- | ------------- | | mod_12289 | Given dividend ( i.e. a u64 given by its lower and higher u32 decomposition ) on the stack,
this routine computes c = a % M where M = 12289

Expected stack state

[a_hi, a_lo, ...]

Output stack state looks like

[c, ...] \| c = a % M

Note that it is the responsibility of the calling procedure to ensure that `a_hi` and `a_lo` are
within the appropriate range i.e., they are u32-s.

Cycles: 29
| | hash_to_point | Takes as input a message digest, a nonce of size 40 bytes represented as 8 field elements
and a pointer. The procedure absorbs MSG and NONCE into a fresh Poseidon2 state and squeezes the
coefficients of a polynomial c representing the hash-to-point of (MSG \|\| NONCE). The coefficients
are then saved in the memory region [c_ptr, c_ptr + 512).
This implementation of the `hash_to_point` procedure avoids the rejection-sampling step
required in the per-the-spec algorithm by using the observation on page 31 in
https://falcon-sign.info/falcon.pdf

Input: [c_ptr, MSG, NONCE1, NONCE0, ...]
Output: [...]

Cycles: ~1430
| -| load_h_s2_and_product | Takes as input PK, the hash of the coefficients of the polynomial `h` representing the expanded
public key, and a pointer to the memory location where the coefficients of the polynomial `h`
will be stored.
The procedure loads `h` from the advice stack and compares its hash with the provided hash `PK`.
It then loads the polynomial `s2` representing the signature from the advice stack and lays it
in memory right after `h`.
After that, it loads the claimed polynomial `h * s2` in Z_Q[x] where Q is the Miden VM prime
from the advice stack and lays it right after `s2`.
The hash of `h`, `s2` and the claimed product is also computed and the first two field elements
of the digest (i.e., the Fiat-Shamir challenge) are used in order to check that
pi == h * s2 in Z_Q[x] by evaluating both sides at the random point. Note that since we need
to hash the polynomials before computing their evaluations, the procedure receives
the evaluation point non-deterministically so that it can perform the evaluations while hashing
is still going on. After hashing of the polynomials is finished, the procedure checks that
the evaluation point was indeed derived correctly from the hash of all 3 polynomials.

Inputs:
Operand stack: [ptr, PK, ...]
Advice stack: [tau0, tau1, h_0, ..., h_511, s2_0, ..., s2_511, pi_0, ..., pi_1022, ...]
Outputs:
Operand stack: []
Advice stack: []

Cycles: 6780
| +| load_h_s2_and_product | Takes as input PK, the hash of the coefficients of the polynomial `h` representing the expanded
public key, and a pointer to the memory location where the coefficients of the polynomial `h`
will be stored.
The procedure loads `h` from the advice stack and compares its hash with the provided hash `PK`.
It then loads the polynomial `s2` representing the signature from the advice stack and lays it
in memory right after `h`.
After that, it loads the claimed polynomial `h * s2` in Z_Q[x] where Q is the Miden VM prime
from the advice stack and lays it right after `s2`.
The hash of `h`, `s2` and the claimed product is also computed and the first two field elements
of the digest (i.e., the Fiat-Shamir challenge) are used in order to check that
pi == h * s2 in Z_Q[x] by evaluating both sides at the random point. Note that since we need
to hash the polynomials before computing their evaluations, the procedure receives
the evaluation point non-deterministically so that it can perform the evaluations while hashing
is still going on. After hashing of the polynomials is finished, the procedure checks that
the evaluation point was indeed derived correctly from the hash of all 3 polynomials.

Inputs:
Operand stack: [ptr, PK, ...]
Advice stack: [tau0, tau1, h_0, ..., h_511, s2_0, ..., s2_511, pi_0, ..., pi_1022, ...]
Outputs:
Operand stack: []
Advice stack: []

Cycles: 6785
| | norm_sq | Normalizes an `e` in [0, M) to be in [-(M-1) << 1, (M-1) << 1) and returns its square norm.

We use the following formula to do so:
normalize(e) = e^2 - phi * (2*M*e - M^2) where phi := (e > (M - 1)/2)

The formula implements:

if e > (M-1)/2:
return (M - e)^2
else:
return e^2

The use of the formula avoids using the if-else block.

Input: [e, ...]
Output [norm(e)^2, ...]

Cycles: 20
| | diff_mod_M | Given a tuple (u, w, v), we want to compute (v - (u + (- w % M) % M) % M), where:

1. v is a field element given by its u32 decomposition i.e., (c_lo, c_hi) such that
v = c_hi * 2**32 + c_lo
2. w is a field element representing the (i+512)-th coefficient of the product polynomial
pi (i.e., h * s2). We are guaranteed that w is at most 512 * (M-1)^2.
3. u is a field element representing the i-th coefficient of the product polynomial
pi (i.e., h * s2). We are guaranteed that u is at most 512 * (M-1)^2.

We can avoid doing three modular reductions by using the following facts:

1. M is much smaller than the Miden prime Q. Precisely, M * 2^50 < Q
2. The coefficients of the product polynomial, u and w, are strictly less than J := 512 * M^2.
3. The coefficients of c are at most M - 1.

This means that we can substitute (v - (u + (- w % M) % M) % M) with v + w + J - u
(note J % M = 0) without risking Q-underflow but we can still overflow.
For this reason, we use the u32 decomposition of v and perform the addition of
v and w + J - u as u64. Note that \|w + J - u\| <= 1024 * (M - 1)^2
and hence there is the possibility of an overflow when we add v and w + J - u as u64.
When there is an overflow, we add 10952, which is equal to 2^32 % M, to the upper u32 limb of
the result of (v + (w + J - u)). Note that since \|w + J - u\| <= 1024 * (M-1)^2 < 2^38, and
10952 < M, we are guaranteed that this final u32 addition to the upper limb will not overflow.

To get the final result we reduce (v + w + J - u) modulo M.

Input: [pi0, pi512 + J, c_hi, c_lo, ...]
Output: [e, ...]

Cycles: 45
| | compute_s1_norm_sq | Takes a pointer to a polynomial pi of degree less than 1024 with coefficients in Z_Q and
a polynomial c of degree 512 with coefficients also in Z_Q, where Q is the Miden prime.
The goal is to compute s1 = c - pi = c - h * s2 in Z_M[x]/(phi) where M is the Falcon prime.
The pointer pi_ptr points both to pi and c through the relation c_ptr = pi_ptr + offset
where offset := 1281.
The naive way to compute s1 would be to first reduce the polynomial pi modulo the Falcon
prime M and then modulo the irreducible polynomial phi = x^512 + 1. Then we would need to negate
the coefficients of pi modulo M and only then can we add these coefficients to the coefficients
of c and then reduce the result modulo M one more time.
Knowing that the end goal of computing c is to compute its norm squared, we can do better.

We can compute s1 in a single pass by delaying the M-modular reduction til the end. This can
be achieved through a careful analysis of the computation of the difference between pi and c.

The i-th coefficient s1_i of s1 is equal to c_i - (pi_i - pi_{512 + i}) which is equal to
c_i + pi_{512 + i} - pi_i. Now, we know that the size of the pi_i coefficients is bounded by
J := 512 * M^2 and this means that J + pi_{512 + i} - pi_i does not Q-underflow and since
J = 0 mod M, the addition of J does not affect the final result. It is also important to
note that adding J does not Q-overflow by virtue of M * 2^50 < Q.
All of the above implies that we can compute s1_i with only one modular reduction at the end,
in addition to one modular reduction applied to c_i.
Moreover, since we are only interested in the square norm of s1_i, we do not have to store
s1_i and then load it at a later point, and instead we can immediately follow the computation
of s1_i with computing its square norm.
After computing the square norm of s1_i, we can accumulate into an accumulator to compute the
sum of the square norms of all the coefficients of polynomial c. Using the overflow stack, this
can be delayed til the end.

Input: [pi_ptr, ...]
Output: [norm_sq(s1), ...]

Cycles: 40450
| diff --git a/docs/src/design/stack/crypto_ops.md b/docs/src/design/stack/crypto_ops.md index f567fe4cd3..f5032f9d10 100644 --- a/docs/src/design/stack/crypto_ops.md +++ b/docs/src/design/stack/crypto_ops.md @@ -238,16 +238,18 @@ where $c_i$ are the coefficients of the polynomial, $\alpha$ the evaluation poin The stack for the operation is expected to be arranged as follows: - The first $8$ stack elements (positions 0-7) are the $8$ base field elements representing the current 8-element batch of coefficients for the polynomial being evaluated, arranged as $[c_0, c_1, c_2, c_3, c_4, c_5, c_6, c_7]$ where $c_0$ is at position 0 (top of stack). Here $c_0$ is the highest-degree coefficient ($\alpha^7$ term) and $c_7$ is the constant term. - The next $5$ stack elements are irrelevant for the operation and unaffected by it. -- The next stack element contains the memory address `alpha_ptr` pointing to the evaluation point $\alpha = (\alpha_0, \alpha_1)$. The operation reads $\alpha_0$ from `alpha_ptr` and $\alpha_1$ from `alpha_ptr + 1`. +- The next stack element contains the word-aligned address `alpha_ptr` pointing to the word $[\alpha_0, \alpha_1, 0, 0]$, which contains the evaluation point $\alpha = (\alpha_0, \alpha_1)$. - The next $2$ stack elements contain the value of the current accumulator $\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1)$. -The diagram below illustrates the stack transition for `HORNERBASE` operation. +Execution fails if either padding element is nonzero; the AIR enforces the same requirement. + +The diagram below illustrates the stack transition for `HORNERBASE`. ![horner_eval_base](../../img/design/stack/crypto_ops/HORNERBASE.png) After calling the operation: -- Helper registers $h_i$ will contain the values $[\alpha_0, \alpha_1, \mathsf{tmp1}_0, \mathsf{tmp1}_1, \mathsf{tmp0}_0, \mathsf{tmp0}_1]$. -- Stack elements $14$ and $15$ will contain the value of the updated accumulator i.e., $\mathsf{acc}^{'}$. +- Helper registers contain $[\alpha_0, \alpha_1, \mathsf{tmp1}_0, \mathsf{tmp1}_1, \mathsf{tmp0}_0, \mathsf{tmp0}_1]$. +- Stack elements $14$ and $15$ contain the updated accumulator $\mathsf{acc}^{'}$. More specifically, the stack transition for this operation must satisfy the following constraints. Here $\alpha = (\alpha_0, \alpha_1)$ is an element of $\mathbb{F}_{p^2}$ with $u^2 = 7$. @@ -272,26 +274,16 @@ $$ \end{align*} $$ -The `HORNERBASE` makes two memory access requests (reading $\alpha_0$ and $\alpha_1$ individually): +`HORNERBASE` makes one word-read request, which also constrains the unused half of the word to zero: $$ -\begin{aligned} - u_{mem,0} &= \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} \\ - &\quad + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0}. -\end{aligned} +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0} + \alpha_{6} \cdot h_{1} $$ -$$ -\begin{aligned} - u_{mem,1} &= \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot (s_{13} + 1) \\ - &\quad + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{1}. -\end{aligned} -$$ - -Using the above values, we can describe the constraint for the chiplets bus column as follows: +Using the above value, we can describe the constraint for the chiplets bus column as follows: $$ -b_{chip}' \cdot u_{mem,0} \cdot u_{mem,1} = b_{chip} \text{ | degree} = 3 +b_{chip}' \cdot u_{mem} = b_{chip} \text{ | degree} = 2 $$ The effect on the rest of the stack is: @@ -307,16 +299,18 @@ where $c_i$ are the coefficients of the polynomial, $\alpha$ the evaluation poin The stack for the operation is expected to be arranged as follows: - The first $8$ stack elements contain $8$ base field elements that make up the current 4-element batch of coefficients, in the quadratic extension field, for the polynomial being evaluated. We interpret these coefficients as $c_0 = (s_0, s_1)$, $c_1 = (s_2, s_3)$, $c_2 = (s_4, s_5)$, and $c_3 = (s_6, s_7)$. - The next $5$ stack elements are irrelevant for the operation and unaffected by it. -- The next stack element contains the value of the memory pointer `alpha_ptr` to the evaluation point $\alpha$. The word address containing $\alpha = (\alpha_0, \alpha_1)$ is expected to have layout $[\alpha_0, \alpha_1, k_0, k_1]$ where $[k_0, k_1]$ is the second half of the memory word containing $\alpha$. Note that, in the context of the above expressions, we only care about the first half i.e., $[\alpha_0, \alpha_1]$, but providing the second half of the word in order to be able to do a one word memory read is more optimal than doing two element memory reads. +- The next stack element contains the word-aligned address `alpha_ptr` pointing to the word $[\alpha_0, \alpha_1, 0, 0]$, which contains the evaluation point $\alpha = (\alpha_0, \alpha_1)$. - The next $2$ stack elements contain the value of the current accumulator $\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1)$. -The diagram below illustrates the stack transition for `HORNEREXT` operation. +Execution fails if either padding element is nonzero; the AIR enforces the same requirement. + +The diagram below illustrates the stack transition for `HORNEREXT`. ![horner_eval_ext](../../img/design/stack/crypto_ops/HORNEREXT.png) After calling the operation: -- Helper registers $h_i$ will contain the values $[\alpha_0, \alpha_1, k_0, k_1, \mathsf{tmp}_0, \mathsf{tmp}_1]$. -- Stack elements $14$ and $15$ will contain the value of the updated accumulator i.e., $\mathsf{acc}^{'}$. +- Helper registers $h_0$ and $h_1$ contain $(\alpha_0, \alpha_1)$, $h_2$ and $h_3$ are unused, and $h_4$ and $h_5$ contain the intermediate extension-field value $\mathsf{tmp}$. +- Stack elements $14$ and $15$ contain the updated accumulator $\mathsf{acc}^{'}$. More specifically, the stack transition for this operation must satisfy the following constraints. Here $\alpha = (\alpha_0, \alpha_1)$ is an element of $\mathbb{F}_{p^2}$ with $u^2 = 7$. @@ -339,10 +333,10 @@ $$ The effect on the rest of the stack is: * **No change.** -The `HORNEREXT` makes one memory access request: +`HORNEREXT` makes one word-read request, which also constrains the unused half of the word to zero: $$ -u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0} + \alpha_{6} \cdot h_{1} + \alpha_{7} \cdot h_{2} + \alpha_{8} \cdot h_{3} +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + \alpha_{5} \cdot h_{0} + \alpha_{6} \cdot h_{1} $$ Using the above value, we can describe the constraint for the chiplets bus column as follows: diff --git a/docs/src/img/design/stack/crypto_ops/HORNERBASE.png b/docs/src/img/design/stack/crypto_ops/HORNERBASE.png index 2779d152ab0ff3c7d48f949ac7b6dd385cbcf239..f59593f70caf140383fc0c977a657bb810758caf 100644 GIT binary patch literal 143685 zcmb4McOcdK`>&Jgl!jAQM#L>yQDlb*kyRl?MD`9L^ORC%g^+!%WF<2@S5FT_7ONc)JBJoidGg=}L(7l6!Wk!@5#E{4EkUpQK8MVO>G>@1 zwpaA!I<-}d1R2>+y-t&v5<(H6iD-pgwrpMb5b+%~NWcC^{LeqKMTo5bwKNv?tK;}z z>o?z8{rkU*UprzE#q^i3i*O$y+W*!v|0&V6wXm{e?Em=wmA|~U9pwAfX)C89t4`RK zO2I0d{s-HZO@D@OJ6`Crd)L}|bk&8QTh;At#Uj7d#N08w`KT_!bK4cp|FVLghVVTC zwDgv(kyX1!pGZIc`F+BAgn`tnLR$MBd8e%;2Q~=LMVK26XMB70@C)(MO=5M~f*mJ( z{^;jwD<3xQmX(Nz$z>?*8>(c)g-0r zp{(|6O*tg1*Wq_wnDMuI7;ynal>1eT*>?-J7d7pjQdARvxjH#_xXJ3k6%9vgRf8D+ zs?^fM&6C-on-|i|MwS1UJ?eY8$Qu|@wcbNSCw^qqLt~Ln-LmYo=(X1i!?|&9_wgIQ zx1{<@IHCsS{$FcsxS~9F%uV*TYnctCd7LlDP=O0Ndvk$ed zi=Ub(?+rE!OMcz^Y!Y_E)$YHKuRnq~;c4Bi^vKJLC*BQ7zF71&&9 zPZOHJy>mGG`(ug24`t%7y2jmmbYaPzL;|tae>J>ymzyIFmThz`Lu40GV36=)YV#cL zexXJ4whR+rVb`4={sIxCvqsC?&ao7wL? zD>`4{yzFb5GZ1UFJl7XT?;~)E+G)0PfWLiCmaoU}@+%jkH2-7a4&##TzL)|J9MNzY zcY6q(!(yM%4_>@)P+Oy2oTg`a45NgJL3X3D^b(kpQ{<`;=%rigPGgCE9yckYni9mJ{ zFU+yzGx`yh^8;~l+sV|}lQNqO4Ln#g&uh$A1yngL3K+#Iupi2}GHp5c zLC)#a(5p`;tm~y+-(0%Ds>of2%lWpCV$B5j)QToIUH+z^-hUX|m=mYq4?&n#$C-Fe zMK9F9U^*j3r?V0r&zkz~fB|+p>G}QOTpm2oBKe3>@A%4x#R)Ah)TTIDw~p!FJzcMs zW;zpMS+2r;c%TD}O1hNFQks;=D`M2XeW8tb!o6ek{K(W~wOsv)AZ>46!=T`=KEKU-CceowCeVmJ8a~#um zbFtrm8u0gw#}rR@q5U&GtGv)efoN3fi?4_SCqA2rPDeC{ez)(5SMTWzs%5xk>__r# z=DTTr@L;5d8+f_y4~E?yj1MZe2G=VyVpn1RYT{PIb+^f9^?Ap>`Sf;!dng~y=nJ!t zeY;;sAk+ePXkh+Z3eA_KI%RDy;)&UEn*!fk%_dn*8HB9q@=4C{v36c(L}2_q{Q!f| zoFw1ZN1|16LQ4aa*!>Q)CV6AHf#CKmeX;Euu5oo%x5yid5M{@jN0z&;{oas+tLTaJzot_m*Zq* z*a~K~GU@u$&r}O`FG+vfyL{+uP5bh6)&Q88Z0TY9?;Q;Ly3Z#n^FJZdCUDOJATilG z;>5kQJX0tK`l^;Q-DFhF7#H&77U-~o+UY6l6nk}(9b@_NdsSO@l`f1F3K70-gBaH+ zmB;GzPTGu2=paLgxrnM>sP!ds$Hi_MIgRD`Es*GGr{SnM-M$)En{WI7&P zvivx)1Bo46j{TxEQX1e2t0DM=b>$wo-ik!Wsp;w1`oiU@xujBS%JzroW2IDPg-g?z ze4p9Z!t3au$ykuss};k7@kM!%of8GGUY z5!2A$0Z9s|))VZ_#`FuUBi{VO??vvqQQuOYZzvUf+BS>h_I@z>W+#ob1dXFr=98ZL zl%|3A-XH=)nJl%hBWAa!B3)M7 z?Qbmd*^RovF+Uaxe|+jC*;=aWYug*7S2dWB61JWFit_b)IhXHWy+CjwKt(r*bEd_z zUR|i_Y|N!Y(??SWbzK=Mc>6+(#j2E9t)KU?y(~gy*k{ozP(d&82CZ1&-;$0Kw% zB5=@nBuz68EqZ@IDak&W9{`u;=j~vw1;&bRyY6cDd_ZCpp-*x~oob1wie`#wDf>>J zGtuhfdPOqpOjcz08+i;9nS9dW<*#*tY)JtQCndUb4#ThexTs77J^jVG+Z`sF-WX;)LttPN&z&g5sM1KV z-3#jc?3KF~{_ZPEzPL0Gh8)m!2s7#}Z0RbfZCz`U)yh>;(Wzf())(&VbbFz>J#5K= zTb3n}&O8^eTwK7l+wo)O)fNQu+A)*VFG$vE0oCa52y#(5pFO5`^n|gMyCdSsrI;dE z{^b@oq{$I?A(`Os*9qK>y}8Ru_jKy3-r6e~p-9}1h8+l&eDnpNEu*UKb-D^iX{N?K zpo~c~EtTGZq2LD)QB`$;EfkC~J18WyYTJH+N8pN5(jlzJAr4HY!iVI@m8&Kh#wFFU z412x{Ox0YzOd#tF9%sL2)6BPr3}Nr@cw!0`A;g`o-ie3VzA{`N)S~LLHJ{g>(YCH` zYimm%hz*7t6PWny`&3!$A@Px-Gczq5p;lFr1F$2{=JaSWc8=khiSQo+t<nWHy^# z*m>Aib2<+_NkVg~FT|46jbo?+!e@r%4npQ@R$s&zMao8Q*XLGEH=BjuPy5@z^@$Kf z_gLjOuclqj@_kFny5?@f^}h4s$l<{==WIEPlP_K+xJv_`dlI5+Tq%vSz|{;6XZ^(f zL)dN}w&IliXC~#{<;O||r&EoOHtz>VITt^mm~vY&N$8oZ06!|pXwRajGo(5)aMO=^ zA>j+V7fG*hSjY{-LOrll2xcA2n%O`nW^47_Nc-Lwn#GQtqM=WQ!KkffJKf@9o~kmH zNX3<*ayxSY62)X|xVpLJMc!Rcb#T%}cmE4}3(8KlXfRLL}^B{H7igq@lm7g_xJQZ z(wI!+mUd&RH?OPZgz_6BRocipKPfkkQG$bum~p4oPrHk0asW6IP@4+*Tr`@Wi$A5Y z039Wc9u3s?mJDBL>AOV0uI>LJTQ6-wB4=bO#o2MX)Sc`Kie3+y+H}d73tKE`mT5Nf=zIRtq;NrdDuj1 z{iR}t!z|Pb=m*~~(6{1fF3_BKyGyNfcQOC>m!hP1sLL%8s1_6D&1ZQ3a<^XI*a9(w z*erkM-Hh22-?Q4CR2uRcc`-bDJw_Zy>=~69u4!VoKMUm=cTC=p+u@I8B5dXc_M`H{QHa4;GU+-6 z7rt}6eND4G>V^d5Yt1x$r5|5pQ11;zAHXrimb9jJ^=Kx9kycO|{8(uJu^e$d3*9AV zeoATHv*@dJy?Q`Wk&1)3t0=uP1@o0_fRF~@=2hlrONFip=rk;$J8>^wVXje4i$(FI zk~ewSoP2WiGsxdMu9PV=wfi2~wewfa@~sUsnRH#<=+9C0oz6sm5r3YUEu_?z*X_-J zDN+9rb0TgqH}!ztScolIi3Rig-TW+Dr$pSLcC3nH`Qk){My{BH4ZxTmwYaxKu6m2# zOWdPNj9%0l0vz&jnvAmQ@xKzgZ68m>t=aTy*$Zw|H_w|0C^MAv_eb0v+*iufrML8-At9)&$I}(xba6e>V@Zz)fg`h$T{;7Cg(%ID#wjsn4jWa+)bU=Ni?Qd z7)SDyn8nxNYGey?O({DwK-@EocU&>Ftv7sS2ECm-2{q0mK&Z7qg?f*YhAX`sZ;zkf z&YRtN;BC*8w9t=|M;0tLrhe=SD42{PF30RfAkyW;S3xvBL*NC1>BtXZ8(-@0DZ}yF zc_@lcIOS%ECeb#1Z%pOWv-kmtZ1~?LhOaOv*2B!x-J#21Rjk8O6#^5|O>ooSrc3w> zyWE*CB{n^yc`zb#(vNDPJNH1dQl=MI)A3OA;$87$&KMSNe+BK+jUH?TJTjqt8DoCe zeVF~{{VzyRFzro~;cn$Du{q(~KmLg=ysPinooGzu;#UTtpcl1+dlKG?Kj$oNpR@LA zMx#dxe=J+)YU@-fmvI-QS$;k#aqBx|uFg%638XnWc;4vg<`GhI^0E)J>kTphT<)#D zG3f|p)I@AxquH$95;pljU{b;hMHUEb_lflPBzxT`DbdPibAAUnIZRe)X|g8UP{+vu zN-ok!fvL$LE_7VI(J%r->_yGpVlHZ|tB=wD^!6DbVHtJE0Ob}e| z-0%Ee#2Fy@1-_BIi7Jay>Ht~6xvw>lT4>#?!Cq*ZPMBS$KhN`I=jnn8G_h0H!SM5` zUHW5!UQ?y1%G)_I?f82GwO@^h33{6(PEuf7&>cAY2PCpkQffqMNAEgS?ICQaf6IDx zFd-H?`StN-9T7q+ZNb$bA2F z_4CP*zGm=ROe5tl^C32WD}G@i;;J72nPGoj#+m5`da=OMZI%vM-}aT?7!9g$IF^(% z_=1D?Q>O}<`Oe=%2fY6hr(-`U8bcpn`5G>bJXW~-qkHPOcG`vRJa82pOMq?Tsl7?~ zjP7E~={#7Tcni+=8!MJ$J0l92{H5G$`8^oapvc(BUL2_sH;Q4F6Iaf7$z1Xx5R=I= z>`_bZ(XBGXU6Faq9yN!w!K;^nW0JW+;^)R3D_kwM+(U1c)hMcVMztzl#@WF5TqlQ6Q!ES4Ot3u z(H;9p#XV`Y8*uKyW7M}husgdlGcw=opo})mP^HYuImrBEdDy1#lhRYK5&Q0??lYx^ zk(l#>OOw?`%%fak^~NpPRi zx=ZxlJanx#?T#viWAsW7Scg3n-RRR*TLNYRAUF7)YcbPmQ}MFNI8mR1Ii$Cb%T&9z z4QFoB&5Og^MFcYeF;tW{WlNhX_tZzB5`Hn|?G3mPIV?JyB%H=M#Kt0I2FXI>NUo@z zERpiWXnvX@%Q8yJPCtv|EWM_t-*CQtMr^;8A-5$+8sU6{;iz#~&-~(ZRjO9P=X7(y8y7VK4TuaHOJq zA@+zvNg&iDb9z}v@Xq7ZSH=XY^qiC#oaNu3DkRlxR_N#szG|wmGm(!E{A7J=FF^Dc zl%*XAq8y$B=bwM#<)O&$e>ciGjr*p*J8dg5dt!Et@0^)lIuiPU&a$vGr(WTe_r;%8RF4u)9vk z@0k{3EV~ng3Co4VzsuacvL1@pkEZC=0<2@5-|cp<&J{I1>@fN!9g3^9p=pcbpW+Za zY`rjkmS7*$nA;qbvslXuv)v6_C8MY~cif;9^}XAA>!Tmzz*k?nWd6LGOBZT!UY&2? z5NsLR-K~r}Fu{&wT265xH>HeUk#4O_2yO1+en6r);Z)#NY3llxe)QRh zQ43D$l^3?3N3YGU&r2pL6e{`|%jfwJz&ve#-l+`g)Iqo4|2YJj%z$#$81HL5O9Qvv zapK>Dz{X1v{|nx==#pi58wJor`ff#(ZKL;dn#Iw{rlmz4keMEyb?B)_5wbkmOCf5G zB-xh<7GB+z5v1Ef7MY=ZJ$*I=!Q3SUks^^02%Ox7RmU=ymX?sLB5~+8lwFOMl&svh ze#&l-bUXa){xfZXAKx^7+@4FMO49kCiEuJg^nzvxcFy6W9sfBS6zd;8xpQfz4-p@meV_%+1Gtx!JU|srOJ;v zi(>$zzTHz=0S-4EEGq_mtK8gt-n+{%GnKixK9w~Sx#^7SiER{}Ee)8h^$op4P}csO z+sGyIEVXL5giIsEYr+Q*+lkXoG~M~{=H_@)O$7Q}56Zs3)7gnBb!+BowW`j!OmFP? z{)k&m*^vTE43}4>@#5!psKglz*2G)aA6m5y9Wr^EjaLML{AV2pe$ICRGLf`4w?-mb zc5f6E#J+tCa{`oJ=M@fM0C~=-nHGD*in2=826P}9zC0Nhb=An1K~A=PY1k&s++cC6 z731mQqUzeB1eKWYi&_y@K{`(*Em%w|jdgGhG^|QjJR=&p3&y-AtHYN$#cVm09(roq zed_b~(X*zkU`}?OAAP$&Zofmvwt??y6|&CD6XBN|-hE8EqGlmBKX|3^8NNQ_pW+ZR zoC-lwr%5M3eQ+A;EAH0aS`S_g7xC;Ny=t7A69+mQYTf-jpZu#Mplc2OuzT7@Q3j!4 zWvq@vMjF9f`m>%RQWjSZ+wQ4$aL$_v&~W0ViB;~gPRgj03SauJS6z42EVOIc3;VI@ zgGoP?5H^O>IL7iryp|lDLFrkim-L_--dqbWi z#ZtDHDKBw4C2MP>3)I-b6G17v(M7YdkcLB+cBTAnk3k=4q7nI|8oehKVoMz~AB&zn zbL7qkFUtp(NEMklawe;AX{d3*8((W!$81&)TnzrrS7J{bghJjcvSc+B!45KgwbqkL zlwOJ?Be^35#F4L*k4!$jY8v0F33;9L+~9*$4iTf&va1ADf?&CEHiZkVj3WA}^I8N= zh`&(SLx`I*N=y~US5W8_NV}101y@EL`A3g$5J20B6R5>)Il>hA?II4It;WiHauN&0 zkg~S1-|-Cx;9A|>Hl4`{m@O7Am@6r*oG(5uj^T7U1Ik`6%ue@(0#nJ=X!L&ZPd07l zZ}-~Sv}8d=$h?*6?`3nJT1k2;iVQNj26*!`e2d#+NcDhdu*I^1y7iE%!wgaZX<9~* z<<(uc#!ARihCH_1Oq5n_K3N{>aU$n<@EgQ6C!vqSCJ zK~6S5H3SGoC8eZhUdNY}B7`y{GR^0B_*>}JZo_M31rZh8uX#S%&6Y`ULT> zIhfXNQJQ)AoUczVAh`PqSrKd74Zw#fd%^J**3WmhK8(0wu@7^~yQm#0-XbrJU6EG( zk#4bCM5jbDOAZz+4VlGd<@K_)s;koDA^w7(<``)VVN*n&0YcItW zF)w-JL;=cX<;Y1W;+{temhPIjY)h8A6tps$l^rv(*+!63W;x^wjncMl zqoFi@d6p?zf$t?V5;0UB@z3AQKy&=)^r%>jlNbBc0u+o{9?T)*^K5t>k0a5#eD14MzTV>Y+E={ zjN%n_>-*hFO=HmOz+6HlxHKTAm*B~jYvwvd=WrWou9%I=gLLezZd~oO*m-su&imUY zmbz2DgjAQ$z>APH4Yl*4LpM`K>Lp{IcO|KOCKgA_8tN1C!O>L|$IqprWpR$<7RKGV zB;xx8XEpceHEH!!!qY4xp$%>@jw4@_fZ?4@#Moq@MxrvsF$C^6K@xt6AWd7R9@ke| zaWQeQ(bk8&0xGnON;|xk2BX$v0O3u{)ba_?(aX!r$a@_C=2ybXkN;Al zW!X{cUix<-RD<=dr+2-$lUmq7H)#zRBMxmV2=APY(|~f-wPpNkruMx zA5uS{EOd!LJ1P@`;!#SW$x2bi;yC8Sm&Gr2G0b`5P(yQoC#B+-$%hhrz2PMQu5lHm{gW=S@iXV*zkFRxS|lKS$0jg2Nc9Aq^`~NLDA~^s>|@`HbWh z0}trE&wF)NAz^bN40*V2v=JzNpOBQPN;=$PoL1H~{xn0m(-LqoV{um0>wzG>upm-3 z^EYSNyds7*ua}{<#5*#bJM`>QOGu4w`!xJO@IXn0CP8FW$zY0)(6Vi2)pz*{Z%U`H z^fZz)ZT4oFABT^)Q9Vh91UMbCpm&j~9(M1T62}gA*^iF-I6K5LN8?H$WjNmi-9gm{ zYMskg72+=k1GrPOo&s%SdZ`b+#Kc@?f8$vv>Ws@!_$prYrQK&=XklVP%Qf z-5wL3n`PPm1jmK52msG_=uQmMcGLte!Lak?RNYlJ1ZK!VX5w5uR!S32S4mdCFRsKe z79*&pzBx6vpxP?_esfA^&^Sj)Ir*Oq6`u+(bgA9rCKo`&_d!Sm}pN6 zc&!@aKuQe6M+&5LzSeusUZ>C2L~5$Yql3E*c>rs{Y!~tZD23EbNJ*Cglwfj$GtuYn ze=@o@@hrA9c*xVn4q(47vqwSaUbFYI4;9Vm69f0|rootdj%X1)6hi9E`h+Ymg>U!q zw+BI`z0l9Obm9W?G?Ul-%4GIj!>O>-3go%7Mam_U0J)_xA*{n@-zEAv?nKz>pBB*e zuefmH^C2-E$=tYPs;36uks4`~9eKUiWk60_L%W zcL?nMPCY#}6{GCWqjaErgpxV$ux$snk=^i_O`BcXvOG;{m!YdKye-7QW6JZ()IYnK zjt9BiTt_i@1hd}fV)YE#TjKID6Gwz2bWj*8k1YlLi{h?&(7Ti0A1 zP#?K{Wr0RuX&1gU6)^$%2A`wcQttJEnCUtUig7(lcM!mA`a3zB)`9 zJX)^974TC-O#%tt$YYOMJuaXk{FQA`n0HGN}x}d*fAi ze_?n6u9$r65mi7H&s{r@|`)anrQ!6jFfR-HBza_Nj+;r z$dW%<)hHE#JbpvD$)hTx{#x*f!o_i7=k$5V(M!_j2NU9yThns=6@=PQVRFB#AFU+b z!gB-^qA@)Q@?r#NKM`7yXh%m!#yin=9*!klbet5-k7YyoPG9xs#C>t^llF7cem>dh zMiX}YGPm4Xh{HZSqWScu!~-6)+`1K|eYCQ@U4x8n2+EkLY|c&@sxr0MI`Y5D zeB{0kAzPPRRe7QF#Pl{a5*TSyBj@SRp655(d_62oZGi^-6?Si3nxsIKwKwT-a)-8!qP zxAF$XCl8X@rs2+rIUX#0n9hsVpiN8TnjT0#KR>^bDY`Y2!hoX78G^isDijJ0_231T zBcos+UiSUQTho3z&P~g1@Sw+|z-Jz6A55(Is)}MIdQhaFj(()X#E~oxnT9^}8|r)% zjRv>LIGxck7L!wQQG!}Ap$|ZyTvCeY1{KAs956rmn=2QG-e11moB>c(i28XFCtU(@ zdZd+tO~|xajrLA7;5|!xl^*i1=EcA4b7h6!{8l|zc7U&~@?8X39c*{!#RP6aVaUBm zw^m8T%o#?5q!>k_;R84zuB{)xJ!edQ^^wT{(hxc1bOKn}v+4SQN>sph_&fHaiR}Z9 z1@JRbT9z2>LuMlE7qmC&)$gB}LH96!mBA;UW+bo`*%I0c{zl2x^ty+v|1$(V{)zlR z6717m=QPt(QrNCy(=Wx}Xl@{5J$wNBMIS>O%4copZsHlWX{T7sZl2K1*SC2f0qqCz zC_S1@Nu`w;{9*X~PSwP7s`1i4^9eI38D}$JU3?A_Tu-8ZALKS3}7UD z+*dID$FTzED9%>pl@vHU!Rzv+gvY&1nTRd0pWQAU{M#L_ENXFp{H^=|k79o+fz)vj zX+c2&F_?r-lVNl!o9Z>wHFx6F@^s^BLm2Zbzw=MW6To4pv?u0B9VNQ{MZbgyiN@}Z zc9DRv6nBC%O%i{W(BQ3HwEcfIT*z)?z;_<+R1=SAMJyN8y`-Saa)QW!Kyrd~!M(44 zH6ZsKkcZCdP3vwfU9Ot(81$l1^V`YMu}2FNh)0P%fP0T_!11V#6{EexU1|)Y#Yi?K zc~+h-4}Y=G4K^tpkq)XE(?1R3F)03q9VDDDFm=SWfOD()V{+_{|b)TEDVe zc2&g6Kfd-ZI!-{xPQfY??f>yJm^rOMdRSN(ac9*5Bz)$f2^%4 ztl0uUq|~cm;c!b5N#Btw^pjrJX_7O@+Nfw~Uo2BP3bC}yZhn*Kgd?iAfA;y$wXx&7 z(f&_oT3;B45X>(uT>c@9;OSF~wdd*OVI-o|pziPm$fBf+cK-27R)<_J!tjvyv+{cc z*D&u_4corQ(;y#i#TIU*TEO8adi7K&n%lhD%|imLZ7oZ%I@31fMjaQn1J>Mjn4NzCgkzR@ha1VvB*XHdT^ECP;v2pr)9{h-QJJf}?!9@&6bkkiQo3 z85CT{Dc6uP6PBbGR*hGL)VY!%L(?i_{8qx6foz;&&|juDMYZ;$}8(k5U<%N_>X>y963 z)Mg@rfQ1AEPf7;N&yN)x$Jj--#{Ew_!w(+7d<+=0Q@*=LrT$^svaHmUi^oKw>|8k8 zaRs;u&lSmGPZ2)m~uY41cq7Sjl5?HIT$ z+y#103RZl{#jEcS+_0t^fq>t-^58f74|ljKQQ;4;(F@CW7!2)Tn*hf35h#c&8;n;< zU_=76McTyob%4z7=c2vo=quB1o8>GDE^OaWcRxAdIdkv`$Qq3JPvB*}gbmy`FS&xG zr064vHVbv5g5y*my$#WDDUCxqQs|KTd1c}m{8}L{x{NbWCkAabJY`vLnjl%rp zMS8tT|BKnJ8ML>!2jsc^5=OU@9jgh}9rkMQ`&W0cI_-lUBL#;&!d<9_51{B zx2}%Fni$uv;-Gwy-~Z&hga??3nOK*Y4~L`Zc_3Mlig^M#oG!=14p@6Nqtoqc#P^l0 z-Ya%`cQ*g;&&|#Ou{mfLub?p&s=75omEf{i=kKbkB`Kv5#nTRGicI^QFZ%FDMOlZo z?8AurTHbwZnSc&?zR4@eR#CKjRP!Rb%xYErdPwE}IF<@Ze^5hHH?r!XM&$0MV?>=_ zg0@R`ciIKQLXnXhl_P?FyR`dnZR9gjQi(k$dwloTvxwa21$ft zG~T278X6jzbOZ9&s|3y)Dpr{;XFlHqr4L>$`u;mT(dY9X#Y1EF!F=zKOF$*#3~0lP&^Ml_=~ZvJatn{uWzdJq!N$*eqKTT0C><{i*yt017AP*UY<+$Q`YTV-FMwt!!nft(Bl=gjfrP%8T7V`V?R` zw6C~_6IJ!vagP$i7XEyC?Z4Q)BdbonczMHT0NxToi=;kn14}8}77GFIDUHVj=jKJ& z1yNyA>uQ5n!%ga>gCSxHr8PT+LhNl+Uz!&|j! zu_G{R*Xgi0u|Q5$R|z;Xwo3n`x1E?XwDTaH5chxfivxoVXF|fgpLGH zB7;O}3f5S%&M*3@LaN_A*bUMfXtePNy6H$q*d}HCH)ORIF!x-6d(OV3ZFLO-fIGXv z;lpL3Q6*zwJ(mldJ!5-LuCK&*xhQe%(pP`F2}%uQ;-!no92>adt5)pmhre>8*j>->l`CyJYK zpg=lar6pVtC)vF$Ch0XHaa)r<)|-fZ*Y7ulSF?|7?03MIO1PYEx=gt(#v&a3w6_^9 zLSsF_zz0csjQQ_hM#wLK`{XjJV3CKHr0U*7*#Ok3nKV^5Y6FDm&0`TO#=7c^j{Ad- zQflY{0vH?`DhsR3_NtZKF2zAXvt}gwNZ48bhcmgOdiMB}GE>Dl5lAt}g@v5SSBXeh zP0}rO*q}A|Q$-P*ANrj_c*Y}J%68lBCtjowk-oRz6v!Q153*nXA9k?%~?fv|zsIk$D=#6CQ(S^kH$usdKYJ03mW2bEyssFJBvFMp}16b$+Ptrfp1CaitO zdJ##R73`d_D^Xg(Y?c?paf`)fIn%U6sc{i;tiBYyri0lk{ws%+yN;li&n#7mK2P zm(2XKFPQ3em6AAnIKdC61Fu>p>1bco7|y@e@Ji9=?ICAZe`OPz{~3Q)-1xhLG}p1ZOxU3P)3dVdaKfi8f%=^YF#ZX?OrudbKi zBSQq;?;K7bydYYM`^MTQUTX3yk%hl%^|uSsrLgG~|FFC@aLv9C`Sx=@BvMgcqo+33 zBjpcP{xZA$>#G_}6h468ers!_#K=lJ@UsrS>M8#7J6E>$3@{ki*+a%x66GmOUUsh} zqDafq|1utg_pjZ^^Xx(E1ix~E_ZywEI8%qi??eW&DRLzS9Mfy$0q5R3VHM}EBz%3d z{`@Xj!2-fa$wimxo}<^<{1^ z+oX|l5(6p*>SatZhaUb+a41ZTcny8DYj&f5sqpd-*-#7kr<+)V#G34Iy?3Mw_zb>( z6peufjgvJSECL8$dN&uY|7CxbT(AUc$dB>z?kK;+ORA+`rm;IW?H|B@D@d+_`g(x;4AR4nf*Cj3^q0^D~nwUnFrk5Z-chVbZOkA^Z&A&Cgj1qHM6kL z`SyrEpaYH6O3H0peKtyT#iTZpAL4I&9UUB0hBM563O=Rtak4F4#>{La-CJ(ePK&Iz zQvOdS8&UiQLULR)=hb+t`6HWuCy*S4@XKvN2Ak>z1n6`m^6;5zKTnT~)0@Y@@8iUa zE^oL{7WviNW$iqZlK2F#mLzTMW8`;{o{ZCIi$TgGy4d`ZT2;=40Jq`uq z7oJa&5T4m|^gtez?r+N<1WnKy(sa%Su2N>Y30@npnY*)j*EX_x{$BtQ-iTIp>{vQD z;G%EehK8tNDl7nhn?Di2`~T%d(<)2Y27uA1_jX9cdK5NopSDsG)r;p3++gjSuEB() ze_q}IOq}{9nFgcgQa!N)o7T0 zbNds5y9vHmt@tUla4M`+*P2)>Yw`E@%q&ASY07#{S+rR?EdKKbn~sgbSVw*9!dzc? z3c~cZx^d5-B3EbmD{fLAW^&KdpGU4MBXY1Ql=weaJZ5?i_;f^CziLdqxKg{3B5%)? zhy>A?5`QT-!d?Ls!LbMz#!V7}6*ol*>ZgFP*LU;$d$&U1C<-A|%z>`iDqyK- zw#eYzu~TEAss2_Am5jbIfzxzacnmURi*QT~6m8otjm)Yx2xO1?!3b3(mwR|{lklpo z1QG#;*_L9Uq{K*_-SQ7G`xW)^r@vm&#Med00BK(H$>uH^?!&&XE*Tv6%=7PbN5=ZC zbryjyMhXW19e_x5|1}_`%aLeVWmuGhp%Odbx>@H~G`@y3?a#x6+%=nm)h-400+c!> z)6mFB$_GOKy5le>MJq49_4IF0gXU)ft)3F32m5mvcgRj?$-ZD6rxKs;9hI;H23MN) zOaaSb@&H1ablsW%)*|qp$RG?^tw{*=W;zVA#X`?GH?SavUjWiJ=lL$O!a%KS&8E2~ zsxad*Sk4UxFCs$i!_cztKsHCc?dnE2qN$9M{fVVltreeu^&CLxSNUE<;N%Bj0O-rb z367xDgR=;8S~=gh#PrXnKGmF={LE32NLevRt5oXM=Kv$DpNb<90z_43;p-}OEAQfm^*Wh zTz~_W@KD}og$^0}w{ZP^DG#*oF&Uz^pMRHI#!eWQ5*oGRPP+=R>Ayn}D988kjU6!RWYiG?ujMH(#tE*i%R|2XN>geDecPMk4+^CG+{YhHBj+fDIru)Y+{4CM&zNGHvADE z06{a051?7nD@8Jx?o&=DsOIh`|$BAbM87lGF zoHwQuK!i}Y4JiL${YWPu*`W>H&-EEaT2CRg@$09W&mzb4!C`I|`>$3#6v{+6{zDvdDmzs_E;VS2I~a|En{BO!QQ{f%{>(!S_B!`xSq$ zO|9aU7~oxD1|O(}CnSZ*x9LX}-n0fz#W2M7)W$8t%ij?Ot(u5Oc-wjq`i1N2^|n>E z4i&n!FH(1`G*7W(2*jta%s-TW%zJ?R^49(TBFYW?>o7CO3Cs_5*>8$qW;A<^6^q6}OC+LgIr+pauc1!I21mH3a{P=v=YwMkQ7Nfwn8UsUX zX3EYE_5}0@b5%5Oa>~hs&x3r9l#C$^$gQA@93hrKVH}MPaspDu;LZl+p$1^UyR`T) zxqY#hJ8jE|c~~xRe$>f&_}_r%W25f=bNTrBs?mO!Ng&BJG>E2JBQ{CE&mUT zC(D%fdUZ6PET!^IIyZ+5;1aqE0v4D%DwAbVhVaMj6P)dcE%o3>I4_C&NN0MMfP9E< zBo@6`yJK%C_}vpjc@yP#ckH4c&Hf@z8G}xR$(fAK!TIYT!#AH1(%UKSun*7S(Us#w zatJSsJl*C|da$=WD2!*xmB+?kQArZd+%giBIS1o^;m6>$#1x244fyZRE+QkjrrDkM zkCdNh6e$v#eY5`(0oN6v0LSy64uRie4CDBEPn62jDEMB$#Dy;Yo4`>RD@1UcMry4V z05(1V?1XyL>T@tX7f=V|Hj947lp{w(J|HvARS3Hjj5vm*mvJ>mpRQA;M`%?WdFOzB z_VZ&Ki9G6x5tW*j-3b`7Aru0g$<>M;pTzWk1mKmanndchxigp@hgxs&_!%Z5_K+0x zo=%DP|3c1$uquZlw2A2m4=h^u8qja6$a5CEXLwNXVV-(u0_)Q3+Z_57AX-T4&`WH4 zjRY`igm9$_AuPyoJV`G5d=j{YrM)WLje;`}0;FJ3SBhY2v)Y#6q&hobFNi@{S5)N3 zfH+1fbzCo-v~LXPB$*t6Ad_jB!dmE#FXnHSwhm;TE3->iK6^Kn-p!mC^t3r(>4?xUAZ!!F?F(;2 zA~83;I(p!46m4?vk*Yp76=>LRoiO;qmEFO$^+^-iP&h_j^gv%gE zj9|X&j#SKnU=>q@rlIu=0w;`X%akoR!f2Rla1KEHoWRm?yXzrCEV$@`TQV@D+13ak z=Q7!GuLU&-FO0=`JE^VY-8&4scgJF=l3Mj|Gu-j}G2{Frz%dTI`O^V=y%s7UCFU4r z`Q8Y8!Ixa+3t_4|8lfP`pig~bXsX=ITWJSO4AsC%9(34waSZz3%Ex`v=tsVMxwW*$yNkr?p;B1=bne z>n%Wf!Pi_ri@~-yv>&*@@)#jr(@QYvT^JwY1`m{8yqoz(WUs^ay2Xa`nQ3K8G-3ve z%lrGTg^r&w%BnSI30d*t*D&P4*OoLsl=cRN{aTFqio(ygtoj=ymo0K7I)48&aPvAV z*s^F@0qaOjS>c$^c^~#ZlD!FmPC(iBJonfRgk#&?Uy5G&HZtXn;7DXPI(}>!x{Ypz zT&s6yjw=UrYF(;j4p6mJ_kJdxgGaJ=Cmq!x=6k)}4G3;djn7ajJ5&59BY22@pQVT16N7C5(R7W%Q*lzHLV=I2dqm66Rdac*kSPll;t!l zlIxRQ~&qR#6}?*0}Y47kCBk}>mn5dA{YPQgb>I+b}=k~q%{sIn=0*aMey z%mMk|zgMEMh#e;a2Z!?~lmq7Q=UaOm-VOQz%M)i|{h5dS zdI6;Qtd5Wy$Y3=2=li3yZPYy~ub*srMuYI~j+<6%_5w|z0{^w|4nPgex-ZQsQHkO# z&X|3449H;}IxL$~l**To0rB);*&m;;w1o&#rOAv@i??D_sXx5czSpSRrvb4B57xth z(cx0!U(ma1=fU#od=tPD!`!YV?j5$>A>b6a2$ylYqb$aO8(|(l{Y&UL4Re`YHq72v zn5qlnFt*TPt~>H$ubHhVsVzdeRfRCl@FXRdI3Qf~#C-_I%fJ^2nk0U~8rtIIlJ2`n z3KV@}*{7AlS;N{5+SAw{n49w_x?6wqDRjZ}D;04f1umE&HF)N_Rz%){J+=G!!+$Sy zu^&SroRdSma)NU=T6h1A`M3bW4o(t3#8BcMm0H$}(5=|uZBkW;&nWq|uCZ=YCPFU` zEYqn2b|vx#pOeO-Au2^#T;Vd!92@}p{eUi&#$DTJFej)QZWOBW&Hz6-ORnDXMHo^O z?d?u&;7WeZTcx)=SK%xRbas+Muh22bAacf-W+=}HP=3lGJUT;upV$iX>U?BFCJ;QB zlwKuu(zvMrG`M!ihiDM~TKJGX!WwPTP2WqtwY;A@6QO(tmQ52pF?4Z)S84YG@%8#Z zBM7?)lSQ-b$35MF_7-|AQ9R`fG(cBT?arJW*akj3uFEhsbpDKyo1*D;NLB#*@kvJ9 z6^$9vcK?wjyD&WbYpX!f==P9bYU;=CBh2n?R)1q6av`m+hI~Yo|M1x&t!#EBUkBg^ zzeI4^8UAH~+H#Lt8G7^EK%Q2&o!5wAj}_hVSpaQ9B{UC(LWB-4L&P|g{bIQuAzYuQ zu9m=Xg|sC#{~u-V9fyj1*-=2$?rR$;uw#Mnrb@cAI5ZB6}nwTlSXO zu(C&XnOWKUcRfkt^?koTulMh-O7(nR&+9tZxz0J)IgbN*L3Wb>M_@uW?cRpVlI|z9 zLLdII-(MO6Qcj%hyrp($A2^xX4*L*dQeUNG2{cZ*GNLv7Zq{3tXW*j5dk4UkimrYz zXjD8fH%Ud*0Ho_o+Ih1rS8x<|0;XSB8{cG5IRnJ{F|Pz#rDw-F*U`vaDy$8Prydam z^hPm5n>Nc=3PDx{E1w&H(ecn^Q1SMT0Q3yd53g~qmeN&RRSm2H7=gF$nI4jZD?+9z zDyoIY7hWBDuyqf$PQ5U$|EY8uC6lP3&V_&TmMtiZe{7)*Fw?DbP5H!M0lZ1Ogk6;z zYWUKXk(m?iD|raK(EBX9l6)%=6HMn^Z-5~Q_)A%1LB24rifi%;}}$$Y&JItyvCRi0MGn>UgGI0m_E7D zo!*j8t&txQwhcs5-EmajfUR+fM}x&L);$SFU7 zGJ2bL9ZMR;2c)BNoqIABGp9!=-tPg0 zs9wu^qr@k-sSqOkvZW|1}#xv~$XGt7n{_4dIIVx6GOjI}Zh>-&# z&cyf{1R|l@1m_c^D;5AE6*_oY0d?$0_#Fo0%J&xnyYT^%PUKGTX!zb(=ILs7Cz0Y6 z3kWF);F!Sw6FF*E1o&)E#5DpD-yz3uU0}tfS1vQl_&6CNu^!@8eD{q!;seNBM7Q}> z`2A-n@;G>nkail&HeP|~LRC9GxmOo6=G#F-r`h!6kc(rt@7OXXUw(hZOQhcNj6XqCwU;B1M#ZuLaJ#k7J~Z>s;#jj z=L=*Fwyx0#b_)bgHWq_02AMyU+#G!Ig{H$wEnU1D(A=rHuHRTrC66BlpxI>EYF1tl zYj0eE^gDIia$JZf0O9XV74B~gJrJG1^k~9P1yd@@4#< z34@4%fN_@63B0yvP?w{@vK67nJ!-4xX`$AEV=euAfSH-a!){f zq#+Zo|< zlUP8UDhNf}g2pY^wMn_^o&bD_Q~;^G!TQHN8lvas-@fnyw|uX6z#ar2+lmdg^8uYM zC68U$1`tuM;QN1<8Y{k5Sq99?2TKp$lTsm)&$lJk*cynU4ta93z)i6W z@U#NQcL14QllltJhHQ;Q@srI2JAe@CW%W~{n%+(#aJ$@i!}m^Sn7nWl1b9B1885lT zrOFXZ_~VF^J=!%<_Sq7*r>VMf0VoY8hYoW=5mofy9);DgR|j&t@kK+-xx>dL=(Yg} zH9v6SxXX12a(m6)Y*Rfo{}5x)jLr*I=J(ZSYaXmgIH3)tOCd%TN4df0K^N)lQ^N|R_q0K_B<=Yas*ab{>q++RtkGP$d6R1t{%tzJV*eq`!VdSBvDElkRCOd~ zJ0=50K;sSoI2b`%lRzAB!sqaF!BMG^$G52KfQ7*H3w#KbmA@EJit}GJo3?&G9AT#|w%0f9?n1r~S^QfMdfz zB-1gV=Ny%|o3E15jHLRJIl#ZtK%zvmEV%tOtbov9$y**`W?>t;Bw;x~hDI>@6)R72rw1NA?$vv20d~G5O|IhD+Cg872Wza&Dg%G`A zE4d5`3Z@x(z#X|F)vsJveX$2kvA*4hErYU{IJGb2EQVyw5LMrrFXKSH)lE5!DlJgo zt{()%gcg9tt&e|q2E<@(0X^J^r2}fVBOSc-FpsLSqCHeLn)}L9+YcUCGm#nat?iCD zRuiG=qu?CrgaV2a6+FQ24V^njWM*-X-hq#I?(5RP<7_{rJe!{u38t+bWboO)H|4-P z-X_Qk*pXZ(N5#)|{gnj+nuLOm8ii0dBgkXbDq8~$JX*Y5iFkq%XHQd^J@ws%?0#E_ z$q1RgjRDW=wHK~U2?H!@Q)=%4@LT$J3Oe5?4~lGRM2$M$qe>cux^gvsjLjFDMn6OS zRR8)Q$erKZ4LDeV*=slpLCc^U)}#9X3?3=Y(FyEgs5y_+qgfw;62%pv7%TQx zA8^$M(MEPjQ91bG)pdz~UmgC(IxAGCaw!C|f}$`061k$$&*7B{c})QblPt8JwDQaXGtl|yGT##PxUmKIGlu)X-6;gV zKpJFECP#MyEckN_#nnK71a7R<+R0whnAOdPK{tb-sU@&+zDh)%Mu!E%j&bb2cGF+E zFHn3*9h0@}>+_;!=nAT+T5cM<;b9u6hM-{b<&q46y zrnA=6Ng~t?Frxm${ye(sx$IjD;@kH!Cn1*_Lc2aRh-)K;+CX+eN*sia`cl+=*Pzym zF=J4JQL0=BOcZisDn0mD`bRR^<$ITvHET~&Vo&0bhQtb>1;+1V5d0i)7si!KJEd8A zQE&(cc zpk^9_E2OIGNl+SSVlswI2-piG(okv%xYvzVVE=Ug=w=t%A^f*%@11<;&o`Zlb* zwTK&DEZy$*&o{J@a&Y}UqX;^ZNZLT2RpYACqsxVj|w-TZ^mC`ira8Mq&&r=pUr z>TeexcJeF+Py%@VNrVmbDInKC@c*nVXp%z$F_VY7_-xenUj?1WG)AQ!U_l*RW={~d z!*g$g3%&rvWTzMoTX*RYl=OT43Q`gDH)aJXL{HYMTYlqvPTbCu(Li%|tZEc4prEFU zM`k8;IR{||JE7<$v5H!bBIxt51NH8KIx`f48`w65FQyoL(2Kuy_)!LB zq5)jSrxp&?qafK^Y<~$EAjbhJ#UE$wk(xM4#sDeC0kx0=<$D{Lacy!@&z*kG!6i~c zX13WtmENZ?{C`XF`Zf7S$2J?N_A|ehKJ?R`%_g)u4$1s+)*lys{*=={&U!$a<^Mbq zx&7x06G`AL|5Ca*!w&=`$K@L~<5S|}^Y&lgb$mtL@etDYz(!PcDL+BQgLcu2C-)&I zbN2T-90?t8g&6Pof}FfKE6Y#N`SHgq#o;ngqv2nw1N`PBef3yF+a`d7;LR!<(toCx z{XhTm=(#5<0j_x!$mJtN-d{yQkt*nY|9r+jtO;lo9Zs-p{BV06U!#8(xE5;G?jL>6 z@A3Y68}#!;93Z0=%PA~TXebMEg}=(f!QcNn-v9X8pYPWMRW5_hN)ErqO2=Ue%lY^1 z)PVw~5}W%Ki@(p{|B%-GckE+E5?V_T_jZ^RP=m~na&5$aXCVFl=Hs<36ovG&`LgYu zKbFl%qxSCxy8kIP`6rF<mQxDNH}N=wGr0 zZJiE$An*3+6BgtY^w!@(`%jDENFIL4eWCy)2CG$> z$5p4cxt<^Y68Zl$F~>qbbOC%MnJVvJ0T|S0>{94d{BsC@?-0Tn8T2GUYif#ejQ>HJ3aRQemOKRU>g*s)#czq>S4pU zoaOJ){_Dm=;gdp12maMKT^A6EaG3O;a}B-yzb!6UrV?nG=3~@Oz7)*9@_*$C{IM9oL?8j6@>P$ zpeN8FQ+x9)$Aka%A;T;R44MCOfAxE(AiH5HIN_2M zg@Ei4;gu3DeMvT6(5XB9s#&OK#h)h^G_n6d8ULXH;*JI*Hw=L)k_yB+LUsHm9FFO>J>u-d{1WF0Jb*8i6H$g$yf-wJIyUX8``PXb7|K=|}4?QlK z{IYg>A;oE^_!RFYlURCwok^^2rF_tJ*yL5@KPLEJ^o2KuG&wWgygnVb*Q?#zgI;MZ8QyDD_zP_GJ4OE2jQm4@kYsS_ zbIfcx%9K3ySP>!i5?!S=l8{H_2t21h=ji`B%`k1>BEU${b#NniPD{r4$^`BPu*5Z0 zom8Ttq4Y5M{dPZs%HK7g%rZ(_7-KEZ~o=bf`;|ywgMJU3PJd3c^X!z zi9;iu33Z@RQK!NYT;jwt{BvUdaR&Z+9Ks*RRwA8S*SAn3k$XhmUGQh+{qZyaOw1VN z-;(@ir1I!15d#?I7nA|b(saoh%x;mpZI&V>w)QC6UpBzMDy)Lb9i&N}93QRRM`t{2+leP#nNn%QX-Szb5GTH%GTDzqHxk zDuCk?Oq-M$^f|dw_xf{|X7t_(e~?2DF7v-|rB=v|9nx+0Sj-|teQqaT|IYK|cuK0`9pN#p!m?arFX2DFH{iP zpsMv2-9fT@pv5J@6=)ZUr~`OfKE?82qpa|k?%tbwPKK#|z8K@NfotHF&=q%! zSHUaELKX-*_nLqv548H+POOH_asY4M3Xw`LvBh9ckAp4#H;cdH5^R!&IrvcFS;Q1SkN$K$!BrKRVzOokznbOaJXQ<{B*GmIz@O}1bzVR#rknowR}#ZW4%PmM9* zq66}y2!kiN1)U>dE1SKaC#oa@4>;KEGy~!Ad@cBm_vTypFfPf8)lsD%3lu`V20)?! z+;L?$zE^kGp$xPoOj&$6ULzWm<5$hC)3;7utu%RX%JO8kjwULy7PiuEw=$Z~WQCPF zVvp(-M7D^ftgz!v+=(jzBt{@WADi=`4hvP6jPC~Wlr0XcGIxzQr08qCDQrq)9nIgW zqIX~Xq$sZ=wdd=4p)hjkX5@pC_G=?bE9|?$G%Gf**Gu+997$V~K_30+j|XcN4~!_Q zoeVk?qSiv+IWX|Hmk@oW44ihBJY=n!+;jV;wPfBaxGS|%&?~Uqo>g{eoI~4leSd$F z49$3NH^e@)o@q1K+_iaVUuM(e5|Z=o-sDnfMSf%Z&5;S0q2SXC>1B#0tu<+Hl)t1Y zMwOmk*s>^luwO&Fcrg^rvf`e@;}I-nsg->-=J3JMp(zjT0?6e>&BAzoZW2@!WyV6`T_m6y6tXEtd!QzD$&{J9>XNss`^g??XR9jNB^K@IdNl|&zalmL%*3GzTd`60 zi*N}~iqOZ~UV0E$9el%Khq^VFx6y_9Ad5S?LD#gX_pmwRi(v5zK1In0A1~ghRS4O> zYmNDCOFfS}JJxDtBj1MUf}N$WiQgJ`gZP10-B0k$-s<*Kde2LHNr&|9<6!?jfVT$fmig zh!b0QfTu}v@HJFEv2ow1R?%wpibqzb&bA0fNW&}AL*b7?O$Vc**J$InZ@H7j3Y3qE zfBeo9U1L#}Dzd)08EQ(ES))7GFx{X_wR2`qz}jL;Sr`@JFFpB*N_PSi zvY)|n5(_wL)-VL>k4*OgM!frDgh=x6PuAsbDyF99xRt{YioADD*k;<4ijXUc_3Pwxq-q;MA7%z`4oeQh^BRc3?TJrhRy091TUciI(beeN(sZ()_!O+L>oLFXDg@fH!p21MXm@1E@>5( zdYCn5G(s>#KVqw&A&|>4wd(s-_5<-GhhK=<4$d)Zru_IQ($yd`d9!NaAgAR~Iyd9F zuu!mH>Q>m#Oz0*y)lfV(4_Y-ShBH2g4lBk^+Xs=Rd*AIInZ{bRTt7(BkJ8$U@5jnE z9xdKJtC~u)$Tg8mH3*t2&wZi){$rC=gkc*rT1sw`Gu6|!T`JrTHxZVY--?;!?CJeb zbar^Mg2sleYl+1$=hBGU$i#!xmZGN3w_%!lLJuuyMa1K{OXQ;O@C6t9np#i~eI7}R z$-ya5E#hD=##Aes8zK27+_(b@11V6RN?1V!h&?-_*tD^gqYX$gaae|JUq0BaarhkVKveVcYfWwgPm`IJ!O)YC75C(C9v(zM z+U|21czopsJ#03ZuI+hjw4Kt4cqP|;y*zV12b)5*L&Xb=0f}a(f-m_(2kjZr%4-bDJa89^zmaebA*YyqSfwlB)!e^yM8ru$-O>1DOFB>adCEbdvU4v zO-B!Db7EDEyox6j85kCe0FRvR;c-7GIN{Ez| z1OH3&r)<|LSVXR&^C%0%wejmI4wAXNq<}}|n>f6o4>7uwm6c^Yr$;wU_DD`3JDjYy zoj*Hhz2e*O^I~5^!NLmtUE0j-_a+i@BzvkecfM;yq_r+zeaB&`ci^QLA9Q;N-CZWF zYZf~)wa>^-o=y4VYg|-kA==sJ(h6_5@0Ea$Kz*Y_vRzWZLxszFe|V_KqXK=@(KwI4 zk2o^c)8Xhx2vT^zmVSLuRcfTL#YXZgT`gEWW{0XVmkD*Rs)-uKm&mnp3#_w5h$P~L zH(j$nt|FC}tM!O)VRaCEwlJ15DYd}5g(SjKR+^G)-n?`DzN`vtfw4CBoVOX{DpeD; zN$`pT|2M3ca+j1p(nWiH#Yi4Ai3I-z0ZYa#_2de8+II;WQKB-8kr72kXtmevwB6HHLLHx-mVxJH5#-udR4Q}h)QPV8vE@CBXT zHDmZEO^125qxC0#TKkCby+~gkby2(r+c)9E2S_Y1tNJiM=|AStQz*@r9h-Mo>G?AR z5ju~I3BwAd#m3cPgTR}GmpWu%5z=7JYdJ}fW|^v!h8F0(+OTg#$b(p%mz+$mLZdFd zeBvw3Yw@N3WkT3nVQOdSt690w=db=80wj&#w7esF2Sp?TyJnAU!R4 z*U@e8g4{)A=?4);Aw!Y^Y|cwz(CdD_88HlR$u);Z5*BC6^-BGEUQ3{->G0|W%W7f@ z88q~FTSNB`>V&DIUc@oEt)A>BG#F+CCwpCRlp&4K zGleTr65fa_7*E_60 zmM@ExLjRiLe*p~$-!)A2kE$fgaouyx0!rV^sJO;gjlhp_VQXA(kXLL{OuptfkuiT; zV~T2cn-aD~k@|Cq)s0|w-EruM^V^aBzd!xgEHuJ#!#&}1muj6U7^gnEtJs>d^P&pb zN-fL2n7QavO$RA>Z zfZ;JBKmVF+=#3~cVr0Q<-=4S9SSV#=hfoH*<+*@?)?<4w@wC6khDu?q)w~_clJw>* z7uM5!Uugrzi}7+JE2b9}gwSmuO#~LP`;MomYSoRamKJYyMjlwUrZJ3J8u432t;J$6>!qe^X1-5l%cbawOLRQ(e<`dyTzHlN z3uY`R8W?uwNNuzgGT1wJC*5|kDcInm;aTY4g^^e(nLRQZ$&ZEaWwTD(l=MP^^N|th zd?Y{}_^_OwqRAVYI7bhS4Dy^e2wQpR^1bxj!-~u9oqz3Vuuh(6HjKE7U~?gZ4V#FW zWtF7my;8@^FVUf-u_8uFamn$>uA^xf~r5nDfGT$dA@-YfKmpC?R znXS3CSMirdfSxf!guEI&ixD(VaVMQSwE(2+NTn4+GOdXf8Fe&Ug)z0Sz5DIE^6ZHz z!cpeVDxnSP) z;^&YOHDRg8`f-&$Pp{rDpELeex^33k;ETUx`eI%XJZsg9DL?R z7?Xk)H&F} zXJ*rnm1~&Nq>w@j^7CI@27T?3vp2<3Fq{hM-}B!}4D}>w{CQmcF*Aj}p6fry9&~YR zaO~F2lj&nUk4_+X^$SBu?H~Fa1J0FHwA?6u8BE)n5Q>V})7S>;{V`S=hD)6PkR4CDV7rKag*Nhqcb|qbBU9!1h%gFBbci0&mo~i;a5w? zysVO`hA)5l5mAEM?%Z2&T7v7|uOSD?BHhohHeY>3bfoN%Pp~de<~IrNlys8fb>9ot`eN|UM z<9d-dSq-1s$~p5GPZj`y$r(w70%di1FL@_F#7p_ZDQ`i!UzW%>tj~W*CN$q1Jm?H3 zGQjbome1t}!;w+N#~oIz4W+u$;tQ>HEI*bKyg-NeT)d+}i^U!6=@s1Sc;RW?sm_yF zz^C3mUhndhNS|84i9r!xE6~!}k0FHbf*EMFJT6M$tl$W0G|%JT`1;feR8I)Tf&}7K zN7ku76m*^^{9wxRAXRN{6mYkMN{&vj8|eEd%lXGvIZI;PCE7nhNjZ2C21XmB+@JC3 z$I{`!(l}2^nJgd3w6E6!g zb1|wzMMXuaq#u41zNm##x$+JVcKu&ge!VpR@O|XWXTS$3w>q_h1r^-jL*SG8!@c%H zpHWaXiaPiP(g5#nfpUuTP`;TJC;;$n7M!{^-sVtkQ@UVoIkeycH2_~@dL<7#g$W!N z+)~aGBv%CD3fycc1<6f>#T`;hn$uAO|DdTgiEV;9(HmfOn&^?#CtJ?w;?rDI`Jn(q z$EB)a`-0a5)lCmKb7y)Zt-IdpCwl;1$Q%i^1XW*7#Ukaw;?lv@?;yEud`_*#xwMhZ zf+&8gfyoc5FqF-#t9v;OrM&Wi`ucj0{XP#iS8cLPScEF@JeHw+Iclp~^l?z4;Z7li zx#-L8s^>T~G(zSM+KJj0aUOvBa&z9q8-0sVopJxZaGeu1+PkrMo6lNtLT5}VPC;0J zQ}a$0L&Fwh*V%n?5@*6eIgKY;V|b2j$}+*>GQoIsU!_q$DBP@w)^Py+;qyk_cYUGF zI6iu?Fk1H#6oXiddKbHH|5C-MhnOwwD)Vz5;sYefUhl4cuk0?o~t0 z1SC%bORB-X3~b8@Mls;*(Ntf?PLD&>qBn*QZS}8{EpjDn%c$81)FcRM!3&@c z+O`-dBEOd0`I;cKGxrJ4R<-aB=w1((Yx>lGvQ=15qjQ(0T{0AYx?>0FmdoA<79YNk zX!&pxvoEFASPxA|Pt2_om77=uCri}F4!3Z=1xO3I^>cG`jD1^&P$hz&$M#DPEC4W> z*y24`@U#~6^reAviPv-Xzp;DFmf5|yGZyNxXRuXAUfn_ZSoqrur8+=cqRp%M5uWRl zYGqk(BJcC$NH?xDlE2(VlQq8p!)zEH$9I_NLhOSIE=Oa^N`elBXi2d#q4oKz{FKvMG8Z3yC!3Wq9_ra6bX#-tY^_C}nq@hm^=^yv zfd_OqtwE0e=@yawE|KiOAL&v;8JYVQ!x*QUE-W|kR_C+y^Qqovkd+++>DdHOWm>(+ z!KBD(9~HvKsj?7qZMfJiX6OqesCnfXmR~-A6k7K->r5~gIn{VElkmzDy*_ORJhhsg zo(fI!5|Dptm2IlcV#^8Q-(svs(J0)`3?CpxT9o2YH1^xR!g+cCKLQ%RpgNC3n-*pAZeK59K8Qo#~CPU z>RcmSSezVFC6EaM2&{$N!xV4ogh*rvk|lufsays?GCpdjug~TcD?qJw)zuc@Qv#WL ziim?dKIn-`aAG@yUac6`Ybz&~VnSs5^5z)W--i8pdg7&hRg0vciq`VQi;NFH)%&2l zXO$Nyw_&;mNOTLiS=V3GJ}0(vuX;@|@{kwhX?P^BC}PYY(29#bw28_3dwz2c%Ot2_ z-}SU=1jV9#J+J+EilAa|?d%Yz``mB7=^5@MBQ2v$c<9**4TKl1Lrul*YHIsIq$9oE zg%W~X$3~W3EbJ802P9+HLY-tBNYG?sX0H!>V=cc)fht7Di*nt}x4?I*zuyAbmcu?$ z8nH-n(XJs-ygz<^$oobN^pY6tsQ2byhoM;DhLT=cMWKF)kp1oe0O1M zFC@|QHZ@2p09;ALlz5~j3N1Q|b}jLY@4fz);%8i&_Q7esI2GBW)i@bHx5_4PX5e||DPy9aILONk4zSb`p zw#TV-c$B^PN&H=Oo3y8!SvALXQ~r@Z-2hZ9qDU;o$pUZfaJS~rWh?Y>E7b0i3nVW& zPOYXnH~_^%O9NFWRxbl?&ptY;vKk>Ijs=+$1YpWXJNRTv-`|ga#G2vx>5k5i=Xt=x zRrJkoO%q;!vz&XllWWkdyYbkc`qBFaOsIJGOkoeb)tVn~rQXDDl^!1oD)d9AFQvf2 ztD@cM@UH_^Nawt<7oZ|SZB(u!kzRSs@OPunncAG_K6zaiM?q<^FH|judf@wX11Q(- z+SCS-!&Q+pg@Tgip)LzWYL0_l@nqns?7k2fNLFm~1^fV&Y(Ze5!K4H;W z2@uzp_jr=Ou;$=0%?*#d)4Z#`w4C_pbrKtDgTxV31Pzk<3GAA1Q+?(S_P2;TpP6=% zLB*5j0T$;nr*Mo1fD8BsUM)8^uFPOBzT!rfm9QE+y{0nDK)Zdb6jXQjk*j_k=MAGF zB&faP+EO4P)4hn;)VqNqpJByaIK7Nn$>?yO+RJBlUfhQj@b z)cp=jz_kP8kvShILD!?~T$HJu{k)DVwW^LZhfrtj6}ifDcPirSSNM=5554>jz$=~8 zeGKSWg<)tP`L`4mGzEG)Eu6bz>#PXJTZB z81WXQvwc}heB;@VjBhnhu%+#O+Op8;Jb0(JDrq*J?W2Z*@i#Rd#>tXyu@qM4YUrdQ z5-0o)v`MpiT3N^Q-U{om$z=poCdo-e6G?(nPG1`Y14w=PTj0u|P&9Puci zrCc!cBCNoXo}Jk-1Uv(~fQucqj$fZi`3a1ygbWvT+3}uxO43pD8tx8km8&ML*&D3K zj)X&ndAh%tKj?9ihz2;IvbVsYRC^E0iZISiq2}$iZ3Fd(Ns7W%ROGRu4jrOr^nwky zVmTAO20vSsZ_*)K90f*-EHKw?+3DX4$fE~X&};5YLx!tvYX`prfaclCKi<^t|5`RQ zyhsKx4<%-gv=4Men?Gsb+R)=}LIw!ki^kh~f>)zuavz0f!i9Cc8C&*tv=<+}%mdVt zZ|`=}=scUeA-mVjA!PT%%RB5<+^_v{3C~UyTyuNUIzV!r{iaJ>W7L&hNcp&JLTYSb zb^c8#TD0r-O~Yu0GXz_BgiV&Gw3LL=8LygTn7vQIc?V9eBRen;6V93p{5xPJR!pu# za6NvO0zfqS^0AX=+#Tit$eBAZNaNYny#G{y4o+(X<| z9^N0TjOW6Bs}QiRd$_K<%3N3L>ZA+FD*ok{loTIuQV5Z4pCz-O0~;u}4o7#Z&IxAH@8+gv4*nyX68vCVl|C zd1Y^5^uT<^mjB4?gLb!f%xafVG_Xke#>s28B({KKdN#7sbxtV(8z6VgboGsGla`zS za%PzHQJUrVz{Cf1C{P|&`{@`XKR)Q2LOL$*8O7QFi zT$xWe$AYs3?}*+4lFqpN4PCfwm;LYu`}mkQ0X~SzW6HbDDbu?FKo_f z%D5=-#^rbY{U@;i?fp5m&^E;d?og<7Hzn0vzY$J&Zwq+hgAOYIvuTFZd8g+(MJ7dU zeG_TXMxP-iGxP1^qM8Wb6BsVbnahk*DiZc~%qY#+5H5<< zzIgR6pbCV(6P69RTiiOGLkDqgPtE~tbebgbQ15D36LcoQTxO}C1DEW4g>T3Q z!qZ;>7b;r0us7UyQy4bzo**xtoaMa^l>yqIG%l4f?=sA*6EZ&Q?@FB~jimDUZa_O9 z@!Wcx065r-!uq_(IuTP-Q-jfW&Mll+83n5Eot5z)3k?7p>u?4~r!d!S4^TO9Fvk)A zp3iOl@=!scSPFN%lDtq+^D5AxZey1^a471nhv#)l$OG}SraxaJLpgd`Xvp;hVWc$N zGOm5iRS5`_B#R)7bOtm_yL1S`8r4fbplW~Nn)nsWSdWA4amv0@Nrx~W(YBK6_3Sz- z4dwj~f8n#hdUiRhqZ@%ZeU%m0x^w1rm>hOx);aE^D$jRkqos(p4#C(2h_UJ{onAwy z3#yuOoX;ni20YqdO8Cb!)L!IwZ6Lo(*sb|*Vn^;QMU)< z7H!jN_1SQku8|g2K-vL6=;fA>Ho)wzC|=qa9Mrrt+s1HLy|bwVpxE*N!oc3wE{c1~ zD|i`%dq#(QsM{p>&Im7~oX(cyJK-}^ECAd}2?1sxAj8t)nJL!**9Nb!53uX`6I0?a zc+Dp0)IZjBC$8bXgp6-gBco>`@zMa3(ClrL!w!J&+lBt z!W4ULt)Yvsv8GJbf?YuwT2I4nRVuwqS)A++yFLyOvzhWl@c9I4SASBNHnp;T@QVW` zZA%)a{o;lXLqxC=^EX4BJ>DCi+%M=#-XjYh31XTf^odG6pU>NW5&fc1tG-ZwMbIM8 zpg*m)(F;q@$taQ~t@QZM?#^Y7WpGv9b(+MXZK< zd03y8XaHlw4GI&yh;!o=ntJf*Lb{wUbfY`W+aQ!Pe(w4{3ze(yj&omyjv88mUAgr< zk8EWMDYHcSrQw$8)zL7RjQqQl#oJvPFSg9u@{m3VGBH+LGloK}5S606uV0{uQ~qKm zCoxXk>zd+XzoNX;c&wOL-GS1^GJbqs^%*F*&tBq$QM#c!H(jWm`kH=#hSK%9LbP?B z<#UAO#HO#G^lAU(49o(x$_Y~OWbN&bYKs<%U!OVBd`0Dbh8#f8WoEZ?R4ubEbXB1k zi}zyeoZX_n7;f%w`!P_yaJq`=e9g+}8euk1NyYF&IV=c7zYaS{>%`F>MU|%68BSD! zI~Xf(sxOHy#Ot88-UG-uiE^OXoEy{9D$mcM0odPPJd6482l6=lPd_Z2Vt6dX z%9eW@DFX|B>^DZd7r z@7I)n(ol8perD$_D6|o=dL45r@;f`7>2lnXD^pF1wR<)4J&l;D0_2jDMUvzYTk<8_{g30VWsC&q|uIyfqK+N9cK6s19beD zr-qo^oS$!rE`n&_8wXa01E;in3(byOX1J@#`UXEi8n+4CuFxdw0Y|=+z!((6PHD93)p&rx^ZExmpBR3#*R<8$q&_It#j`-Sx;WIuY8x1`)cU{UF_6*WYlz9Err7fNjD`N)y5~#RW~emqgSHb+nM|!j&g7NG__M#Q`JT_nnyT?N zX;l1iv^?y&pAjGWjvw$8lQ=q|@R*%QxHqAAkf;4u*A|jow&>yRcA-a+vyeNBY|ASy zCYu?W-JP9=G@V1e23)_(zg?W(v2|km)r#dKC!(_wTM64-b#`Wzajri;jAO7niXsaV z>Zx7DFoUZ>^x%+EJ@Tf2W7s_R(pznS`A2_e47ZJ9dWV~IZ;(5!f+)6$5#KznvBv48 zlg8>a3kEBktS4a;JwZWy`I1g^mGxxPlbU-ZOMNs62>!!tX{A|JE%P20!qouRuqubU z%?)s!)#yGM+jd!tM`4T8xu475P2tB_1rgoyXJ^Ni4!VJDt$rv$@-VFn%Xl_%>6;Ll zh>O6Cd%YA<`X)b>m#Z&o+o0-3j8-_DVmfWMAZk68x9SS6CdmRY_3HibP*uK;v@Tqq z3QLIRv)NED5W;eL?Q~VuEf&?dW##4kM6f3J7H@e#>3FiJwf26t4=p`#0TcUW6!0Gk zGox@(=X%}Pam=Sil_`9zNfM+ijBY<*CpA?GIIyuHx58vq zih&_vGJfMAV@Y6Z%v=0whq_Vx$}((T$)sG+Wux*&86EK;TU0=a9-< zx&w(dH;veHy9y7i2S1BphO5$dKQ~Q6PTCN6um%8Zv+n$`;zg^^6{TbNJl@ZirP3cL ztBi3-#~|Y+uPO(qmJ7;J?c=`CP+-wo{lc9UP}q}n$tt>#Y@9|Xq7u>n`m*>fq3l(@ z^Xy)_wi{|yb1J8(wq-23xCa*x0HT|G5tv--Dq+E+z^9f%;W6>dKulZDF%U_c_Sk3? zm`hLgd#Hb(Bue!9sqY%s-~D*=th(i8i;~@_2%a1~6uR3nvCw|+vUTxd!s(k7EZJx< z+e#6>&w+^w8JB#*oCsX{>-`So^ffE}UkQ{%rN=`?6omHK0{127JEFIN<(SS+NkYft zwhJTgR%%oyIlH#r?mESF3dXV(h)ZQ^Y@RggH4UyNKI9lRgE&k!lU1 zD}qrtP~No>UGMHl1*=)-1BMrY8`?gvoYV|R{2Qyq@o(HW+2YQA1>(C_h~OJ%mf$0J zf?Q;`JwPB2K7s@B;L8oDs*&D#RCNz!Go`3L)k}J0_3=eZJE^t|XoQL(&_wI+#$=Y| zJrqs|+BVgd^u)W}l(Bbu>mfW^{xp{Pk1NHW7Af~%vX?HKAjqa}ARb%IkmhhWV?`fq z$T^fNId+D{cMJIt^DYo}Pdl_kM&&LiK7lX1OunNZ=+>0445CifHw9$D$ccN&w6Q9+z{XCe*P1t-m~iQXAvG01?+J*uL7UiSUw z9Nu%awFS(rHr_v|&xR-v1y;`56}sc`YsWWf-E>g#5`Tt4MQ7|k;d{2;Z=gYfWmu4m ze=231HG!JiwnG$!eq&HdQI+Gvt~*&y^L7wkw7l?I5&;I!dB`*O6ZLba#$z_AE`_3a z4n_9kI+99;qarfMIA4UYsV9QiOBa)EYHvVY?o8$OI?pyl(LLQHY7)1VomVD3ijL!x zAP^@;F6>^$y;l9 zPIcV!aKo}$-VvqW84{y-kH`LWxUa(^h}NuDe=^P$Q%Gmedq*R8hqu&m_|zvj zQkhh?Pg$G!*Bbc=Ye=ev5hDIZ*4znng&&#(TDn{*a#P)VuXXUt(FP+QItNd`*lD&d zux`^lJCBw;QDEW>0%pQ=6vE6oebZ8CvM@Q8j4n|`a2eh1);0DG(d3FJ_BqyMNzWJ~ zY$7=Cra58O$!JblV?4c8f{%W2s<|cnJv(J@>D_3nP?s+ZiK1a{F-JzsskTtZ28MFo z#Qlb!bp5(-_iPL~t9G@h(+dEuVR%@!WK;N(I}lz2XOT!>3QRc!7KW6{tWHW0puvVn#{o)Lj|d<5qLde&6~blsi^7pnLQ*FH`8MQq!#uFOy_BzSX0N ztRL==9=gq$$Hf#k!Gyd-O}_EzY4}Z*akpB3dBH&b#pRWO5+<-qQ|Wv8E}6^ z3PGvUXrLLA9-X|LfBw^V4_~?P2A{6K|1D80b@HEpp!!Bc<;zCV3d{%vz%rT+c<0j(80(qmbm^+xnD_Hdo zm6PYS-|3RUh*&3v>qUtQJyx8e3*Vc`Bnffh#e5c8+XxR2PwLAozvY1#CF*(rLq({< z<5wHsT|n==tLb*T#JJ$)C|XRtM9u9*J!;SMcrkmQs~+>jW>T6?2|8LpJV~_~ULEmL z(@t;M6&-Ud?(kZ`HX;LIAzcizr2*Tir)O|Tf>56|o6u=Tip=0i=fgJlUxge_uZK{q ze<#|~Ct4&)*t&_lMjg|5+UK!)KFE?>`_`*B<^r6(IT2_$1Jq4O?J;1dKR_Nx(u!3`uCf?N;9s7af0!)YSV7-( z+k@*GZcH^6+ad)0Tp^<@L|(Ak0p^0s@VYC0a|KgqgR7S;z0ZB3ysaOj847KgF8*A0q6y@ALe7#w7_JQ%Bbm&6%=Nz;1kIo$jx{YCe~>U4*L zwWt3!eY;WZH@kNYDTizi&&*$-0(JE6E|@8O@hhpgCSfsIqK9}1&!8Ld-aDxEQ@t84 z=nT|mU##VWB3Pmyj}H>7lfl-)O|;Jhhj#LXl_%2?>t=|5>Uq7{&LF&dhC01{)d<(I z)l`Rz0N+^Rfg5{OZDi3xbcw!Om%XwC)dgBO2cewNo`1~;zkC)K{1(0Fdu^3wWn&Zx zlM;?WW!TY7{~uLv;n#EpKeiq>MQ`Y_aBB!?)YdS-mR1fNVuy1&^Pb$J-ES%`g>WabTavm zZ{I|88{G2|`0rovPbuS0J*vGVj(P_xVCR=lMX(5{Z{RK>u)jz*;B^aGD%wOQ9CPMe z*zy>%8~n=K=3GTQjgAZnB8C;aQk7e~dDhqJXRqjQdWT(-SmKTu)=(uo_c~nNdk2@qJE~$AvUm4TtKp&6N|c4GZ0^&i9$= zX&zov;4?TcJYI}YP4Bw(DMaNf76JhnbL)bgRPV2npqEtY?gmz2gN4}Gx*OZi?buF$ zvx1-aP*-yH#=@T!&&uXqz|WC**$r0f+2EXIZHculg^iB6X3rPDY>{j%P7zPwCJaw* zTON&3VK^151YZL)4UC5mw%V(OH7a!Ix4N)a7t)E}fXHb(2KqgeK{gAZzA!8&13EF8 z#=Zj=nCnhXeeL*mrn_N#+nhJT=tR~+cd9Jeb$lp%s&eBP6|QX0g@@T5%=IS?U(19` zCB;=9=`%(002~{iL%^oSj0M~7=5~nqUjb8MRMG15AU1?%G^F(*dW4H*Y};xLF?B!f zoic;JY#GosO@;+N_G0d^#p08^8IWe4Nz#6=op}01I$1|XGC6~|NZ(E_S_Q9XyR9pm zEMRm!H7-n~rS8%Oblwz)+03x7y9p- zRm{DCA%&L`+-kO4|HNCNyCi|~w6B7n4#_%8KTv!9zp_?z%{xf9C$GZy&yu*R(utDx z#PJ#m&@fb;8Bt!^L0<{~l`;@295f8Y&U}*CLpX73te}UdD*@FZsZf)p<>U;ujECgV zxXEWI1|^y5oZ^x@p2s^SdIiha*Uxg*-($PgC;LqXfDqrp5oXxPj0f4f7+TgE_fPP; ziD6#%$obn(SEZcKe+I+QQ9U_f1sIyLsM~+b3dgb1nb6}+5aM+mUzzJXL5D&6)yc!k zq0t$_%+XwA(B|0w5z!lo3QrimH?Wl?)A=Stv=%|Pc(ErGSTsb(5$w%yQ4VDZ?vx=@ zlizRj7NYsu>6iI6QP%j2*5ucxA7zmBK>H%9s;sxcvxFBRt&lB4#6ED znJjHHZ#zj^20#bCPwtm37E9y&L`hMS&V9htyvl!IMpJu+cM}~5;~7yS!ocNh0(Itcf4!B^ zboPMqc$`cTxgyLX`K4W%xMNl2ZE4HFb9PZNZQ4=7roepG~Ag zrC5G6?kCHKCJ~U&&Oq}q!pa?c-7jUTPg1WSqV#Bw@; z*W!LBj*+SAG+$0|T}l^vzi+`?umHq(ta!4`v4?q@(3WRIge4Kr0o>T&Xqfa^0bZQ zF$1*<D8Nw-rlu3%fsyUq>F-;oEWG4W}#0HjpNDHu1I$eY-^VHH-lM)Q)Qvm|RS=7vr>D zgC(jBK?CU@>8rb>TPVJ!l4EeFBXSZ;KICzoE`H3o{oEi$$=RER;^1F&wFGN^9Uk4k z9NUtGNI~P0P!Bq#S>Ni(m=-V(ycOFT=lvJ15DN@VhR%?oS4+=~$|J3l>vrk6MU@9Y z;bnF)(RslB!DQ$4lb`y}lpjdTn7GWlL;8@O){gS>r&>6<__J)q@l-lnQXe>E9oYJX z%MGMrhC(8msqmi`o|QkwJ_pUI$N|a;re%^-kV4M%V?XvGVZoBa28QQND0!yM*sAZf z)53fTc8{Lft5gkvlZq@L z2qNTrNJ3ykux6{cUmH68q48(+HontoK_FC2nWRmz%8X-l;BypZ_gZk8&W)ss-M=Oa zP7ZcH0!4iK7LZ^Et>B5^r1|c0=VbExhkS$}-GS61S~>DtZjU-E<~mJ!8022AHUjXD z7TAxSXJ|a=WVZ7==qt2*(vLpX9~H%r(fA*#jF-Fy8E1+#04h8x7iPuWSR_J1p(n?S<;@gFm z93jw|h;)0OHXlD;%|w@x7{&$un;#={6ue19p>QQ0NC0SP5=#Gd3L4PEE#d@KEYk>r zo_bo+y?|th9Y`;PaMthmeA|E)bDHWJ^R6n2SdR-Gs==Jjw%=5LItIihA13>c%y-UM zTS6hLY~Un!#2)ZlS;yE*chxr(pqg3tJNS!b?4pzb&Wj}-gmxtT71oFs>a%mpU$zO; zlxTz9MFsK)`ZQfUj7JJKPU|T64-5DCO87`c1j6Z;goKJ5QMfuUEdisV;?` zz1Cz2-9FRNh;F~P{FdQbvg|NnX0A_lR{lx>l{=~mEHO>f+hR{frwUgQ%A|bPa>-Q2 zSR00q{UEK#(^y*HO2CnCuXtO^gCO8~ueqF5HeqeiQzB0k#j4qFY&#{V(==jSP)B0F z>MuQTlo$L*Y#7}(Ec3endU=RE<&aSS)>HcAy4=RDEX;uDwQ0r_P937~ZlV&6tsM9k zxGp4J$?&`D8|iFDJ8fdk-fyEe58}Uvqa_h8#*c?*GDawN=oP@Z)jn2@yG13qJ89Zo zeQ)4Os(LQ+75Avp$RZIQxqjyW$PS9lO4_NHlFB3F3jG!iN`Lz|qOwBmqyOq|4AW$c zeJ>&DOn|8{}AKzXKw}advYHIX`rcUbW~St{KfHI zntX45&10eT7Af#*5a0N|A-mZ!VvOTIivjl&axEhkcFDwWvU))1?HpjC+U=v=(Rp_+ zyY|iaS>wuB`Cq#TXxwZ7GsYa~rFuAQ-;9@;K(67r7~J#p;vt@}TuGJ7Ro~TvgH_r2 zj7s*UZfu=FdYsuGjPfK;`RvVuAr(1k#-e?lIA$$Ey7x0N!W)w0ajgPZFr`k%ndEJKL+)Xq_mie*k7c~KW@Ve#^)Ol4 zX`FFsOqa4)8TaY`v;ZNKKYh>&MyzX3V$rIEo;?P-q(dj-iNfAhV?;rZN)je+8);#8 zs?tS-(89~+!RNI>aVo=tp}S8p=`?~PVGLD1n||v)UN8Z_`j2cf>q#ZQFWSVxrdU)$1UBKMx=zW<5t7S0U>yfpQX?VbPD_ zq~CI9z~AuirWBnI_T|l% z(~sZC8)LFUt!1r})&DAWbcjr7&Dig#px{^V!M^WilVli;F)kdnRai8YJuU356pQxr zz*?W0rT5v>P;@YAWAA=F1zV4GA7D3 zV@MqI8}Ru5ZINkqUgBTU8F|ctOEdY@>6=;?Zt$D^nev%2qoS)XzJ{p}0pv)juG$cq z^-^IvR!yvrm-8(6u$)wV3#>=DNR3I~Nk7bPLp8OFZ8ynTV|&_*C}vA(wf@YL+onL_ zW!w7{KZZl*f9;&Tuxx$*UqEU}7quew;m+W&!IITox}CN`LCkp>Fc{k_5~BWq=#PLF zBf63w7IYNY91Y7wCO8%<{QLi@)=vqz?mP$r4?ZrpnVR-PZ zRc7c@Wsr!Zun1^MSX?v^iTP^>d(a<#DLdNcpgbt} z*I$fG$$x|^8Bo;Y$*DQMs15_O&xcphM_Y=SilA@%KBiNjD^HxMyL3CbULiLC@sd%2 zZQnb}qjDqv`4c8V=&UBeXEY-;uK{h8pfw|qx)zqx~CtA zH>({HPB4SH{2KzR@jTX2!@|tLT{^3f0Q&>I z7);nBkP7>XtWE*x=M|E};$^-LR!A|cALxU|5keL4Iw_W&+j8vUGub6KMJtDbj< z*P}5A2IxZnC_gKV%Ig`oe^M_oHOb- zZLe>LwoZ0VM>5uYy?C@F*BL*I__!}qa0%*8=6TsV!ldnMYpAfVr9FRCj#|#aiHaI8 z_zl7=wa%^S7cR09q<e&8}_pty9 z8gUOXvPh9}2^5i<S?%SEciXhm;A zM_I*JZ^QO0K%afaNU&uyopV;(qWDohqi-5`LV6-ojOcZ=DD5vSvpHg z;W#wARxSP|*hDxWXK@n!Pk{9&pi?M+-$0mjID$=^OIXgRpPRRR3MQFt1CrjvgdkeG z73Mx6sbc~W?@ax8K-r;mF9?J!;)+Q+u>(9uMG{LZMTq$FQ ztxRq)`xBR@gv&tuOj$ZB5$1o%%0pGKE;kFBsB7;+OaW_3IewEcp;r>ZIqiJ+JF2iULzJaS;Kc>$jc72Fn;>NVi zxmXcr)DVg|Z0=?6dr%#iZ#avDX~8G_uEbj1B|`P{wq>!>Bt7AUW+NQgY6+<2hF18( za2w5xk0%(thbH?Hq&&p+X>ACrKeETA-@TYI4gNYtxNwtzS}l-0JdKHh-WT2F z{2K&zY*tH$B2V~~>p`E{pFX4bbYMBxzx?6(KMg1a52MD%N4My8`tZ(JoIHZblXvSe zWYGhyN*@IK0-zZe|Jg9_ZbN{h`!cdJBe!;!nLt_WLTBW~o#QxJwe#lZ3C^6mj|0Rd zH2Xhjaa_3#ab`e9&r1S{bTFh8q`j%=1_V10N4Vlw+GgHL9Bbbc^9UqK+d-U+A(PBT ztdczrXqYZknvT}*h!I-;S!a1cf|y!YGXdUW{ErWgddT(mU-!2G3C(PaKh3+9Nik)# z<>zeB-Qtc_a{H6U0nYWD#vdZ4jG^Hz=>uqF2Xr91$_(KZHm6a9(Vg~ei1q1{dzusM1CkciHz;gJ^+L`YI)WxzTaX+Il>&)y}v%iZh zL=QhSQPPWglKySEP(?#3N%7Te)mjC=Pu2;*c+8&8;Ga~+iGv$z%B_cW3>CUVP&V(f z%6KHAoYb6AAxYgRNpQy_A^U6< zeENB!SP5S#QH;2dHXu+?F#93W26UzB(a;lmWO+RP%n0aBm zn471iZs!L8GKn9WMyt=h;PMw(l8O+tlJT1 zMQ6!xTN7C;rt8c6V1(BpS$+(2UkuV&7O3cpFJ=F}oUP|xk2HD_DVOHEkTFxzpL*UV z);bIql`5015nE=HnAh92UGEZKJ7$SY99Yn&Zi1g5Cnn)9$|$Zm0iYE}hAKv|pD zW)020HS%pWBY`S;M@{Zcy-m)`H1NS%lRd>QvRiQ%Hs`#Vmg(=xEz~&{U%H`Yy+Uqj z!g^TQwB+O2!uQ1s4UP6QJa|7FklK@iEQ+*JwtdT7hX!%NE|gJ!8gvld4@HryX>@1I z(Ex;K_d}6Fv7@sq!C3u@e~C05*S5b;{=DwQdvxp8fMaMXDNWJ}UV^$=RL}Iib)m!7 za`NIVaI&IZ)T!M&6A~~Pv2{FknW}VxggT&bI?%rj$av&@A9MYp>MX3O{bg^@T zI7yrj^hoKePd5&0kpU&I3XFyEb)Fu>Sta1C%zO$Yvi_A%OF+b@zP(CF_Yc^=jhI#p zBPQe$O5Uu6_} zJu$2-jfpYOcVpsIwBW)MRj(z7rsb0e#%I?uS<3 z&cS>#<=nzf5aia{@J+_ATUq8e?#9byOo0LN)tgo?Dn*$&1Lr;;*RNP)3ed|9%T2h1 zypgjcM@mH4hD@ITWv=)gelwXh+RAyCoM3O2YWrxBt$Dt;GgK;}mvVJoSJ^4PxfK{zM|{gN|7#~Zuu_=H`rs6ZfB3AJCytG9 z1Wqm^F5offCUBeE)MfMJqWR3oW%-UTH|FU_g2%zyN4-cHI(zo2uc8neSWhKY2u~t=RewX*rTG0eucnw=(rFJ;p*bMEj)|eFrc7EkSjfXeTk+MM zDzeBZB0)NtXOd$^tHzacqv)>vc)J6;d&`={)jBLg;fq;q{_-ZrrtQt}1$0>$-*c@k znRo79**LRKE?AJ8ereR{6Nmh|XN=yY9v69vQiij91xssx?Wk6fxaRREccq+C5vo|> z5$LLHDpD(yL*`jXCF5xY3!vzMy95RepM1K` zxm&(nTvpp2h8%elEvq-j$trC@TEA2WFYe*2_4;KaPlB>c9|dzwOOcdoW6oXc-mW)I z$`@R4)xisUhj~7Z%xKdPOr{o#XMYO;Dk*=im2s^>pKX0U?nV-aSiOXE-8qc~=*T38 zxn5-|`N&L*1h&+c0n11+_RUfJiW14eM} zWpRUvad{b?uUuR<>sbG`Gl@`=Gf};BF=(?y&egc}huQk0XYmjvoM!I&&-9^rFBjeC zi3mu_PCt~bojFUJz*JET(RE%ZDZuf+N{l|{E$Xm&ms^=G$n|e=R-gFq&~J*QGbe?O z*la3TfOZOSVgKzHO}ioDxfcYvw@yPw;Sgwg)$`q!3)5rr;S1%c4z1M}{;le)xARGi z^|#1xoqgz1#iWL8urIoVyiOS{a2qLgsY{#mYq%j!T58?5E_6|%eeS302v0aqp*u4; z(J(*qy^I`;f!Y5QGG=P@pOXBH9a4+4Lm;QHf z@QLpmw?d5>;eQHL53TP1+74>0X{nDL`27J3Yq!LC>u2)Mar_8}XIn0=VilJ{S&n8S zS2!-Ocf}zbq&e4sVfh}%e{s0+XNpdE4!y^>pnF=MbJ^Tz%oqlu&0^lDH_55FE|udn z|JEK0ym6PjF!8NT6t-?ricSOBuC(NAZ1Vk1^~CnUL!f5i6Y;|9p<=oZ?-KLX9$)a= z?Y3o`tar9U=yNb0dvX5ObWEZbrnhMA0{uSr-5onQ=aqKu--g%GT%c-Lx@k z5PK;8yKvY`Vg7wiHzLDAXTn>9Am+VrCthv1XT6EMlcD^K_mhxeV91w2@;HSaew6B` zy`UEN1AY`It`nneoBwShVRt;Nls?!9#z7PpRB?-UD{ur{jsI|3g=LTxkE)m1j+@#S zQYj~^g?cdwUQM=prRj5bxWojn$-!Mu!+;$^W^W~dtF!>irET_}{v*;@4%dCd<^Sqa zKocjiv8o;j87Na~dzVZY>=g!f(e!<|mIKXhdTs4)yEvbfC6~~`*fO;PX#vkH5+Zo6 z6$ed98Z1irIq6t{D0setYtJLx)(Y-Dt{;)+WeI?yk*()?5iuF{#)y3A5WLZ04i@@R znm?}nc_scLnel=Vu27LNWGb|rbW3a7PTjhKE&O^^el>RdRHOck$j zkwq|IPLS+mOw{g-^d(2nyCqv@FWRAV6eqAxXQ-=VYK zH?-D>X3+AjJb&0nU&~Joxe?eHf?+2>xS+4S;Ep;gin@@vS0FcnoX=7hxXf0NNlA1JfuZO;< zD3(4INR}ggo3c6VMl>RHH~>FE9d@U1hI{mSBECE%*D()r8%TtpGbDVQyp7|j1+QW1 zO61n-bQJm(sFzngJYh%cS(RKCcLK;hQni1>WYd}@&v=Z!>k|Z8k<33V-BV03bF4O( z)Qzo3j%QfHk~%81bJ18>bu0Vq(y5=!&0_`5sp`B2y?ws8LWGc&Bmrr+?*|DGQ|mAa1-@|inB|cCb{pMP=z;HvPWj3 zt)ETR0Axld+Q_^wa0dsZN!}s@UM*3E!)P}*hJJFFNsdlU9d}QZ$HJ7At#wi8xmKAk zcF8Oki$xHt`Is7)cpDY5E20*YSXM~-CxrfRcxc?Uvk4+d9aW*a;mP;r^{kmDH4L!z zb2Dr;qO* zdVQX*kds&*kF_EVoQW`0}$`AVUiS_=rH{k61r3cids?Amd?vZ;=cDp`r|A8E}{^iLy z6p4Nr;!|)uu?DrWuV6zi^Sk$-| zqF0IxDEQmBlLApzy_9@p0pAKm%3%46?fYy+nN{&3_I5q8go*c{$+v5No$4Lqt%A-p zem>y@HT8tl`anHnz@k%fZA%o9WP?3Qgoxq5S8JxPn^Mw7o7~<8Yv8>Xd9xBo=ATK+ zah!qwOdlb}I4vP11~1`Tf`WorGKZzlYaNXrkq>4QRDAGT3Hs$QA>~&TFw`HE=N5--+jzYynfsuxG87g09y~N*rc73f7=)0nN#s9Y#VN8esNF{DD3}q*=I`h zpeCJOYkl2eVNeDi_`#vu@#T_|q%|5cp%P@%<$)?oZww=Y2c?rtOiXr{EE*6+JVB8P z>(TBTz>91rUJ5tr{)CsZGT(c!<)=ZF2Fo5#Pj3O;G}dXWRDiLO!=Wu>-1j`+uMz&B zTidHcHusF!7VhN+PKA}RU5cR$!Y4)T;%!;?Z>)1Im}d$UG?q5kmtcKm9d;JRQ`98c zHOL7ZFKSu}x?ayEx#)?K`?qchf=(>k6i0A}(04zQ=v5e82@l-!;Q~GO`|p^m9YaRH zbLO@xJn6fBo>^a~{K4qQlM<>4JtTlAneBAu$C2Vovof}_S9(t_?^_Lu`tXl00~V_; zUBT0U=Z#T>&KJ!XcI>u2i1s#{OW}>~84Cjfj}Rx){&k^kg!$N0|D?Pt%I^{(X~@)eKA=FF77;DnNOXoOWd;W?0# zx$(KGeoJ`o@r_^l)zC5_hF^;KK$5?K8B4LbM0C^#V>^sGIeu?OzMuaEafY+e=npUB z^b8V=C2W_HBWzknXbS#*y9w>7V4p9MG@@TW{61qbPD?`e=qpW{pjAzEHIA5M?_RX) z7O!ibdWqz;(nM3$9#pz^07sJE{zQHnZO?`b#b94wJ7P+NYdu#Ri3I9)^cPr+e{J0` ztSKONz&)j}Qyp_u>@}+mdcpTbG3FSJW+v**Ntb5&RaJ;O>Ue6@SmJNl-?!pa@_i{* zNp8O4necpqm#Hk^mP%CTKnwFlK^$x^$~2r^k0K~%(4#TXDfo|NI2lxAo?>^;kvO2@T-4&4Wp`^?lV22+O2%x$;4%E)RwV{J zgr_6Fx0Yh&Ka_qkO^a+>+31xRrh6t`W||P<&pZ^z^1iRPcM8h_Un2E3q2eudyF&&} z5kLEDD>4YGjs+AoYaS@ZPGsm|KFOpLBEr>Hma%vFf^DoEZ%Y%+B4BM%H~s|`D!;GONF1OZ>ky|Ix+oDQNCl)Rz`K6?8A5_8rcH5Vrr8SG=al#P_s5wXX=&==e?Wnk(qfIXbj z)&LS=IQsf#_ehZPLW^B^L{FTS%`)BPb1)E_ge^_@)k`e-x!z4?)+zX8Bh~&eaejP) z=A3sIBhQ`%hRM^(NVI0>5VH-XkJh1@)-QH3(@CxPS)k0mQjidimo!q ze+WlFwXOwkXeN46;+miOj{Y7QdQqXjTifC%eH-;QFkE4xs;>HK*y9OtZ{p{x{w zkfr>Z>6}+ZS$?-;f%ElUT{V0^fJFw;yb?uF>Qj!Cfl#}vmhJb`_q|O^H5~Y|16hNT z89BDa`$d*so4m4Whgqm}S&A6vx0FtJTPt zxIP`MeJy@kAxHevk!j`>E~@6@VxRS8T=_#?sTdz8S}WFM?_NM?4zgQ>4u?qL=J^bkoB3x_(` z_J9^HRR|F2Xze6$rBLB);mc>a80N^uW-Mgro`Pw*BLliyZt`uF=3zv2t9mTRIUPC0>W6vYsN*Qb{&CA>4;0^yl#|@eokpXRqrg?qGJpeTgTsQ)$>sANt zTmhxWg(6vU%h{;p4|j6&|8l9a3ekKFh%%^B7 zmBqL`7q)H(iYh?b^Em}k&9}Lf3eJTpnOYzm_c-cn$k#Y=+AURAbx$;6YrbhG<88ap zsO`9w7vtSBe`)L_kB3chiVAt@vb|SQp`2J4E7DvAG$SQlJ4_DsyFCm-2$Z()M!y*WBG~N#!ExUe!j9S^bci5-#Zve2v#lQ=SPfYCq;$a;`LBV0GQOl)99jt|F(h1cyo8$m z`ztq#{def|rL&e=r?*1X!R!o*tf^DS zhkCckx6dMrKId-4lY4i)Z*Xmf$>oYAhnk;{P1FW9k?yEUvLaz{L(ti=ydpV&_mD(W z7kGlp+`roE2fgv}_%)X4w81On}M$PXP1-y^)H!@h9)-)Vy!*iEvqGG z{{~T1n8KSZaLG?s&662*EaGXFD1fQA2EJpR+fT4_mF0}MP4}x_$(n&0VUMRrj!Rht zVDB9MrM?i>7DHH_?w>xLpIUuFA;|ROsq#?fRW%>&ZlJ|ZrMO1>L3W~DDh{Px=3u(z z$<_FF24_z83lRM6I2MVo2nYkc_W+_Cp z{Oq9$B#$k&8O1)j^-7LAxDSVLT-6yg#VulGq`Kii4-y9-R;+QuW=`n3+jr?+5ds0c z#-b%$)D-jLh6(WQi6_pHskAiGy2ih%&hdX;Uz2xL20K-gcyo2=xR_S!G**#!AGiM9 ziM=eN<9FWGduXe`^VDE!<&R?W{r2B;H~3u^ac6$c^Aa`*@;^Gn|4>?KQWf)4 zq13;PA1Km$ED^Mrsw(WcKi_Y1bg1u?!tfqA2#vgw97NW!BaVUu9jRNF4XRHl}39 z&2L2OKmMy7%Qxr{g%HM4pUV#hnyd823RaPH2hW>Gr6qyqFGfDeM|-8vv9-G~e5jYO zhy#iOn#-qIFCRHZ=R(17p+f)X{I`dK`fFB}v%z|1irl|se=IqQ6VU|SFe2+4TQ~V5 z;bYMgxf%hqF44PN^nSB`Gv;;Ev=AXH0Cuv426-PK0Be&=TDBUkA2?GfkHh;nyfvKt5p0luLg7SUW!z?lRtQ-Iak2y$!h zRWpl#t=$rFrZ7K^pXO~7X%qLAujI@#c!1m$>O|25Lm{vQ)Zm4dEbKN+FGH!_o2u0( z?kCK;972eUv7lsI_*_%6yKN;fuauhBvV77e!SFHv)X@_18?6Oy+Vurr4?Bpay{nL4I zbPrJH=6r~~kFU%5H)nu|*mB>ST!L?*K)2~&sryQ4#^5+!&|YJJ%xBeJT4gzfRkFuGOq{#&&7EqR83}U%|z29rU@Z zI3-Z<-v$mX4G|MbVG4DCUJv9cY%R#0OeuyLr+UTyXa##Ez0kecJs&ahL zE~=B)Qci19@HY|%@Q1Q%6h2r>*@|J+$#?nWzJX$VwPGlR(oIV znwG0ozges*5nCBW8q(}+sQ=-97tVvUGF)q}<%GF|G&}x&^#D7EN~nRIg4QO(L6+;= z{^>5ySEV}uf0dubjPHz4%~)|Ohtve7WipUyVBm3doQIVYOP$Ws$D(rXPY@@YkT$-`3* z_*i{X7hNJQX9Uv?T-Z?1b(1V8&2m`4)!#DyB;rg)_4DR6wjH=dsyrFDv}5<}iYz#z zQ>Hc`;Q`nD2G5A;VL@rxE3KPI1y*k(Wh59R2jSk~JVu>O;%y@|RUh5PQ&9q1>ms=Q zvHX%#YWcF}IhAfvlCpv^45%O<#pJ!58`J07{k)VvdY64O2oQvH@RC)?^dtOh)q9rf zG!-K5)}H*xOS}#_qP!337jA0z#hB`?zisoU91gRtCgtBAHz3yp|5)PH{1!7oOArnk zWS`j>wiW@{XGEh}+jO^T5T9hT2p+_G+e4sH&13fUm#@k^zVyTlMi;9bWH z9#BUpk=JQ&3NriW3j^9RPZNpm3I#trclW{1|Co_oLP+tLkuR>7H?1GTRmLeTDObJ> zM~9)>@Kbh+i?%O6_#ln+X;|Az%cpn(r6>_Ev#){KSMRBdG`}cJBFSP8-2rJz7aM2O z`XAcqBXxT;$;6)X4v1X(i%LzwU>~IKrBDrHQfM{Eqwb&cGCaf!;CAs6JQn54&=lE zgK1qq)QXe7gYP4| zQ#X!>m=l$~L9UuFVV=J2yerSvJipIs1dTrxOwDyL*@1nqn-eR_Uv1vQVZf2}NDa<0 z%9>RGB^28zog?E<1^qrrUiaI7V9z`R8i_mr`eBQ5OpKZj04BqkMd$=!DV>7JW5~xz zk|X-LO7e3AAiFjia0K;ck&%i+Ymv^}4x+FRA#|DLMpCNrd+?+0Wj5 ze6klWUR=35q8LkbVwy;PgaOtxc~ghrgah#{UN-~Co4s}vp-}Wvbu8YINn&g8iKEYt z5+HQ}Q6|lVTEkth#ICsatxsAxBRNUmM)_vqK_kvSG#N%8Gbm31m6nQShKgQ(X!y7q z^@pWu*s|_S`t53C5w>5Ovx16Zh{jmzdS7|m@*~qf0pWd~R~b8qE%PXIT=E76IP9iW zNeHK8Xh+Wt>lI0Rr9$%R!l;bM)wXjHfV6JgH;@+5M7x|a)B`&u?yKoCfM9IJ`B-0l zD*ldQLkGz&2C%D)O{!qFhR1nrN^~hbni!sxKka8% zk*{H~xC)a5z>gsO=B-Y%r(y?`t0xbztHm1M@>@zg7N^95yayrRQ=`7(S3zYwCM-=U zu5MPt26TXuKN5-gqxI!3+=i;*Xa`a4VgaWqx+&!*+p_=4f{%0hYs&0GUcdf84)rbgfcv)1lSbI0&jCsx<3@gAs6q#D zoaNvTXkNwpK2v;L_esg!i)c?GW1Gsjztjyrf`cz17yOeNyb^|(SL<_U zijx`3Vq_WruZ@L{2YhDVX*oLQ=Kb${0>F7DaSCOC2v0uQns=FSN!oWX7%?X%4>=w+PVB*gVz`;yW|;)_v# zlN@yz)s;Dzich(@!gz9CZqF^X8>>ykbQ#wN0Q`C*YC#wkfgi>w;)S?jU9PGn35kyR z2o=vXuqjqDo+GAMDjdPDT>3zW#Dt|cls)Jfs}Fr>nsV$INPHTRb447~5i1SxD~usqx{?R-^7liLR+6;18gIYpCP@3RMN_Ey)xK;sWuP% zz6DzmizE}^;;{(}*t@@5`U#*b|Fs&7Nu@jrq87omRT=EPh_2)E$!{1fsj+6rUwYBV zqMhgervkcxQGK&CA=$Le5~2EIPg|ZUq+f-J><4gc9U6erC}MvYM7T8a;(HwTz;mrBh`I>4y90QRP6MGU0mQ1hqzZI+R4pls>6i?!Z}Z@5h$_@ImFc&N9}^o9L|! z*;h+$f!9_RJ)z^}M_HT%etoC9E~1TkM<2|Evxcsj=YfwTZrH@dg;ZKz9P-b|c4a;O z=X%}0I~octulS|~TT+nWG>bVB4`WLepJ4 zLjlktf$Qn%oC%gZD)t}uJ4=6pyk8`WYV4q-G3OYw9hSC=pL7^bgij=NaW+qZKuz_R zP|Vdlns^<{c|weMfD(XYmS>E|sRKYS^2OYQU6j?EabOTuR?$haBNpU_w8a{g0=B`T zZXS^X$z+wVW;6)7dAJaV?xmp@E%CFYzKiDC>+!}McJ!i9T%fGT62LX&)?0HUl^D}2B4d7f3qfC)p zdu!V8v{%IWX)zzhCo4*4HnCoHDO`vogR;!F1VWs!o8IY6RdEx@=`btFMO$GXt$mbh z^0&@lr<}S0=+^Dr8>jd7c;2@FqNq)FyvJ`X;V}*ZPfLGvKs^+AT6RTdx8!sN8fJyw z(+aZYlU*xGqU4bhlcTbzVMT>0e^kC~fo`RsV*ooJ8767Dk|*yBi&mQj&^<#qnZv@e z{Qc6W8{K0zh;cK?jyf?p8lL8)@E(ggxsK$NY8u-D%Byw~DFnd%{qilqd4c4(g}x9T z(cOEN1@m{#n~nq1wrPt7w76qB-ERp2XxN zxmnDx*#)&ikry7vD9UWOU<`YaP}$4(nqqc4Ja}+c3jk;){=dPI$Y@F@R-J47Q*Mz~ zOHc=}ACmM-^r1jBYW{)!h(1TK{IW!@K)P2b#7JN?_A zlkX9fMwM001_26w(%I0^2~6dO-W7p3vyb5VYga-y8dy6Wee|(ZNMg)wb0OUo{gwPu zpmRW_Apo(S$XEaAOb5&z9b_#vVB%rFqSGD*He^w z=|I}~xXnNv$S1_KJ}mAOf4~YD_|fqC#s4n*)KXxDWM?1e=qUepN^j}1Sf;txlh6^g zwfpN18XJlm?dVNW;op$G&mrS!kfKyjECmjKR@r z$gOpTA$YyZ?2jHmr5m|nS*b`nEFDt20SQ>*M2TEU3C~QL*1|_Wa)~${vV90Gr0UaS zTOX{vQ)HEapFQgOV_6Em(<3HCJk%93dTGRrQRW&`b`r1mvXqh2y=9VUEG*tU)8Aw_ z4N~+8+9UJ`BX zw6$py(!5AUr6r}Il+w`N`}dsN#^>|>{QPy?&Uv2q^WM+ZP-k5Qnz3IuR{P|z8 zCqcO#g@0*Vj!GA zEm74d%+Xtf<&pxzS<0b?w9&-wnBW7pR}wCIDmv8VN!?N{v{8EECZ5?Z5l%(*kq9ON zut1s=a3vymQQa4Zu~`4Tj>tEs>H|h<4_*{soq2uXbXl`yT-6&0NtWhVAT8l53f`>4 zR3=1BR7Yx5g24-J6JM}LaK7e)It-Hwd`j#uDI3vE(Z@<@R}jJvp=0l_)?GPpE=KAb zW%Z@d>zU0bap^xsQ6<(F*q`fj^Es?q_iEB>fX0F%{iso33{U3jMB>LceXETb-o}I< zZze3tP$!*(+gm!8o$!12P{4=C8-Tp-Ut;z-PaIFHANnvUaQf5$d0_TXjcb(13oKBr zJ>wBG8&}oN^<|A2pzoIjIceXu)`+DeseqQQpM|AB`1MlfV2s_^il1aE6A$%2i z;WFNxv67KIvs+pk60svy>VhW zXpHq2TcEsCRJ9bFQ8dTJ?<+JIO*N~HwO;h^00Vub^5?V9DdDa zkGA^~arh^WK;T~cZXxlA((L^^7svCOS!6CS#!1+~Cxy*Z`vBCm`=vQwC0SIRJbxI` zkqZQrCrt6w5$q^KsyN*~dTkA%z8|J1p=ORg)iTdOqMi@?UlGL^g$q_DRV;#r zGei4jXd)>w9*B|}k{{sdEA7QiOT0# z%87@c2dg<0dJal(H7OjvB@<{3wnb*_w+3#4!n9U226Z=%>eXvkh>FIR++!5B( ztEA^WhMavq5jD^Rc1y83`6FpwE8yLF7~~LV`<$j-=r)F+o#1%Bec>Pqul}S_5d>_6 zYsB1}?#s<|9*s9k^Z9{(z_oUJCzE$84JvoJPTu0j(`wYDvAK;o(%wFJrSX=Z9gT^Z z57@+BOpdqckSqx9K5U6-yG=x5;_Bt`gQpfzd@5g)21VAK*kbl8giG_PFNWJxW{||F zUE;j@j{rOJoPtSZYf9m>6+hQ>vm*EjiMpnzP6XZZ5+U{CoTQzWuf}gYZJTI9mcbCXQn*-WY*MV88&*FeN-)qnqh4Fz zb79?Q&Z4uAvN`}y;nR}GsVIudXE)hnt7Y)Q?KvvZye9?DKRt;`8J8%CEBMUC|1dY* z=w)0BH!-~w5zaNj{{kt0wRX$kfY&}r&k=Xf7t-BFnf{_x-zCm z`&L=T4}Ihs)8ucM3+7_8e5jOk;Z304qMm&+dY$IL7P^cvl){?Hfj!gI1f+gdtYa(V08PdS^PiKP=8sTtxy-UPIo~Bf zSQ4#7B}VLbpCpdchJR|v5H9jnmS#QX=xcVV*zR6uct%478o}P~$Jlb;&k$edQJb2{ zG+jovo*1sreha#%86r|bPtAr_Nn-qQ;<8k`1bZ3b(GZ3Q3L{M&spPPv!T;nS{zSot zTAEuc-=IbfMiEd|IDUU1F*$NXhOQTUTE~C(%8`Q&QAF*2p+=~l%ik)9AD4j7EU6pb zQ*IpH_}Y@Dpu>$gHBv`AahpgRSgdUs-sE@ct3;n<^J!4PT8rp1+&KMDJTyLX_%Q3Z zCwRvXu={R4%>G)&e59S9cmnOrDk-fCw`%acOLTlq;=scQ(9(oCS$)b(pj2CS|IK2i zU~VEYZ_sdw7!*vz;njEWRA?gJ#7u+l7Y@H9Iyo=>Myo)U$aj+Eyf~gdU0X2GSx;h^ zu$_c39ZWXId+G|ag6v&~#?vM;`#3)Wki-61DIAszZQmeij|B`BnpNy!AQ1=bxoMiX zC6(Lq`_=Fa0p_(V^SWsXi;#09uixR(@cfj(If1(X?JCd}#711-PMjPL@Z}G{5t}9> z9A%HXJ&!QBes9gfMJQpAy)HxVsC?khnsLL1S;|lG!Cr`^)CB4>6ux7lYtY(B#@d#J z)prw}(inlfuW9q|Tyusd#%>9pcbDp8f=_DQ?csW#JgIu@e3w!KD?ZbaT11bs$6JG| zuD*E6V&E%I{nmBzw@G|9*Y037$VO5788 zkC3g=ikbUlq_?CB*I;>*>@j6`1P?T4S|0N7ZxUIVc=JXbK-2XIbO>(M0>;t-PCG|I z#qImTu@4`xIdQ7ZWFoVQQsNagwo5p`L`+C1`9rCB@KdpI2O|rs3+Y)-UaWslBA-kM z11AO?Igj%bc!U0fvnxU$X)w|biuky1c60PrUIUnwc z$BgI{I&uK6&=7c=sFt1T?7I&&g3z{dKn@43$UjHiV0`h`a~dD8ca(T8Oz2svo2i(3o9^r`R{ z^_&8&_O5pEsq+k(Jy|?De!Pe^W%if^x#9{LJJn;N63N@c1A{?DL?S&6(xG-Z;3eKZ zgh&9+6@6Tvb*R4*>b{!$VI#64-s%EfyJJQPu4kSRuNC(m%csmBUXVb>-jqA~Dp&34 zDI+Og`-4=BeSFo4#UqAcQ#8u($pVU3h`>?+L_y?6=X&w<6zDd?jG%lml2>{p_W(>x zWaMF}w5}xAojIAJi58A!Vj2`!{2h(9>fpuHOC`ux(B`>r3yYSgJ?L}EnM`5GP(j$ zLfmAsQZ*i1NIGuglT7?eDoZ5T(s-qlG53j2iCeIU2fXUs#G5w4C}-=)KxfI3GbSgB zv}uta(MOAFXeEzkL*L9!AXUpMZQ|zXDj5>l>Mg$A8cz7sKOTQ+G|h&*FXP?!AS3%B zRmwi@=Q7DB>b}k>^B%ES8a*8A~_r#+-d?P~)n?sJq`4Ch;Vf(?SH-& zbrThQkG}sIf-gX2Nb}ZCva}bS`qpzm01w;7^aAzNeN3j+e7Ei_cE})yZVe5PFj4BDjozo6$9C$0_t_}hw?t=#9`<0MnIOgbpJB{2#E;`yp}lZ zf(LEw(1nGF2@(0&=QLBN=f9G=L<~7`jQ?CvA`GZ^l&le3B^h0T(q6odL{L(|HBviL zGZ}XtLGbDYb26zd+$qKArfkQ(MF>?K_toRU{C4Sy{9%z{`O~8%mio{K_`_%Fl$JBc zFSPm3lYno!p5FlNK2_5A9KQq(X%Zt%6M3z5RJ#o9NNvd^c;WOfYp>s0TYGbQ%?@gQ zLPZo6__{~8{%|Xo+XB6p*Lv+@ujoQ*Tm@T6YLMM26Q*xp7X=xskDEo)e6Ex5HFArv z4>G?Lam1uDOZ{y5#r4}NY^%o(M!V)WCB0$|(#(vj@QO88yK%hvY@j=5M8bUW(BzTc z2h%6d{VD=RJti19f0(M5IAg%Gsx2|UJlXBU;g5JObZSba(#0|7a2)py9BOQ6on&Vw zsMO?T2){-qm}s7qroy5uMTf?P_nPU%9dO(C*s1_~bP~8(22!qb+ZhQ0J&8BLQqRlS zo_V=)>?=MZ=GAK##UZF~W%0yof>cd#R2w)L7v^p8QXyaUIqQSuI)YbXqKQ#NK6OUsY zoapTO(eUjqzpjrZQA+8LFa=mW@tI`+m$zIpx+2BtehJw&2fcy7W1%aq9S`tWJS>Tu zVA1SV>UVwVcD031S}L-Yj(YN*(!;eRU!>5BA6`XnCYnaBoW_r3JuWr*gZ)VAk;`2Fm+e;z~5`s4$N+R8yY3(Tl+A zFaAa8ghbjaU&WlBZSi{z|I7QrY_Qle2r| z0ze_}@MM~SQT^xe3|Tyy!9&^M$m_g8D{G~6!bW|wFY5GMUs_-VP^^$;mkd^ z)ruJEoo!f%H}T-3mNEZg%9T$mX4dpH#&~Q z1R_t?a5Pb0b=F~EG7(^(dtw!NvO4WUEA-*o+_7T{ydKAQIFwjE z{^$|DWffp4y+j3RTO5})gu(E_yugtlg3Kf@cUPzu_$opAR5VVK;wnIorT7LK&H6gl ziwdYcTr}@|!hTt#)r4ZmQd zVWPEi?b**(ZgJS5$WI~V=QgLxVCeCLrj%z$E^vP&sHC_%^)n^rIt6NoD3oIxj9G#5 z^S}*27YYAhSauFZ`2#YV_ay{-S{}O&G?&mSc;tISp&zCTRK1kEIoG{EvSjLxHMUp* zigJEUayY++pnLPzu)Tnx=7Wg_GB4qb4`jf8jKf0};u)lsfCJ2ER8kB(Zi-y(1Ju_| zIye6|P0RIx@k7~1i;AbD_jKUq${)zcBau8_BmK>)_p@(i)I_Dg#B1iNvLB5@z5XhE zu!X_O#1D8yqY2L7qaWT*0K+vTu>hLEbUTg!kvx-szP$J;`^F;`7E?%{?xa89fwXZ2 zr?vtO|E-(%27%B?IP5W;WM??pS(`4pGdcGUULq)@Y@>x8a(Fgq^yv}np_N+vn>A0$ zp?JPFd3wE2GaKsd_5C#g=dVSIk48ppG!y=sWhv2PP;b@5D>BRYX)qL!-THK1sxQ{VlDyf283W!oBO~BhRE%7SH+sYJG`|ucm}qE4O<6G;^G1*gQ)aE43y3;z#ZE z7m{`u8GxFed?I@>I@ksBdZ5|MAqT#ZNALC#1n@Je^kiy}S`aHZ-eJ?FISKmXdP=Q> zw~}o?Tc38qiRf1WdJQ>LZA{j9)o(>GB;(+ii z6il2B$IXo$>x{ss(x*!73tx0P)Z}%DliY)2yMaOb>y^6h6(zzZeHc)!`1K3d`qmnt zZedv9frzXhcc})H%T6(%Y;!)0m5x&cI{6MjYpaq8t!t>UY=oo-{Mg#ltG`2sgLL>%MHQsfu7S7psgQA z8FWSo03Td~4$v&+jcAj>qEuo1B*0(16j2&=fMGu!*8?b>HT#?*xAmL-t-SW5KO*n+Z*Z( zq$R#`S?+iaNkdE(43PH&CdM1kX#+bJ&9VCNf>07w)F?pUn=vzRU^(1XBaNA>v{CL{ zl23jB?F@~13;McZKhB%3Q2JaQpe)clPy%|3(E>($hB&}cd zzV&6}X}|6ty2lnQr&JC&_tSHY>AKTCZ0eV^%w<5J8R!h1u~uam z4DUr~yc#+u27d}ki_07~smJxxIHnx5l=gysQ33OTf$k&Y_Ty1Z*w^O12d%4TI;Z$e z@Y$(w_j?V@SLya8p7wBzLP{g^0q@Z1Oa&z+?Iv7f6oue><&3y*#H} zpPOzw?P{QekxODn2M;~QD;`rhrrjI~u^%mjzaW+!ek0QDu(C&IP{H>{uFj446CUr_ z-9_0ex8(!q%Ao1da;37WaaML0B>mo`M(#xE2L!!X$HP}G!o9kOYa4Z7FNv#8rLQ>!puM)Rjgeh zN8uudn!;0e>h3$|I#G+tbsSg7;!TLL1>MMvB(&|YcTp-~D6z8_#KU9M*v;!EJUmyo zY5}>=w%4F_5%xuAg9!!i;lW}{gZh)&%}_(>`uq!<%mJvo50DYBj0wX_vRR)FfvloN z0g=E&qf50MZ+}mbT}?id|KjWFP@pI=KV*vD&lYW~3p+h7!5K6)?z>!3GNCQK27CiN zjv|#6coTJ%eK9eIPNX&F?)0L^-fs96pX=_$ev#(eZhZyNI@LYp&0_3H z1TJ1guBQ+eAY|lCE}(@!FIC8=e;a zwjS*`xprj?6?mRN3eg??@eV%4%g5i9ZOOkT+ z01z3NJ%}C?dx4I?>>)ju4D3_fPrU9Nh6Z_2p`EA;WhzJ8)W>l=`AL>b>+^Qf1tP1`M%K!%j{hUldJ7CQA zYRnyM6hLbb-LuxMxT`ZMnCkEcYgJDiP{Mc7Vr+vs!vHg51V;PQOYkX!c;cxO>e{MR&>hUeUb zu7ynH-3#HQhEJuwggaXIo|?O)bdxp8QUg{P@+2uX^2f~lG+{=i$IJ1Y$8#&xlx&MO zk7;d6NQwh-D*1Ht-kvwI530XzpQOiruKxbK{!CwRWj$ra88j^{FtSZCy{>Ac_^aKh z69*}msW<5b;Wt&T0oPQcP5DIX&mKP6Jcp{Rory~%ucWA>){_X56KuF>T>AaEvO~T- z)6Xur*_)3<9T%*Q28=%g4KR)BdFPH5da^qtNqpryQF1qCxCa^cs*2yo7-yx%iuNvl z`{aYBJY(;sp&5d(voh7mfhaeX2PZ+o!q8WSqxhOxq?NaYI2l(hx`ZX<0xH-w-W76N z#0A&2<1^nPaGrusbKAo5*aS^Z1y)8oTd)!smiawm-$yz|N2J|Ryx#OBb&ub zy_EcjeY{NRD~zb`8Bh`nJXy*4i&9)nInjRQ@Kh}~I6jVE4Y!ye)z5gKl@)PbrLBl) zf#oJuMyW2$j#EHNH5|!<-}WVLQI}nv2XA3HrryzT@;FaYXPr8%g*5rP(e3o5_}Yi8 zFp)Qpo35FaOmjxo{#++ymv{zuLiv9(*H6@uq^&*9*p@6#Gr}Anr6aBy7evj034*1<7GYN`OM!Y)Golkl=Smo`sN< z^N_zV730FtqejX+(G$2-I615F{Wd2vU!IujjONh&-dc+^e4qP#lE%)eSW;Z|0>2pz zE{5jsO`MJ5X)fcP@evI8%S<}{mWoKs_IfsLfl(lsZ1ZYeaSHE|> zQuO4?w;H^OU_;$op4zg5RtA2+cSlH|JTDTSD5T^!&E)RPK3BSY!j|st!kTh7i)~J( zGwX}P?`~W$km?A%{_-Y85R)zHInm)!lg44LCuQcG^E`=nl-oJ%n)S`(>OG?K&oo`* z5?g-jHJ3U-Gc;{_ul^X=N4IdRzrgW0H$tQviGN1v8Lil z4z#8Ax_jP$*=2Y(e=}FEBdcb>oEQETogf_W*_Ny9cE{B=q(wF;>rwO!I>q*nIek2% zd?o2S`>e5IruO0NIR2kZ+_r<6xo_HwFOcagCahYH4fZXU8pRBwm^5R)Hy% zz8`0wJ5y*QqWmSW&?M$LJXR9*{E;?3P|sXGH;*fp_IkM>WR40xKT%D`_WVIpF$b+e zJI*NjiHV7wwMJBlUXINeg~icF56i2p+u5Fn){S13DxI~Z89n&eK{;*=^n$;7kBEeT zmvV)&6PQ>*@;w7pYFyW5qloAfmcw*7*A;KN^-+_1f6Xg=ulXEp^wqL@W-y%mZPnXm zA)6RXd0?4P!W4hCP=b3iYwDVk>}?mjIK`FK=Zym{nV@Ebb*XZgj?Jj12UCh+tMfRb z)1rIwIr3ujxYMw9w(NAy>pC5k$OAV_Pb9vj_jT57ZDgY1?=CKiZu?^Yu4#hs7;_LW zh%;yg2E%6*@tEqM6BK0!$h|dS_9Aa$%)S(r8Gz7@yojpUAAWZv8bH38k+7x{u{Rj@ z!f1^sR}wy%zD|c)Ne>z0wDN@$qYRo4*T4M0$f2lYrOK4}C2d~mL}l|Mb971bgEL8% zBLex$gW&_$HQo#l56d>sbT1`OO$s-H7#B?p#ACVo=uxEqaIuiIxMh4xfetMbV|?t} zHD2kj_18aV@|Uf(BK93;_c*~^+Ra@))qTM<_*i?GhI(IXuahQg!ckrRk%{Y9@CkKX zSlG&jyPb|%dnbId8Hq{ww6t8q&NTAQH1=$wbGY~E&zw`^PC~c>MuoLN+BD#p9}qv5 zkA|1uz6ZW`uo_g81NpU(2pW)ripY_Y1fT6keNh(23&; z0@EegJd|UgJM0uo{6$jV?LqI;Yme(*f2NID=xEfpfhiJ2z2%)1Ffe@?Gh_KlBY7bI z_I@jy)6E3ZT#517=avQwY!*3dmOr}Z1)k}A(w54lz=@0hS;ElN)by8_>DyvQqh~e% zyJff|gIg#rG~jCNM=t<&pTl2O`!qkNue`BJur-?YOtjiRQQou4z4|9HE09ARY=|B0o0{Biu^ zrDfL&yA2pil{|m4J8MVHr_XL>#L3!l_8=^K*~!t`0}1NiI?|j2Z|0_7Trd0bb2!H& z!Ma-gxpq_Da;rgg1%V|YtAKOwV$~eVTSfN`l#G2=LW%i_$$xJu`CyRPol;`KQ*^pI zEH8VOCKCC;R<5=hVnQAVlHM=u9=9$gn>Mz`54=6)QdIlC7sHOR- z=m4`wV)miiWEzti&FdcGnY~28UGu-{lTWLbx(!EQUp7w2?fNw;6#3PI51Kiq{_NRn8VP3r54rGh zmw1-)<;J-4HuaSfJyRZ5a<{9G4-%m>NtRw#(Wj4R)b%y?yjI=JsOLxvd^avPW8LPS zFbTy6q@|;TGny8oq1p5icv=D_p)N%7Z{%!lVfOqAfHx0C9zpk|OVJu? z|C8wU(glr+T*lYBurP+Wni^XYE}U6sHl0TZ^nI=;z=or1%jInX)Wf{tNVl5Z-9V>;zKkcZn7W1Rbbh(W~{F6(Wza7ZK-X{RyB9SYOr@0p2O2p_?^{& zy<5-h;xxDGb+jpQ%5r(J!tEv$wL;l0tr$rNUa*O_5EB#MP;qr%gf#;^Tq3aDd zVZfjKVQRaqV;8ZYumX8COeZANc^Sh!Ulbtz^_e}D9O$$o8LQrkxy9kfjD|~p%)U9# z-e(}b?y&$mhzgQW;w?^C2;tT+?VlH9%lQk73hkVw1_MC}zAwk|8aBcwgg?6sgX z2;hkcs8iIT0;=LBQ+KPtf8a_W@a97n($zbntYb7B+FI7muUm?=tvs7_0R!Sq2L6Y? zheiwjJJr~CR@)2M9`p~RD?$TS*t+_Ek${n8g`q6}V)K6(_lDqhRtTKJ9r<^tt7JPw zpczREuw~=zng2Z~9Dz+AW6e$V9RT~h-`9z`i6658RJ8XmSpT=fa~%m_B^n~qY|s$N zLa+?>KZp8b`I{qd`47swfE#@(XM=ZJd8`5^40;Q*OCr$a-`S!<0C-{jN`#pdn{Vx< z_+KQs6JF^-4g-Qp-#HxuEGx47QBa-szfHeojTkM+aS?=v?%_W=gQP(B_kjQhvF81o z*`eL>K;P)2=8i0khTx3l4xcwKf8+kv4ZEEYQUMIn9(K@@Z}YD$L|p2Bi1(ko_a=k; zh(VARxmdmHWeS%2im)j8KbW$${5@;2q6N09lw2nblY$fO&781w?eQ+VxU*}8wlrXf zD~V~dTRM<@2$u2xjA_A8+Qcn!d{+T?L#nZ|n6v*$zp?^=b~=ahp9W!G6!*dw%eQC( zqwVJYE*|Wty~Samo>!)sbMI}7lsO5-B99<}-V-=k{cirq8ayrVCixHbYgabxq+(SQ z;$VSF3ENCFl*31;l$4ZE6-1MvTcVKR_*(pbeHvR_-+nBaA87m+-}XRgNk8B-n^ceo zV1|dSzyVTG^6ZAFog}bo_!8P2=cgMxpgH`Li8beF{b>Xdwd1>h4V*wPy6?OMo3+5+%RYIJ18 z(B|{Yu9FOAo3q${9MFr}>$=bH`6`lQUu8(wv^1uBA&GMHO z-3MC`-t>o8V54B`u|>%)=KW8DzK_fWMC2ZO@nG8y(Xz;mQ)YX{+X>e@(%K*jS!$J~ zVp*D)`E0LT!26mfcrklI;pT|DJmbbP(PDs^bKe`ld(EOdsBJH~%QI{l=kGCjd#Gf6 zNFLCu^Bwv)=z0wPyGRNQ;Tc4%dIeNEf7=abAbQUH2bmznftoM74MK6^uf+twm7Oi* zp4DuLR|VR*SRc4{WA}6@6x0fCmrsT+|2`60iVh@(Ka^{Qcd>%LKn)J?A)+2vV5<=r zGuVHynfD<6#^N@zVac=wJC5B0ZnF8`H7xlWBo(F3l|4_QiLUMlY5OlbQD)aVwFJQc z9G!G=-sm&v?U=K3!?yf)=?b{qOO*o{V?c5{EP%}jG=K^V`q{H*^34MFdvwH*9VY!- zvH%ggp@3FQzy*!IulsH9gIPBpzldRY^zk-sk~C(23N!Py91bFhAe4kx^h-Qe{Ol8W zzIe2>*Kqxe)nbnGN^p@$|kW zxAWe)4SPSr8<&{m@6R*J?hoIp1%P{FXc3Q`4*4D07$A8FR;|2eRyf`dPQ>}Y3?HnI zygW2m*A=>(ralgY*I(}4MM?t_S1$c(PaTSgi_yRSEzn9tQmHm>k+oUD? zfr;Y;t*aGL7~@050(+h&Lfo%J;r-L|qHy}iIpig&t+5)e;+in+6)Sv-f{5m~A-M?A z?hKJf=+1lY;vDv|SrRU)e`7H3W#nZ`y3D#{Kv97!Y)o-$=FLg}L6~5gi)pEJt}}l^xgpJvu8-IlR)?0*td+Vz;(Ap@;?~^O>+1cu(bw1 z9vdw03j|?f@+eI4Aw@;SK)8JF3==^qh-iydNeSPLgE%#p%w|8JW8Z^jY? zF}dv#kKN`3n8E#>d%VjgVcW!PTmUs&rHxQ)SC$stJDEiyikS58PH^K%OaoZrksytC z;J-21sJTA}{wMv7WkWnFdg*BXWBZX*a>~BtGultC2BuO7?V%$u5@op8j4+f0fl8N$ zDC-Lwe)~fk9$v&>M6o@~8{qSWM6QTJ%ffWRi~go-dnVs7iW^2iT zF9F5e-Z(zWhPv>oU0xTv&|PBRTKHD_fw6?T(|EVggy#Q}ay76`(zLQOy6H&4iG` z-$y&|WI=}fu=cGvwzTk1?ft9=o_lRBdvtrLe`wm)!@DEC;V;0U;8i*t$b|Y32<>KN80u(6V|MKIe?W7R$bc_|8UR`;PHDj5j?kBh|rpcIe|M^bG+)~ zOu;4gzt*R|bwjb~E||}0p?YjLow%@O0zrj;5580g zKqqea)qC*aGlqLi5zM#C+x)2mvT~OnWo`|AzZA8weapth@171?+z(E!TX+nS!8l*f z?7bV~1pD-_J{?I4LLR?SRA49$S_t4eU|F?eN&gm}v%!;j=O?yP2ZT9p;lIwcp|g}e z$ntRcgTxQTc9BYX4|uIAK!bS1 zOGdVH|Kg#0f6H_>v}A)^;9C$NS3CGxFz4dT{}4K~pxs6g3+5|2K7AEyMJk%OeY7A- zQywbn808MsdA~i2$r*&}4o$mENd-@q^$ca%E@yK;8&$lEZ4jIO)Q8)|AoNknloQH; z^YDA`{#VUIG!TJ+a`*xBn+@ZbCF5fKD_why0dhYWEAV9Ysj}TM2BD05>3`|NJ`zAG zLbi*D(ibJR%X9sGVY~L84P;8mS~WM6EOGx=tZEFC36EcI>DoU zKz?(AtRxii|Kbl2-+P1PK$XjuEJnX6-xMhk@xNLtp(2Pt7-na%AhZO*V8~y1@Vjl7^YYsR>CtvhR)R2(ITa*uOb> zOb?jw$fwK75N%vqxaI$eMbO5MR~F<3w;5|xcJ%180>)ym0sO^W`sjf~(DKo4ng>=~0? z`#?})u0H=i_!}4r60pM9Yp=eYG5jxb-Pw2_fU?{Bu}i=cK{OsV~Y?DCWSq|Ehm{ltSN*C&0-rJ)*+hhELmE6Z*AQgyL zySsa3T3M)(--h`;YNMC%Wm$8(<0&sS?+;A+kM09&!zRo?TM3!;Hu4F>&iz%&D9r2q z8aY~p6BlQ8hiWviyx_e7KyQ1&Ls8(R+BHwUhS$OOj z!;;(l0#w)~QgPnBH3b#06@VKd4}esy zZSSrJ7_88GPRDgCa({YD`NQ@_SCkj%8*_B#3x%H3rIPQyCz=2RanvWVg^D8CG&A&Wd}ZkRDK;;6#rBzcVc1|Y$(8Fb z`=cyP7D4laBnaNd=`}u6+Jz(M=)r1l3;{?w&316rB*&Bx@!GWg$N{IR%Jt>SX5SOn zVs3Ofx6YsriXikHJ}~SX))oMCTcm+g$Xi};)9^O!%>;@n?3sEEo1#G5lM@N!!ph;a zmfR#{HI4^Q-?R%wE-t(i7h9bcUw0G&_}WdNwP*ZTabNJ05AR<XGf1T2wZnXWUSE-&^6?wH7!aG?k=|t54w%~BM45d%-c2Glk6TBfgQ z>e@>IVEAtl5Kur-^F`rfZ0Z$17&uwNa7>(=6DbZqnz}{6F*vxG5qE7^Fmd!1G=|e` zi)QA%Gr!DDsI%|P4X*>_KY4%p_nvcKnMCIbLzLodp2)ecju;rw_k7Ce9_0C^*{bhh z>rdkMaDCQb{a8zQKOpT!q0o@Q%3A})FV5qjw;qe-(>i_y@cD9SiSpqe3nAVsCjRta ztjr;d@a(&e-$Lm7U8898AY_hHj_Y2^0TUo64z6d@* zH(BaAC?0b}bf&=x^np|IkefM`efe71WVL1PvoG=xs%z-POPjAx$k^PftzzF%d)T;7 zg(t-WSm=Xk=?c*q-K%3R&2z_@t^*LQwpy*;H3$^vGCgh{@CYnnMl#Qg2R}x#30J=W z#((4Fym0a2KwNXC*z6-?_4?fB?W_>efdbU7dboj$N?~2Vle9M~OYT)fzd5vkrgs@R8?6S#~#CvXm$0VR87wNLTK~>F9>&1jm zx%~(=4G$iLARB1UJxz7MIT)?$@F7CqY{Z~^BGx*Og~Z^+SF^a+%!%p4i^E>@J{lp* zqcRS&0-(jQ)5_$#On~lr=8kFY$n%bCaS#RsnOt^=YZYCS)BJGj3#?Zi5BS_)R`^%J1*`x_ZenodnV)(v5+)$~sx+B@kw0a_1A|n4mT;39EOq zb*lZt41n1-yHrdksLSxIKN14=dsIF&rfvK& zi@R20>0j0AI_B{#_lqs?#qYne&`(ECg* z8d9rOOk$TbzV3w3_j<^iI}Qa>ctd@VYyEutT#Mo*co(wyH*jDMlmpxe(|l6}*H@;6 zNhmnWCSF_09RF~w7s7_e|QgK>pdnXpfQL)fV#&%QsyeYj*^&~(^`#$qIQ|y^V>naIbnpofQdS;U4 zl=`vh^5E{-JePEH(GhKyF#YbyV3!%Iv)UAW!S)fy>Vc09h}5AQdZPQ1PlGzB>aAAf zSq)21;8c^uz^u{HwZ%a`&45{tQuR)bgRndVxt~j?*@_bFtBFV4bX75Fecn9 zqdjTMXH~GF6#e-&3zA;)BUm*yA2Q*z3-RboeS^@b{gpK+bT6&WN}XO(Bg8~}>E6{3 zR+mjbM>B+-HIe!(`!PUWydK0|GP&sfMTb(5MyN4{L^+3ErTCE0Z%=P2eXAE(TFe*# zU!`a{db4Mr>dDg%?`ejBraS7FA=lx}Dm&@DGRdN`fAm?edAhcL3me^s zXF1F7yjR{i)tq?|aaZ+ui@JQ@RoAMyPRDwsP$x=$D4_UPy&Vc8qA{ODZiCFJty%zm z`IvLQ0#Kx*&Fd@m>tr7ivtyMPpN>~7XFMRW$UaioZZpr*)HMX%=Cm}FnK@YMJ~o^0 z?Kx}rZP|#ZZ*U9|rc*l7>)QV@a%kV_1>|Jy$s27(ECY& z^c6MtjbhF;vEAWq1KYU3I=sL~7#$O;7X`agaY|J*fF1d8r^N%T%{zwbpZaO6-W2RFb}+P=yaZVt0TypP^;SJC;zg7C`^uMdj+K-ZPie(!Il7GAwV4v)`4^wd%Qa zIRRuX@_CMWnJ`=W@u+vxU)Mm#19^a{;Z(2S!4O&PpAmPx!p*&(&ENqoaZ&3XWv{7l zRS^>?br9;kN7n?RXr+FgncfR$$kKHz!6_td3sK)KY)y&XE_elr<80)dfH< zS3f#lBMPAPX2PP&==Gdi)KxWBL_qhJCYQ?Pt_sf?9Zw#O#a}KGE|-TuR{tg6^3v(- zP%p50IlwGE|Fq6iH`-{VdQI)0jPyfsCUnJPb+$G0Fn?Qe->+2a*{t9`!9l*ctZ|ML zwndlh$7S(?eOiVWDOJUPj-@q?RrOhkx2(Dzp2a{RRTLe(8lC)rt3E1_;vQnW)9AX_a08;f)(s}Ri(jJ&}K+&k^L zc>wb^{8id?6i#?=i)Wj=)17JvQfdE}$3w-0oAs!z3?BIcqhqbK-kPU%yvSWCXG)7& zX398cL5n)NW4?Uca|rAJWTE89OwrJD;`ZryW4=#gKN>*?Cxp~`;YW-4m$R(~m1QkC z&*v-V%bT^$6ZlNoq2Rr&zy367)#l?%*^2nfFEdiQuH}DKumtIr0zgH(Xn%6~?Zy6n z0J?S4zeN_L#;s0}BX!7FzefN5xlS(2VMa#AjL&r~<^gA(%bF*7-jEUJ9$yW!C=FLK ze|p){iTXoUp_T0~89&J{4>a_HZmfO;=-ts5#TI{fWoFibhHF31K5rqZZv$CeGsqwk zXMC7M9=cm;EDMhosmhdU?H5Q)Y6ULJlT=QaL(7?H{Ee5~+w6 z=ppg0lbXlucW-_SabclcTKeCt`QA*4UuI9DXVor3pZ#+^DfhSDbrhCp!=qy%hL~i-5|Xg zsv=d67vXS$66T1~QypWHZBTx3>Zix4s#TMgHsFhGre*+^9p6jw)&wny`Q+P2hKqBC zPcqT6oO|9f74uFc!(Hna)CN%YF15SGut23$s--@(#d3}hs#yB!%%U++q0u zw2Mr%DCmU7-4+p-LPqYd8uY~8Gyx%1HGA!ra|V;5=N#BbfE~OgqgRGiV5o3z1q*B zfv|ni@pd?lDcs}dfg1TxEaF%f;0APQXC6R#`6aN-oaj^qAfb(p|Zszj6X0w~YMy`)HdcB17DTMW2@9IWR>kUvF*))cN)Q$Vr%oE+rmOvbc zy;d@maXh~j9Bd;f7XMoig*N_3Jy7KO;%pYUB_<3tt$t_P|2Pb6h&^Oh-A8gYJ1bKiT3$irpD1?i zFeu$kOK|yiZWJI+4h^P;aFDQRCM2$q%H1hUv2ri? zdSjML>Xwa+R6in2nC)(OX`#GXpty!!ki96V3b6G;-vszy?SLS~MnRs*5`R==0y6_; zKgIlE`~e9cYuc^zyBzz!fcyC0S|vgVZq1tZXe9)6se=x&w%?o$`! zVNM-OTBu^WJW~#ph(P|$>j^O2I5a|o&fW8AhrsPs$B0c_UlHgxG}hZ3rS)3?y<@ts zPD3gAkilBN!DYVh=PusJ&!Tefuz9Mq1i20}?rM7%Kd;lXSu*L+PF@dmDC){IckX0+ z#5-X9*8kWY)ttN&^%Ky3FL5@<{8;_abmDy34Cs6$E@f;}cJ%Rhsdkqd-Dbo^W}>z( zx1t2X8(kO#>SJve%Q3rCAzut%t8R1SsFPeeWcR}D)#6?H8j{BPFHd(s{k2rXmQuVp z7}h?_SuLOSn+1iA3(r6t{_)Y+JQ(C6^I*5dGz#iDp80`4D#s*iEN;FzERd#evIw>K z$_yY`pYe2QGF?#}efvS3v(UkUUJC31$Jt3}=!W>#{FbyBcr=neBqa{B6- z#1}uzKycz&BYcU1lk02U>j8D~HK8)p+VpymP5<(+b-5X;LY18ifo%?fE{SrG-|<(! zO8517v)Ai)jth|7DGir}vLLIcA68}AgVgQl?2Ee{XCP=d zF!jZu2lY423ZIPpcq@9NWxdF14t&u1v!oygAMjQ zwh)smN@`_;DFG=~PkwTDy z)~igiL)KqpI4iXY+C4(0K}BsjzVi9I|gMKt=9h*VzJ2Ke9{?`9_;bW_uk|h9=on;=Uz*%0DDmE~i&gp3hZLIHqo5>JJ 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 literal 143537 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 diff --git a/docs/src/img/design/stack/crypto_ops/HORNEREXT.png b/docs/src/img/design/stack/crypto_ops/HORNEREXT.png index 80f69d05d91f99f74c6e763df96cbcab92ad735f..96fb5a4b8324f0dcedcb677466c2ece3cb671a5e 100644 GIT binary patch literal 137436 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�= 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 R;!|b-0#8>zmvv4FO#lc3t1tin diff --git a/docs/src/user_docs/assembly/cryptographic_operations.md b/docs/src/user_docs/assembly/cryptographic_operations.md index 7cedd94ff4..8f3267267b 100644 --- a/docs/src/user_docs/assembly/cryptographic_operations.md +++ b/docs/src/user_docs/assembly/cryptographic_operations.md @@ -98,8 +98,8 @@ The following instructions are designed mainly for use in recursive verification | Instruction | Stack_input | Stack_output | Notes | | ----------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | eval_circuit
- *(1 cycle)* | [ptr, n_read, n_eval, ...] | [ptr, n_read, n_eval, ...] | Evaluates an arithmetic circuit, and checks that its output is equal to zero. `ptr` specifies the memory address at which the circuit description is stored with the number of input extension field elements specified by `n_read` and the number of evaluation gates, encoded as base field elements, specified by `n_eval`. | -| horner_eval_base
- *(1 cycle)* | [c7, c6, c5, c4, c3, c2, c1, c0, - , - , - , - , - , alpha_addr, acc1, acc0, ...] | [c7, c6, c5, c4, c3, c2, c1, c0, - , - , - , - , - , alpha_addr, acc1', acc0', ...] | Performs 8 steps of the Horner evaluation method to update the accumulator using evaluation point `alpha` read from memory at `alpha_addr` and `alpha_addr + 1`. Computes `acc' = (((((((acc * alpha + c0) * alpha + c1) * alpha + c2) * alpha + c3) * alpha + c4) * alpha + c5) * alpha + c6) * alpha + c7`. | -| horner_eval_ext
- *(1 cycle)* | [c3_1, c3_0, c2_1, c2_0, c1_1, c1_0, c0_1, c0_0, - , - , - , - , - , alpha_addr, acc1, acc0, ...] | [c3_1, c3_0, c2_1, c2_0, c1_1, c1_0, c0_1, c0_0, - , - , - , - , - , alpha_addr, acc1', acc0', ...] | Performs 4 steps of the Horner evaluation method on a polynomial with coefficients over the quadratic extension field using evaluation point `alpha` read from memory at `alpha_addr` and `alpha_addr + 1`. Computes `acc' = (((acc * alpha + c0) * alpha + c1) * alpha + c2) * alpha + c3` where coefficients are extension field elements `c0 = (c0_1, c0_0)`, `c1 = (c1_1, c1_0)`, `c2 = (c2_1, c2_0)`, `c3 = (c3_1, c3_0)`. | +| horner_eval_base
- *(1 cycle)* | [c7, c6, c5, c4, c3, c2, c1, c0, - , - , - , - , - , alpha_addr, acc1, acc0, ...] | [c7, c6, c5, c4, c3, c2, c1, c0, - , - , - , - , - , alpha_addr, acc1', acc0', ...] | Performs 8 steps of the Horner evaluation method to update the accumulator using evaluation point `alpha`. `alpha_addr` must be word-aligned and reference `[alpha0, alpha1, 0, 0]`; execution fails if either padding element is nonzero. Computes `acc' = (((((((acc * alpha + c0) * alpha + c1) * alpha + c2) * alpha + c3) * alpha + c4) * alpha + c5) * alpha + c6) * alpha + c7`. | +| horner_eval_ext
- *(1 cycle)* | [c3_1, c3_0, c2_1, c2_0, c1_1, c1_0, c0_1, c0_0, - , - , - , - , - , alpha_addr, acc1, acc0, ...] | [c3_1, c3_0, c2_1, c2_0, c1_1, c1_0, c0_1, c0_0, - , - , - , - , - , alpha_addr, acc1', acc0', ...] | Performs 4 steps of the Horner evaluation method on a polynomial with coefficients over the quadratic extension field using evaluation point `alpha`. `alpha_addr` must be word-aligned and reference `[alpha0, alpha1, 0, 0]`; execution fails if either padding element is nonzero. Computes `acc' = (((acc * alpha + c0) * alpha + c1) * alpha + c2) * alpha + c3` where coefficients are extension field elements `c0 = (c0_1, c0_0)`, `c1 = (c1_1, c1_0)`, `c2 = (c2_1, c2_0)`, `c3 = (c3_1, c3_0)`. | | log_deferred
- *(1 cycle)* | [_, STMNT, _, ...] | [ROOT_NEW, OUT_RATE1, OUT_CAP, ...] | Folds `STMNT` from stack offsets 4..8 into the VM's rolling deferred root via `ROOT_NEW = rate0(Poseidon2([ROOT_PREV, STMNT, [1,0,0,0]]))`, using the internally maintained previous root and the `Tag::AND` capacity word `[1, 0, 0, 0]`.
`STMNT` must be a registered statement for a precompile claim that evaluates to `TRUE`. The top 3 stack words are replaced with `[ROOT_NEW, OUT_RATE1, OUT_CAP]`, and wrappers typically drop them right away. Core-library facades and internal support code wrap this low-level opcode when precompile claims need to be logged. | | crypto_stream
- *(1 cycle)* | [rate(8), cap(4), src_ptr, dst_ptr, ...] | [ciphertext(8), cap(4), src_ptr+8, dst_ptr+8, ...] | Performs one Poseidon2-sponge keystream step against memory: loads two words from `src_ptr`, adds the rate (top 8 stack elements) element-wise to produce ciphertext, writes ciphertext to `dst_ptr`, replaces the rate on the stack with the ciphertext, preserves capacity, and increments both pointers by 8. Used as the primitive underlying `miden::core::crypto::aead`. | diff --git a/processor/src/errors.rs b/processor/src/errors.rs index a48ae5fd32..8f3b998b4c 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -328,6 +328,10 @@ pub enum OperationError { }, #[error("FRI operation failed: {0}")] FriError(String), + #[error( + "Horner evaluation point at memory address {addr} in context {ctx} must be encoded as [alpha0, alpha1, 0, 0]" + )] + InvalidHornerEvaluationPointWord { ctx: ContextId, addr: u64 }, #[error( "invalid crypto operation: Merkle path length {path_len} does not match expected depth {depth}" )] diff --git a/processor/src/execution/operations/crypto_ops/mod.rs b/processor/src/execution/operations/crypto_ops/mod.rs index cc6cccd583..2d8686e846 100644 --- a/processor/src/execution/operations/crypto_ops/mod.rs +++ b/processor/src/execution/operations/crypto_ops/mod.rs @@ -5,8 +5,8 @@ use miden_core::deferred::Tag; use super::{DOUBLE_WORD_SIZE, WORD_SIZE_FELT}; use crate::{ - ContextId, Felt, MemoryError, ONE, RowIndex, Word, ZERO, - errors::{CryptoError, MerklePathVerificationFailedInner, OperationError}, + ContextId, Felt, MemoryError, RowIndex, Word, ZERO, + errors::{CryptoError, IoError, MerklePathVerificationFailedInner, OperationError}, field::{BasedVectorSpace, QuadFelt}, processor::{ AdviceProviderInterface, HasherInterface, MemoryInterface, Processor, StackInterface, @@ -220,6 +220,30 @@ pub(super) fn op_mrupdate( // HORNER-BASED POLYNOMIAL EVALUATION OPERATIONS // ================================================================================================ +/// Reads a Horner evaluation point from an aligned memory word encoded as +/// `[alpha0, alpha1, 0, 0]`. +fn read_horner_eval_point( + processor: &mut P, + tracer: &mut T, + addr: Felt, +) -> Result { + let clk = processor.system().clock(); + let ctx = processor.system().ctx(); + let word = processor.memory_mut().read_word(ctx, addr, clk)?; + + if word[2] != ZERO || word[3] != ZERO { + return Err(OperationError::InvalidHornerEvaluationPointWord { + ctx, + addr: addr.as_canonical_u64(), + } + .into()); + } + + tracer.record_memory_read_word(word, addr, ctx, clk); + + Ok(QuadFelt::new([word[0], word[1]])) +} + /// Performs 8 steps of the Horner evaluation method on a polynomial with coefficients over /// the base field using a 3-level computation to reduce constraint degree. /// @@ -262,8 +286,8 @@ pub(super) fn op_mrupdate( /// coefficient (X^7) and s[7] is the constant term (X^0). /// 2. (acc0, acc1) is a quadratic extension field element accumulating the Horner evaluation. /// (acc0', acc1') is the updated accumulator after processing this batch. -/// 3. alpha_addr is the memory address of the evaluation point α = (α₀, α₁). The operation reads α₀ -/// from alpha_addr and α₁ from alpha_addr + 1. +/// 3. alpha_addr is the word-aligned address of `[alpha0, alpha1, 0, 0]`, which contains the +/// evaluation point alpha = (alpha0, alpha1). /// /// The instruction uses helper registers to store intermediate values: /// - h₀, h₁: evaluation point α = (α₀, α₁) @@ -273,32 +297,14 @@ pub(super) fn op_mrupdate( pub(super) fn op_horner_eval_base( processor: &mut P, tracer: &mut T, -) -> Result { +) -> Result { // Stack positions: low coefficient closer to top (lower index) const ALPHA_ADDR_INDEX: usize = 13; const ACC_LOW_INDEX: usize = 14; const ACC_HIGH_INDEX: usize = 15; - let clk = processor.system().clock(); - let ctx = processor.system().ctx(); - - // Read the evaluation point alpha from memory - let alpha = { - let addr = processor.stack().get(ALPHA_ADDR_INDEX); - let eval_point_0 = processor.memory_mut().read_element(ctx, addr)?; - let eval_point_1 = processor.memory_mut().read_element(ctx, addr + ONE)?; - - tracer.record_memory_read_element_pair( - eval_point_0, - addr, - eval_point_1, - addr + ONE, - ctx, - clk, - ); - - QuadFelt::from_basis_coefficients_fn(|i: usize| [eval_point_0, eval_point_1][i]) - }; + let alpha_addr = processor.stack().get(ALPHA_ADDR_INDEX); + let alpha = read_horner_eval_point(processor, tracer, alpha_addr)?; // Read the coefficients from the stack (top 8 elements) let coef: [Felt; 8] = processor.stack().get_double_word(0); @@ -376,22 +382,20 @@ pub(super) fn op_horner_eval_base( /// 2*i. s[0] is the highest-degree coefficient (X^3) and s[3] is the constant term (X^0). /// 2. (acc0, acc1) is a quadratic extension field element accumulating the Horner evaluation. /// (acc0', acc1') is the updated accumulator after processing this batch. -/// 3. alpha_addr is the memory address of the evaluation point α = (α₀, α₁). +/// 3. alpha_addr is the word-aligned address of `[alpha0, alpha1, 0, 0]`, which contains the +/// evaluation point alpha = (alpha0, alpha1). /// /// The instruction uses helper registers to hold α and the intermediate value acc_tmp. #[inline(always)] pub(super) fn op_horner_eval_ext( processor: &mut P, tracer: &mut T, -) -> Result { +) -> Result { // Stack positions: low coefficient closer to top (lower index) const ALPHA_ADDR_INDEX: usize = 13; const ACC_LOW_INDEX: usize = 14; const ACC_HIGH_INDEX: usize = 15; - let clk = processor.system().clock(); - let ctx = processor.system().ctx(); - // Read the coefficients from the stack as extension field elements (4 QuadFelt elements) // Stack layout: [s0_lo, s0_hi, s1_lo, s1_hi, s2_lo, s2_hi, s3_lo, s3_hi, ...] // s[0] at stack[0,1] is highest degree (X^3), s[3] at stack[6,7] is constant (X^0) @@ -401,23 +405,8 @@ pub(super) fn op_horner_eval_ext( QuadFelt::from_basis_coefficients_fn(|i: usize| [lo, hi][i]) }); - // Read the evaluation point alpha from memory - let (alpha, k0, k1) = { - let addr = processor.stack().get(ALPHA_ADDR_INDEX); - let word = processor.memory_mut().read_word(ctx, addr, clk)?; - tracer.record_memory_read_word( - word, - addr, - processor.system().ctx(), - processor.system().clock(), - ); - - ( - QuadFelt::from_basis_coefficients_fn(|i: usize| [word[0], word[1]][i]), - word[2], - word[3], - ) - }; + let alpha_addr = processor.stack().get(ALPHA_ADDR_INDEX); + let alpha = read_horner_eval_point(processor, tracer, alpha_addr)?; // Read the current accumulator (LE: low at lower index) let acc_low = processor.stack().get(ACC_LOW_INDEX); @@ -438,7 +427,7 @@ pub(super) fn op_horner_eval_ext( processor.stack_mut().set(ACC_LOW_INDEX, acc_new_base_elements[0]); // Return the user operation helpers - Ok(OperationHelperRegisters::HornerEvalExt { alpha, k0, k1, acc_tmp }) + Ok(OperationHelperRegisters::HornerEvalExt { alpha, acc_tmp }) } // LOG DEFERRED OPERATION diff --git a/processor/src/execution/operations/crypto_ops/tests.rs b/processor/src/execution/operations/crypto_ops/tests.rs index ccfb3ee957..b5ef9d65ea 100644 --- a/processor/src/execution/operations/crypto_ops/tests.rs +++ b/processor/src/execution/operations/crypto_ops/tests.rs @@ -14,6 +14,7 @@ use super::{ }; use crate::{ AdviceInputs, ContextId, + errors::{IoError, MemoryError, OperationError}, fast::{FastProcessor, NoopTracer}, processor::{Processor, SystemInterface}, }; @@ -437,21 +438,18 @@ proptest! { let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap()); let mut tracer = NoopTracer; - // Store alpha in memory at ALPHA_ADDR - // Memory format requirement: [alpha_0, alpha_1, k0, k1] (k0, k1 are unused but read) let alpha_word: Word = [felt(alpha_0), felt(alpha_1), ZERO, ZERO].into(); let clk = processor.clock(); - processor.memory_mut().write_word( - ContextId::root(), - felt(ALPHA_ADDR), - clk, - alpha_word, - ).unwrap(); + processor + .memory_mut() + .write_word(ContextId::root(), felt(ALPHA_ADDR), clk, alpha_word) + .unwrap(); processor.system_mut().increment_clock(); // Execute the operation - let result = op_horner_eval_ext(&mut processor, &mut tracer); - prop_assert!(result.is_ok()); + let helpers = op_horner_eval_ext(&mut processor, &mut tracer); + prop_assert!(helpers.is_ok()); + let helpers = helpers.unwrap().to_user_op_helpers(); processor.system_mut().increment_clock(); // Compute expected result @@ -471,6 +469,10 @@ proptest! { let acc_tmp = coefficients.iter().take(2).fold(acc_old, |acc, coef| *coef + alpha * acc); let acc_new = coefficients.iter().skip(2).fold(acc_tmp, |acc, coef| *coef + alpha * acc); + let acc_tmp_base: &[Felt] = acc_tmp.as_basis_coefficients_slice(); + prop_assert_eq!(&helpers[..2], &[felt(alpha_0), felt(alpha_1)]); + prop_assert_eq!(&helpers[4..], acc_tmp_base); + // Check stack state using stack_top() let stack = processor.stack_top(); @@ -501,6 +503,56 @@ proptest! { } } +#[test] +fn horner_eval_ops_reject_nonzero_eval_point_padding() { + let stack_inputs = [ZERO; 16]; + + let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap()); + let mut tracer = NoopTracer; + let clk = processor.clock(); + processor + .memory_mut() + .write_word(ContextId::root(), ZERO, clk, [ZERO, ZERO, Felt::ONE, ZERO].into()) + .unwrap(); + assert!(matches!( + op_horner_eval_base(&mut processor, &mut tracer), + Err(IoError::Operation(OperationError::InvalidHornerEvaluationPointWord { + ctx, + addr, + })) if ctx == ContextId::root() && addr == 0 + )); + + let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap()); + let clk = processor.clock(); + processor + .memory_mut() + .write_word(ContextId::root(), ZERO, clk, [ZERO, ZERO, ZERO, Felt::ONE].into()) + .unwrap(); + assert!(matches!( + op_horner_eval_ext(&mut processor, &mut tracer), + Err(IoError::Operation(OperationError::InvalidHornerEvaluationPointWord { + ctx, + addr, + })) if ctx == ContextId::root() && addr == 0 + )); +} + +#[test] +fn horner_eval_base_rejects_unaligned_eval_point_address() { + const UNALIGNED_ADDR: u32 = 2; + + let mut stack_inputs = [ZERO; 16]; + stack_inputs[13] = Felt::from_u32(UNALIGNED_ADDR); + + let mut processor = FastProcessor::new(StackInputs::new(&stack_inputs).unwrap()); + let mut tracer = NoopTracer; + assert!(matches!( + op_horner_eval_base(&mut processor, &mut tracer), + Err(IoError::Memory(MemoryError::UnalignedWordAccess { addr, ctx })) + if addr == UNALIGNED_ADDR && ctx == ContextId::root() + )); +} + // MERKLE TREE TESTS // -------------------------------------------------------------------------------------------- diff --git a/processor/src/fast/tests/masm_consistency.rs b/processor/src/fast/tests/masm_consistency.rs index 9b84ae918c..ef3be26209 100644 --- a/processor/src/fast/tests/masm_consistency.rs +++ b/processor/src/fast/tests/masm_consistency.rs @@ -190,7 +190,7 @@ use super::*; // ---- horner ops -------------------------------- #[case(None, "begin - push.1.2.3.4 mem_storew_le.40 dropw + push.0.0.3.4 mem_storew_le.40 dropw horner_eval_base end", vec![Felt::from_u32(16), Felt::from_u32(15), Felt::from_u32(14), Felt::from_u32(13), Felt::from_u32(12), Felt::from_u32(11), Felt::from_u32(10), @@ -199,7 +199,7 @@ use super::*; )] #[case(None, "begin - push.1.2.3.4 mem_storew_le.40 dropw + push.0.0.3.4 mem_storew_le.40 dropw horner_eval_ext end", vec![Felt::from_u32(16), Felt::from_u32(15), Felt::from_u32(14), Felt::from_u32(13), Felt::from_u32(12), Felt::from_u32(11), Felt::from_u32(10), diff --git a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_15_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_15_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap index f94dbc1f26..3737b67b64 100644 --- a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_15_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap +++ b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_15_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap @@ -2,25 +2,25 @@ source: processor/src/fast/tests/all_ops.rs expression: fast_stack_outputs --- -Ok( - StackOutputs { - elements: [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 8, - 0, - ], +Err( + MemoryError { + label: SourceSpan { + source_id: SourceId( + 4294967295, + ), + start: ByteIndex( + 0, + ), + end: ByteIndex( + 0, + ), + }, + source_file: None, + err: UnalignedWordAccess { + addr: 14, + ctx: ContextId( + 0, + ), + }, }, ) diff --git a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_16_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_16_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap index f94dbc1f26..3737b67b64 100644 --- a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_16_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap +++ b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_16_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap @@ -2,25 +2,25 @@ source: processor/src/fast/tests/all_ops.rs expression: fast_stack_outputs --- -Ok( - StackOutputs { - elements: [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 8, - 0, - ], +Err( + MemoryError { + label: SourceSpan { + source_id: SourceId( + 4294967295, + ), + start: ByteIndex( + 0, + ), + end: ByteIndex( + 0, + ), + }, + source_file: None, + err: UnalignedWordAccess { + addr: 14, + ctx: ContextId( + 0, + ), + }, }, ) diff --git a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_17_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_17_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap index f94dbc1f26..3737b67b64 100644 --- a/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_17_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap +++ b/processor/src/fast/tests/snapshots/miden_processor__fast__tests__all_ops__fast__tests__all_ops__test_basic_block__stack_inputs_17_vec__Felt__from_u32_1__Felt__from_u32_2__Felt__from_u32_3__Felt___operations_67_vec__Operation__HornerBase_.snap @@ -2,25 +2,25 @@ source: processor/src/fast/tests/all_ops.rs expression: fast_stack_outputs --- -Ok( - StackOutputs { - elements: [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 8, - 0, - ], +Err( + MemoryError { + label: SourceSpan { + source_id: SourceId( + 4294967295, + ), + start: ByteIndex( + 0, + ), + end: ByteIndex( + 0, + ), + }, + source_file: None, + err: UnalignedWordAccess { + addr: 14, + ctx: ContextId( + 0, + ), + }, }, ) diff --git a/processor/src/trace/execution_tracer.rs b/processor/src/trace/execution_tracer.rs index 1dd741f6bb..ec5c8feb7c 100644 --- a/processor/src/trace/execution_tracer.rs +++ b/processor/src/trace/execution_tracer.rs @@ -827,20 +827,6 @@ impl Tracer for ExecutionTracer { self.memory_writes.record_write_word(word, addr, ctx, clk); } - #[inline(always)] - fn record_memory_read_element_pair( - &mut self, - element_0: Felt, - addr_0: Felt, - element_1: Felt, - addr_1: Felt, - ctx: ContextId, - clk: RowIndex, - ) { - self.memory_reads.record_read_element(element_0, addr_0, ctx, clk); - self.memory_reads.record_read_element(element_1, addr_1, ctx, clk); - } - #[inline(always)] fn record_memory_read_dword( &mut self, diff --git a/processor/src/trace/tests/chiplets/memory.rs b/processor/src/trace/tests/chiplets/memory.rs index 58fb8dd55e..09534d7ef9 100644 --- a/processor/src/trace/tests/chiplets/memory.rs +++ b/processor/src/trace/tests/chiplets/memory.rs @@ -38,6 +38,7 @@ use super::super::{ use crate::RowIndex; const FOUR: Felt = Felt::new_unchecked(4); +const TWO: Felt = Felt::new_unchecked(2); /// Covers `MStoreW`, `MLoad`, `MLoadW`, `MStore`, `MStream` — every memory opcode issuable /// directly from the stack — asserting the chiplet-bus request/response pair fires at every @@ -224,13 +225,11 @@ fn cryptostream_emits_four_memory_requests() { log.assert_contains(&exp); } -/// Verifies that `HornerBase`'s two element-reads land on the chiplet-requests bus. -/// HornerBase reads α = (α₀, α₁) from memory at (`alpha_ptr`, `alpha_ptr + 1`); -/// uninitialized memory returns zeros, and the trace stores those same zeros into -/// helpers[0..2], so a missing request or a swapped addr/clk would fail the subset match. +/// Verifies that `HornerBase` emits one zero-padded word read on the chiplet-requests bus. #[test] -fn hornerbase_emits_two_memory_requests() { - // HornerBase stack layout: [c0..c7, _, _, _, _, _, alpha_ptr, acc_low, acc_high] +fn hornerbase_emits_one_memory_request() { + // Use a point for which helpers[2] and helpers[3] are nonzero. This makes either helper + // distinguishable from the zero padding in the memory request. let stack = [ 1, 2, 3, 4, 5, 6, 7, 8, // coeffs[0..8] 0, 0, 0, 0, 0, // pad (5 slots) @@ -238,22 +237,39 @@ fn hornerbase_emits_two_memory_requests() { 0, 0, // acc_low, acc_high ]; - let trace = build_trace_from_ops(vec![Operation::HornerBase], &stack); + let operations = vec![ + Operation::Push(ZERO), + Operation::Push(ZERO), + Operation::Push(TWO), + Operation::Push(ONE), + Operation::Push(ZERO), + Operation::MStoreW, + Operation::Drop, + Operation::Drop, + Operation::Drop, + Operation::Drop, + Operation::HornerBase, + ]; + let trace = build_trace_from_ops(operations, &stack); + const HORNER_CLK: u32 = 11; + let helpers = trace.get_user_op_helpers_at(HORNER_CLK); + assert_ne!(helpers[2], ZERO); + assert_ne!(helpers[3], ZERO); + let log = InteractionLog::new(&trace); let mut exp = Expectations::new(&log); - // HornerBase runs at cycle 1 (cycle 0 is SPAN), ctx = 0, uninitialized memory returns zeros. - const ROW: usize = 1; - exp.remove(ROW, &MemoryMsg::read_element(ZERO, ZERO, ONE, ZERO)); - exp.remove(ROW, &MemoryMsg::read_element(ZERO, ONE, ONE, ZERO)); + // HornerBase runs at cycle 11 (cycle 0 is SPAN). + exp.remove( + HORNER_CLK as usize, + &MemoryMsg::read_word(ZERO, ZERO, Felt::from_u32(HORNER_CLK), [ONE, TWO, ZERO, ZERO]), + ); log.assert_contains(&exp); } -/// Verifies that `HornerExt`'s single word-read lands on the chiplet-requests bus. -/// HornerExt reads `[α₀, α₁, k₀, k₁]` as a single word from `alpha_ptr`; -/// uninitialized memory returns the zero word, which the trace parks in helpers[0..4]. +/// Verifies that `HornerExt`'s word read lands on the chiplet-requests bus. #[test] fn hornerext_emits_one_memory_request() { // HornerExt stack layout: [s0_lo, s0_hi, ..., s3_lo, s3_hi, _, _, _, _, _, alpha_ptr, @@ -271,8 +287,7 @@ fn hornerext_emits_one_memory_request() { let mut exp = Expectations::new(&log); const ROW: usize = 1; - let zero_word = [ZERO, ZERO, ZERO, ZERO]; - exp.remove(ROW, &MemoryMsg::read_word(ZERO, ZERO, ONE, zero_word)); + exp.remove(ROW, &MemoryMsg::read_word(ZERO, ZERO, ONE, [ZERO; 4])); log.assert_contains(&exp); } diff --git a/processor/src/tracer.rs b/processor/src/tracer.rs index 4308c6e93b..af973efab3 100644 --- a/processor/src/tracer.rs +++ b/processor/src/tracer.rs @@ -190,7 +190,7 @@ pub trait Tracer { /// Records the word read from memory at the given address. /// - /// Called by: `MLOADW`, `HORNER_EVAL_EXT`, `DYN`. + /// Called by: `MLOADW`, `HORNER_EVAL_BASE`, `HORNER_EVAL_EXT`, `DYN`. fn record_memory_read_word( &mut self, _word: Word, @@ -224,20 +224,6 @@ pub trait Tracer { ) { } - /// Records two element reads at the given addresses. - /// - /// Called by: `HORNER_EVAL_BASE`. - fn record_memory_read_element_pair( - &mut self, - _element_0: Felt, - _addr_0: Felt, - _element_1: Felt, - _addr_1: Felt, - _ctx: ContextId, - _clk: RowIndex, - ) { - } - /// Records two consecutive word reads (a "dword" read) starting at the given address. /// /// Called by: `MSTREAM`. @@ -467,16 +453,9 @@ pub enum OperationHelperRegisters { /// on a polynomial with extension-field coefficients. /// /// - `alpha`: the evaluation point, read from memory. - /// - `k0`, `k1`: auxiliary values read from the same memory word as `alpha` (elements 2 and 3 - /// of the word). /// - `acc_tmp`: the intermediate accumulator after processing the first 2 (highest-degree) /// coefficients: `(acc * alpha + s[0]) * alpha + s[1]`. - HornerEvalExt { - alpha: QuadFelt, - k0: Felt, - k1: Felt, - acc_tmp: QuadFelt, - }, + HornerEvalExt { alpha: QuadFelt, acc_tmp: QuadFelt }, /// Helper for the `LOG_DEFERRED` operation, which folds a verified statement digest into /// the rolling deferred root via a Poseidon2 permutation. /// @@ -621,11 +600,11 @@ impl OperationHelperRegisters { tmp0.as_basis_coefficients_slice()[0], tmp0.as_basis_coefficients_slice()[1], ], - Self::HornerEvalExt { alpha, k0, k1, acc_tmp } => [ + Self::HornerEvalExt { alpha, acc_tmp } => [ alpha.as_basis_coefficients_slice()[0], alpha.as_basis_coefficients_slice()[1], - *k0, - *k1, + ZERO, + ZERO, acc_tmp.as_basis_coefficients_slice()[0], acc_tmp.as_basis_coefficients_slice()[1], ], From e4ddf1aade4d5080d80068eff2cb227a11f2151c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Tue, 11 Aug 2026 17:40:17 -0400 Subject: [PATCH 17/17] chore: Adopt cargo-fixit, align clippy lints with crypto, clear cargo-shear warnings (#3479) * chore(tooling): use cargo-fixit for the clippy-fix path `make xclippy-fix` now runs `cargo +stable fixit --clippy` (a faster drop-in for `cargo clippy --fix`) instead of the `xclippy-fix` cargo alias. The `xclippy` alias in .cargo/config.toml remains the single source of truth for the lint set. The Makefile extracts those flags into CLIPPY_LINT_FLAGS and passes them to fixit via RUSTFLAGS, since fixit cannot take lint flags as trailing arguments. The check path reads the same alias directly, so the two paths cannot drift apart. The now-unused `xclippy-fix` cargo alias is removed. When cargo-fixit is not installed the target prints the install command and skips rather than falling back to clippy. CI is untouched (it never ran the fix step). CONTRIBUTING.md uses `cargo fixit`. * chore(lint): enable clippy::expl_impl_clone_on_copy Replace the manual `Copy`/`Clone` impls on `Span` with a derive, which matches the existing `T: Copy`/`T: Clone` bounds exactly. Enable `clippy::expl_impl_clone_on_copy` in the project clippy aliases. * chore(lint): enable clippy::unimplemented The `build_aux_trace` stub in the ACE codegen test AIR is never called; use `unreachable!` instead of `unimplemented!`. (`todo!` would trip the already-enabled `clippy::todo`.) Enable `clippy::unimplemented` in the project clippy aliases. * chore(lint): enable clippy::debug_assert_with_mut_call Hoist the mutable iterator `.next()` calls out of `debug_assert!` in the ACE and hasher chiplet traces. Calling `.next()` inside `debug_assert!` means the iterator advances in debug builds but not in release builds; evaluating the call unconditionally keeps behaviour consistent. Enable `clippy::debug_assert_with_mut_call` in the project clippy aliases. * chore(lint): enable clippy::option_option `reexport_target_docs` returns `Option>` to encode three states the caller relies on: target not found (skip the re-export), found without docs (emit the procedure with no docs), and found with docs. Conflating the outer two would emit spurious doc entries for unresolved re-exports, so the nesting is intentional; document it with a scoped `#[allow]`. Enable `clippy::option_option` in the project clippy aliases. * chore(lint): enable clippy::fn_params_excessive_bools `NodeFlags` packs four independent booleans into the hasher state's second word for END operations; there is no natural smaller grouping and `fn_params_excessive_bools` has no configurable threshold, so scope an `#[allow]` on the constructor with a justification, matching the flag-bundle relaxation already in `.clippy.toml`. Enable `clippy::fn_params_excessive_bools` in the project clippy aliases. * chore(lint): enable clippy::checked_conversions Replace the `x <= (T::MAX as U)` checked-cast guards with the idiomatic `T::try_from(x).is_ok()` form across the seven remaining sites (proof order, parser value shrinking, MAST serialization test, parallel trace test, precompiles bench inputs). Enable `clippy::checked_conversions` in the project clippy aliases. * chore(shear): delete empty unlinked test file in miden-air `air/src/constraints/decoder/tests.rs` is a zero-byte file with no corresponding `mod tests` declaration in `decoder/mod.rs`, so it is never compiled. cargo-shear flagged it as unlinked. Delete it. * chore(shear): suppress unlinked-file warning for trybuild UI fixtures `core/tests/ui/mast_forest_immutable/index_node_mut.rs` is a trybuild `compile_fail` fixture loaded by path at runtime in `public_api.rs`, so cargo-shear cannot statically link it. Add a scoped `ignored-paths` entry for `tests/ui/**` rather than deleting the fixture, which would remove a real compile_fail test. * chore(shear): remove unused thiserror from miden-assembly-syntax-cst thiserror is a derive-only crate, and no code in miden-assembly-syntax-cst uses `#[derive(thiserror::Error)]` or imports it. Remove the dependency and the stale `"thiserror/std"` entry from the `std` feature. * chore(shear): drop redundant optional miden-ace-codegen from miden-core-lib miden-ace-codegen was listed both as an optional `[dependencies]` entry (enabled by the `constraints-tools` feature) and as a `[dev-dependencies]` entry. No non-test code in miden-core-lib imports it; the two test files that use it get the crate via the dev-dependency. Remove the redundant optional declaration and the `dep:miden-ace-codegen` reference from the `constraints-tools` feature. * chore(shear): remove stale serde_json ignore from miden-mast-package cargo-shear flagged `ignored = ["serde_json"]` as redundant because serde_json is used in the `#[cfg(test)]` deserialization tests in `section.rs`. The dependency is already correctly declared in `[dev-dependencies]`, so the ignore is stale. Remove it. * chore(shear): delete orphaned legacy FRI PCS tests in miden-core-lib Delete crates/lib/core/tests/pcs/ (839 lines across four files), a winter-fri based FRI PCS verifier test module that has not compiled or run since the Plonky3 migration. cargo-shear flags it as an unlinked file because no `mod pcs;` declaration exists in tests/main.rs. How this directory became orphaned (commit-by-commit): 1. #2365 (a13eea1d4e, 2025-11-15) "chore: rename STARK MASM mod" The pcs/ test directory was created alongside a STARK MASM module rename. 2. #2427 (fd2e580045, 2025-12-05) "chore: rename miden-libcore to miden-core-lib" The crate rename added `mod pcs;` to tests/main.rs, wiring the directory into the compiled test suite. 3. #2472 (0083f515ff) "refactor: migrate underlying proving system to Plonky3" The Plonky3 migration replaced winter-fri with p3-fri. The legacy FRI tests in pcs/ and stark/ broke against the new APIs, so both were gated behind a new empty `legacy-stark-tests` feature: #[cfg(feature = "legacy-stark-tests")] mod pcs; 4. #2616 (972e98ee01, 2026-01-30) "chore: disable legacy tests" The `legacy-stark-tests` feature was removed entirely and both module declarations were commented out: // These tests are disabled until the recursive verifier is // updated to work with Plonky3 proofs // mod pcs; // mod stark; 5. #2869 (e5af1b7d91, 2026-03-27) "Update the recursive verifier to work with p3-miden" This commit created the current orphaned state. It migrated the stark/ test module to p3-fri and re-enabled it with `mod stark;`, but removed the `// mod pcs;` line entirely instead of uncommenting it. The pcs/ tests use winter-fri APIs directly and were not migrated. The stark/ test module now covers the p3-fri based PCS path. The pcs/ tests are dead and deleting them clears the last cargo-shear warning. * chore: Changelog --- .cargo/config.toml | 78 +-- CHANGELOG.md | 1 + CONTRIBUTING.md | 2 +- Cargo.lock | 1 - Makefile | 19 +- air/src/constraints/decoder/tests.rs | 0 air/src/proof_order.rs | 2 +- core/Cargo.toml | 5 + core/src/mast/serialization/tests.rs | 2 +- crates/ace-codegen/src/tests/basic.rs | 2 +- crates/assembly-syntax-cst/Cargo.toml | 3 +- crates/assembly-syntax/src/parser/value.rs | 6 +- crates/debug-types/src/span.rs | 12 +- crates/lib/core/Cargo.toml | 3 +- crates/lib/core/build.rs | 8 + crates/lib/core/tests/pcs/fri/channel.rs | 81 --- crates/lib/core/tests/pcs/fri/mod.rs | 281 ----------- .../core/tests/pcs/fri/verifier_fri_e2f4.rs | 476 ------------------ crates/lib/core/tests/pcs/mod.rs | 1 - crates/mast-package/Cargo.toml | 2 - .../benches/precompiles_bench/support.rs | 2 +- processor/src/trace/chiplets/ace/trace.rs | 3 +- processor/src/trace/chiplets/hasher/mod.rs | 3 +- processor/src/trace/parallel/tests.rs | 2 +- processor/src/trace/trace_state.rs | 5 + tools/miden-core-fuzz/Cargo.lock | 1 - 26 files changed, 61 insertions(+), 940 deletions(-) delete mode 100644 air/src/constraints/decoder/tests.rs delete mode 100644 crates/lib/core/tests/pcs/fri/channel.rs delete mode 100644 crates/lib/core/tests/pcs/fri/mod.rs delete mode 100644 crates/lib/core/tests/pcs/fri/verifier_fri_e2f4.rs delete mode 100644 crates/lib/core/tests/pcs/mod.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 8af5541ffd..18284bf64d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,22 +6,28 @@ # # NOTE: Only lints that the codebase currently passes are enabled. # Additional lints can be enabled incrementally as the codebase is cleaned up. -# NOTE: for the `make lint` command to work well, this must contain the same list of lints as the xclippy-fix command below +# NOTE: the `xclippy` alias below is the single source of truth for the project +# clippy lints; the fix path (`make xclippy-fix`) derives them from here so the +# check and fix commands can never drift apart. xclippy = [ "clippy", "--workspace", "--all-targets", "--all-features", "--", "-Wclippy::all", # Lints that currently pass in the codebase: "-Wclippy::await_holding_lock", "-Wclippy::char_lit_as_u8", + "-Wclippy::checked_conversions", "-Wclippy::dbg_macro", + "-Wclippy::debug_assert_with_mut_call", "-Wclippy::derive_partial_eq_without_eq", "-Wclippy::disallowed_methods", "-Wclippy::empty_enums", "-Wclippy::exit", + "-Wclippy::expl_impl_clone_on_copy", "-Wclippy::explicit_deref_methods", "-Wclippy::filter_map_next", "-Wclippy::flat_map_option", "-Wclippy::float_cmp_const", + "-Wclippy::fn_params_excessive_bools", "-Wclippy::if_let_mutex", "-Wclippy::imprecise_flops", "-Wclippy::inefficient_to_string", @@ -42,6 +48,7 @@ xclippy = [ "-Wclippy::needless_borrow", "-Wclippy::needless_collect", "-Wclippy::needless_continue", + "-Wclippy::option_option", "-Wclippy::path_buf_push_overwrite", "-Wclippy::rc_mutex", "-Wclippy::redundant_clone", @@ -53,6 +60,7 @@ xclippy = [ "-Wclippy::string_add_assign", "-Wclippy::todo", "-Wclippy::trait_duplication_in_bounds", + "-Wclippy::unimplemented", "-Wclippy::trivially_copy_pass_by_ref", "-Wclippy::uninlined_format_args", "-Wclippy::unnested_or_patterns", @@ -66,14 +74,10 @@ xclippy = [ "-Wunused_lifetimes", "-Wunused_qualifications", # The following lints are disabled because they trigger warnings: - # -Wclippy::checked_conversions (12 warnings) - # -Wclippy::debug_assert_with_mut_call (3 warnings) # -Wclippy::doc_markdown (1293 warnings) # -Wclippy::enum_glob_use (17 warnings) - # -Wclippy::expl_impl_clone_on_copy (1 warning) # -Wclippy::explicit_into_iter_loop (7 warnings) # -Wclippy::fallible_impl_from (3 warnings) - # -Wclippy::fn_params_excessive_bools (1 warning) # -Wclippy::from_iter_instead_of_collect (4 warnings) # -Wclippy::implicit_clone (5 warnings) # -Wclippy::map_err_ignore (78 warnings) @@ -82,12 +86,10 @@ xclippy = [ # -Wclippy::match_wild_err_arm (6 warnings) # -Wclippy::match_wildcard_for_single_variants (8 warnings) # -Wclippy::needless_for_each (7 warnings) - # -Wclippy::option_option (6 warnings) # -Wclippy::ptr_as_ptr (15 warnings) # -Wclippy::semicolon_if_nothing_returned (97 warnings) # -Wclippy::single_match_else (54 warnings) # -Wclippy::string_lit_as_bytes (15 warnings) - # -Wclippy::unimplemented (1 warning) # -Wclippy::unused_self (11 warnings) # -Wclippy::cast_lossless (603 warnings) # -Wclippy::manual_assert (17 warnings) @@ -97,67 +99,5 @@ xclippy = [ # -Wunreachable_pub (545 warnings) ] -# Clippy fix with the same lints as xclippy -# NOTE: for the `make lint` command to work well, this must contain the same list of lints as the xclippy setting above -xclippy-fix = [ - "clippy", "--fix", "--allow-staged", "--allow-dirty", "--workspace", "--all-targets", "--all-features", "--", - "-Wclippy::all", - "-Wclippy::await_holding_lock", - "-Wclippy::char_lit_as_u8", - "-Wclippy::dbg_macro", - "-Wclippy::derive_partial_eq_without_eq", - "-Wclippy::disallowed_methods", - "-Wclippy::empty_enums", - "-Wclippy::exit", - "-Wclippy::explicit_deref_methods", - "-Wclippy::filter_map_next", - "-Wclippy::flat_map_option", - "-Wclippy::float_cmp_const", - "-Wclippy::if_let_mutex", - "-Wclippy::imprecise_flops", - "-Wclippy::inefficient_to_string", - "-Wclippy::invalid_upcast_comparisons", - "-Wclippy::large_digit_groups", - "-Wclippy::large_stack_arrays", - "-Wclippy::large_types_passed_by_value", - "-Wclippy::let_unit_value", - "-Wclippy::linkedlist", - "-Wclippy::lossy_float_literal", - "-Wclippy::macro_use_imports", - "-Wclippy::manual_ok_or", - "-Wclippy::map_flatten", - "-Wclippy::mem_forget", - "-Wclippy::missing_enforced_import_renames", - "-Wclippy::mut_mut", - "-Wclippy::mutex_integer", - "-Wclippy::needless_borrow", - "-Wclippy::needless_collect", - "-Wclippy::needless_continue", - "-Wclippy::path_buf_push_overwrite", - "-Wclippy::rc_mutex", - "-Wclippy::redundant_clone", - "-Wclippy::redundant_closure_for_method_calls", - "-Wclippy::ref_option_ref", - "-Wclippy::rest_pat_in_fully_bound_structs", - "-Wclippy::same_functions_in_if_condition", - "-Wclippy::string_add", - "-Wclippy::string_add_assign", - "-Wclippy::todo", - "-Wclippy::trait_duplication_in_bounds", - "-Wclippy::trivially_copy_pass_by_ref", - "-Wclippy::uninlined_format_args", - "-Wclippy::unnested_or_patterns", - "-Wclippy::useless_transmute", - "-Wclippy::verbose_file_reads", - "-Wclippy::zero_sized_map_values", - "-Wfuture_incompatible", - "-Wnonstandard_style", - "-Wtrivial_numeric_casts", - "-Wunexpected_cfgs", - "-Wunused_lifetimes", - "-Wunused_qualifications", - "-Wclippy::unnecessary_wraps", -] - [target.wasm32-unknown-unknown] rustflags = ['--cfg', 'getrandom_backend="unsupported"', '-C', 'target-feature=+simd128'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 696d7d06cd..463393bafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ #### Changes - [BREAKING] Recursive MASM verification now accepts a claim commitment and authenticates the advice-supplied claim and kernel witness. Rust callers construct request-addressed inputs with `RecursiveVerifierInputs::for_request` ([#3447](https://github.com/0xMiden/miden-vm/pull/3447)). +- Adopted cargo-fixit, aligned clippy lints with crypto, cleared cargo-shear warnings ([#3479](https://github.com/0xMiden/miden-vm/pull/3479)) #### Fixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dadad6bf33..cf18f92ee2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,7 +97,7 @@ For example, a new change to the AIR crate might have the following message: `fe - [Rustfmt](https://github.com/rust-lang/rustfmt) and [Clippy](https://github.com/rust-lang/rust-clippy) linting is included in CI pipeline. Anyways it's preferable to run linting locally before push: ``` - cargo fix --allow-staged --allow-dirty --all-targets --all-features; cargo fmt; cargo clippy --workspace --all-targets --all-features -- -D warnings + cargo fixit --allow-staged --allow-dirty --all-targets --all-features; cargo fmt; cargo clippy --workspace --all-targets --all-features -- -D warnings ``` ### Versioning diff --git a/Cargo.lock b/Cargo.lock index f563bfa8cd..1e7f4a92ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2130,7 +2130,6 @@ dependencies = [ "miden-rowan", "miden-utils-diagnostics", "pretty_assertions", - "thiserror", ] [[package]] diff --git a/Makefile b/Makefile index 87617eaa40..5a2e25d18a 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,13 @@ FEATURES_verifier := # which runs `make clippy` with RUSTFLAGS=-D warnings (see .github/workflows/lint.yml). DENY_WARNINGS := RUSTFLAGS="$(RUSTFLAGS) -D warnings" +# The clippy lint set is defined once, in the `xclippy` cargo alias in +# .cargo/config.toml, and extracted here so the check and fix paths cannot +# drift apart. cargo-fixit (a faster drop-in for `cargo clippy --fix`) cannot +# take lint flags as trailing arguments, so the fix path passes them via +# RUSTFLAGS instead. +CLIPPY_LINT_FLAGS := $(shell sed -n '/^xclippy = \[/,/^]/p' .cargo/config.toml | grep -oE '"-[WD][^"]+"' | tr -d '"' | tr '\n' ' ') + .PHONY: clippy clippy: ## Runs Clippy with configs (alias for xclippy) $(DENY_WARNINGS) cargo +stable xclippy @@ -86,8 +93,16 @@ xclippy: ## Runs Clippy with custom lint config from .cargo/config.toml fix: xclippy-fix format ## Applies automatic lint and format fixes .PHONY: xclippy-fix -xclippy-fix: ## Runs Clippy with --fix using the same lints as xclippy - cargo +stable xclippy-fix +xclippy-fix: ## Applies clippy lint fixes via cargo-fixit (a faster `cargo clippy --fix`) + @if ! command -v cargo-fixit >/dev/null 2>&1; then \ + echo "cargo-fixit is not installed; skipping clippy lint fixes." >&2; \ + echo "It is a faster drop-in replacement for 'cargo clippy --fix'." >&2; \ + echo "Install it with: cargo install cargo-fixit --locked" >&2; \ + else \ + RUSTFLAGS="$(CLIPPY_LINT_FLAGS)" cargo +stable fixit --clippy \ + --allow-dirty --allow-staged \ + --workspace --all-targets --all-features; \ + fi .PHONY: format diff --git a/air/src/constraints/decoder/tests.rs b/air/src/constraints/decoder/tests.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/air/src/proof_order.rs b/air/src/proof_order.rs index 39646f305e..9a9189c309 100644 --- a/air/src/proof_order.rs +++ b/air/src/proof_order.rs @@ -105,7 +105,7 @@ impl ProofOrder { /// Decode a Lehmer rank into its AIR permutation. fn from_rank(rank: usize) -> Self { debug_assert!(rank < PROOF_ORDER_COUNT); - debug_assert!(rank <= u32::MAX as usize); + debug_assert!(u32::try_from(rank).is_ok()); let tag = rank as u32; let mut rank = rank; diff --git a/core/Cargo.toml b/core/Cargo.toml index f6a225c03f..34de63b208 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -75,3 +75,8 @@ rstest = { workspace = true } serde_json = { workspace = true } miden-utils-testing.workspace = true trybuild.workspace = true + +[package.metadata.cargo-shear] +# `tests/ui/` holds trybuild `compile_fail` fixtures loaded by path at runtime +# (see core/tests/public_api.rs), so static analysis cannot link them. +ignored-paths = ["tests/ui/**"] diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index 4a7b6974b7..644284c4c8 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -2096,7 +2096,7 @@ fn locate_single_block_indptr_and_digest_offsets(bytes: &[u8]) -> (usize, usize) assert_eq!(discriminant, 3, "expected a Block node"); let payload = node_type_u64 & 0x0f_ff_ff_ff_ff_ff_ff_ff; - assert!(payload <= u32::MAX as u64, "Block ops_offset payload must fit in u32"); + assert!(u32::try_from(payload).is_ok(), "Block ops_offset payload must fit in u32"); let ops_offset = payload as usize; let digest_offset = view.node_hash_offset().unwrap(); diff --git a/crates/ace-codegen/src/tests/basic.rs b/crates/ace-codegen/src/tests/basic.rs index 213637c38e..fca25c1c7a 100644 --- a/crates/ace-codegen/src/tests/basic.rs +++ b/crates/ace-codegen/src/tests/basic.rs @@ -94,7 +94,7 @@ impl LiftedAir for TestAir { _aux_inputs: &[F], _challenges: &[EF], ) -> (RowMajorMatrix, Vec) { - unimplemented!("ACE codegen tests do not build concrete traces") + unreachable!("ACE codegen tests do not build concrete traces") } fn eval>(&self, builder: &mut AB) { diff --git a/crates/assembly-syntax-cst/Cargo.toml b/crates/assembly-syntax-cst/Cargo.toml index f93081fc64..bb39f76f10 100644 --- a/crates/assembly-syntax-cst/Cargo.toml +++ b/crates/assembly-syntax-cst/Cargo.toml @@ -19,12 +19,11 @@ doctest = false [features] default = ["std"] -std = ["miden-debug-types/std", "miden-utils-diagnostics/std", "rowan/std", "thiserror/std"] +std = ["miden-debug-types/std", "miden-utils-diagnostics/std", "rowan/std"] [dependencies] miden-debug-types.workspace = true miden-utils-diagnostics.workspace = true -thiserror.workspace = true rowan = { package = "miden-rowan", version = "0.16", default-features = false } [dev-dependencies] diff --git a/crates/assembly-syntax/src/parser/value.rs b/crates/assembly-syntax/src/parser/value.rs index dc677a4cc7..fac28e0368 100644 --- a/crates/assembly-syntax/src/parser/value.rs +++ b/crates/assembly-syntax/src/parser/value.rs @@ -417,11 +417,11 @@ impl proptest::arbitrary::Arbitrary for IntValue { #[inline] pub(crate) fn shrink_u64_hex(n: u64) -> IntValue { - if n <= (u8::MAX as u64) { + if u8::try_from(n).is_ok() { IntValue::U8(n as u8) - } else if n <= (u16::MAX as u64) { + } else if u16::try_from(n).is_ok() { IntValue::U16(n as u16) - } else if n <= (u32::MAX as u64) { + } else if u32::try_from(n).is_ok() { IntValue::U32(n as u32) } else { IntValue::Felt(Felt::new_unchecked(n)) diff --git a/crates/debug-types/src/span.rs b/crates/debug-types/src/span.rs index 287a540448..7f9a2be3f7 100644 --- a/crates/debug-types/src/span.rs +++ b/crates/debug-types/src/span.rs @@ -48,6 +48,7 @@ impl Spanned for alloc::sync::Arc { /// This type is used to wrap any `T` with a [SourceSpan], and is typically used when it is not /// convenient to add a [SourceSpan] to the type - most commonly because we don't control the type. +#[derive(Clone, Copy)] pub struct Span { span: SourceSpan, spanned: T, @@ -94,17 +95,6 @@ impl Spanned for Span { } } -impl Copy for Span {} - -impl Clone for Span { - fn clone(&self) -> Self { - Self { - span: self.span, - spanned: self.spanned.clone(), - } - } -} - impl Default for Span { fn default() -> Self { Self { diff --git a/crates/lib/core/Cargo.toml b/crates/lib/core/Cargo.toml index bd78fcb122..fe497e1765 100644 --- a/crates/lib/core/Cargo.toml +++ b/crates/lib/core/Cargo.toml @@ -35,7 +35,7 @@ std = [ "miden-utils-sync/std", ] testing = ["arbitrary"] -constraints-tools = ["std", "dep:miden-air", "dep:miden-ace-codegen", "dep:miden-constraint-compiler"] +constraints-tools = ["std", "dep:miden-air", "dep:miden-constraint-compiler"] [dependencies] # Miden dependencies @@ -46,7 +46,6 @@ miden-precompiles.workspace = true miden-processor.workspace = true miden-utils-sync.workspace = true miden-air = { workspace = true, optional = true } -miden-ace-codegen = { workspace = true, optional = true } miden-constraint-compiler = { workspace = true, optional = true } # External dependencies diff --git a/crates/lib/core/build.rs b/crates/lib/core/build.rs index 2baf58987c..8c9500f814 100644 --- a/crates/lib/core/build.rs +++ b/crates/lib/core/build.rs @@ -172,6 +172,14 @@ fn local_procedure_docs(module: &Module) -> Vec<(String, Option)> { procedures } +/// Resolve the documentation for a re-exported item. +/// +/// The outer `Option` distinguishes "target procedure not found" (skip the +/// re-export entirely) from "found", and the inner `Option` carries the docs +/// (`None` when the target has no doc comment). Encoding these three states +/// without nesting would require a bespoke enum, so we allow `option_option` +/// here deliberately. +#[allow(clippy::option_option)] fn reexport_target_docs( import: &ast::ItemImport, current_module_path: &miden_assembly_syntax::Path, diff --git a/crates/lib/core/tests/pcs/fri/channel.rs b/crates/lib/core/tests/pcs/fri/channel.rs deleted file mode 100644 index d680d69b1d..0000000000 --- a/crates/lib/core/tests/pcs/fri/channel.rs +++ /dev/null @@ -1,81 +0,0 @@ -use miden_core::Word; -use miden_utils_testing::{ - Felt, FieldElement, MerkleTreeVC, - crypto::{BatchMerkleProof, ElementHasher, Hasher as HasherTrait, PartialMerkleTree}, - serde::DeserializationError, -}; -use winter_fri::{FriProof, VerifierError}; - -pub trait UnBatch { - fn unbatch( - &mut self, - positions: &[usize], - domain_size: usize, - layer_commitments: Vec<::Digest>, - ) -> (Vec, Vec<(Word, Vec)>); -} - -pub struct MidenFriVerifierChannel< - E: FieldElement, - H: ElementHasher + ElementHasher, -> { - layer_commitments: Vec, - layer_proofs: Vec>, - layer_queries: Vec>, - remainder: Vec, -} - -impl MidenFriVerifierChannel -where - E: FieldElement, - H: ElementHasher + ElementHasher, -{ - /// Builds a new verifier channel from the specified [FriProof]. - /// - /// # Errors - /// Returns an error if the specified `proof` could not be parsed correctly. - pub fn new( - proof: FriProof, - layer_commitments: Vec, - domain_size: usize, - folding_factor: usize, - ) -> Result { - let remainder = proof.parse_remainder()?; - let (layer_queries, layer_proofs) = - proof.parse_layers::>(domain_size, folding_factor)?; - - Ok(MidenFriVerifierChannel { - layer_commitments, - layer_proofs, - layer_queries, - remainder, - }) - } - - pub fn take_fri_remainder(&mut self) -> Vec { - self.remainder.clone() - } - - pub fn layer_proofs(&mut self) -> Vec> { - self.layer_proofs.drain(..).collect() - } - - pub fn layer_queries(&mut self) -> Vec> { - self.layer_queries.clone() - } - - pub fn read_fri_layer_commitments(&mut self) -> Vec { - self.layer_commitments.drain(..).collect() - } - - pub fn read_remainder( - &mut self, - expected_commitment: &::Digest, - ) -> Result, VerifierError> { - let poly = self.take_fri_remainder(); - let commitment = H::hash_elements(&poly); - assert_eq!(&commitment, expected_commitment); - - Ok(poly) - } -} diff --git a/crates/lib/core/tests/pcs/fri/mod.rs b/crates/lib/core/tests/pcs/fri/mod.rs deleted file mode 100644 index 27d4b8fc04..0000000000 --- a/crates/lib/core/tests/pcs/fri/mod.rs +++ /dev/null @@ -1,281 +0,0 @@ -use alloc::collections::BTreeMap; - -use miden_utils_testing::{Felt, PrimeField64, crypto::MerkleStore}; - -mod channel; - -pub(crate) mod verifier_fri_e2f4; -use miden_core::Word; -pub use verifier_fri_e2f4::*; - -const FRI_PREPROCESS_SOURCE: &str = " - use miden::core::stark::constants - - const MAX_FRI_QUERIES = 150 - const MAX_FRI_LAYERS = 32 - const MAX_FRI_REMAINDER_WORDS = 64 - - proc preprocess - adv_push - # => [num_queries, g, ...] - dup u32gt.0 assert.err=\"number of FRI queries must be nonzero\" - dup u32lte.MAX_FRI_QUERIES assert.err=\"number of FRI queries exceeds FRI workspace\" - - exec.constants::fri_com_ptr - # => [layer_ptr, num_queries, g, ...] - dup.1 mul.4 sub - # => [query_ptr, num_queries, g, ...] - dup exec.constants::set_fri_queries_address - swap - sub.1 - padw - push.1 - while.true - adv_loadw - dup.5 - u32wrapping_add.4 - swap.6 - mem_storew_le - dup.4 - sub.1 - swap.5 - neq.0 - end - #=> [X, x, layer_ptr, g] - - drop - #=> [X, layer_ptr, g] - - dup.4 - movdn.5 - #=> [X, layer_ptr, layer_ptr, g] - - adv_push - dup u32lte.MAX_FRI_LAYERS assert.err=\"number of FRI layers exceeds FRI workspace\" - - dup push.0 neq - if.true - mul.2 - sub.1 - movdn.4 - #=> [X, num_layers, layer_ptr, layer_ptr, g] - - push.1 - while.true - adv_loadw - dup.5 - u32wrapping_add.4 - swap.6 - mem_storew_le - dup.4 - sub.1 - swap.5 - neq.0 - end - #=> [X, x, remainder_poly_ptr, layer_ptr, g] - - drop - else - drop - end - #=> [X, remainder_poly_ptr, layer_ptr, g] - - dup.4 - movdn.5 - #=> [X, remainder_poly_ptr, remainder_poly_ptr, layer_ptr, g] - - adv_push - dup u32gt.0 assert.err=\"FRI remainder polynomial must be nonzero\" - dup u32lte.MAX_FRI_REMAINDER_WORDS assert.err=\"FRI remainder polynomial exceeds FRI workspace\" - - dup mul.2 exec.constants::set_remainder_poly_size - - sub.1 - movdn.4 - #=> [X, len_remainder/2, remainder_poly_ptr, remainder_poly_ptr, layer_ptr, g] - - push.1 - while.true - adv_loadw - dup.5 - u32wrapping_add.4 - swap.6 - mem_storew_le - dup.4 - sub.1 - swap.5 - neq.0 - end - #=> [X, x, x, remainder_poly_ptr, layer_ptr, g] - dropw drop drop - #=> [remainder_poly_ptr, layer_ptr, g] - - exec.constants::set_remainder_poly_address - drop drop - end -"; - -#[test] -fn fri_verify_rejects_empty_query_region() { - let source = " - use miden::core::pcs::fri::frie2f4 - use miden::core::stark::constants - - begin - push.1 exec.constants::set_lde_domain_generator - push.64 exec.constants::set_remainder_poly_size - push.4294912800 exec.constants::set_remainder_poly_address - push.4294912800 exec.constants::set_fri_queries_address - exec.frie2f4::verify - end - "; - - let test = build_test!(source, &[]); - expect_assert_error_message!(test, contains "fri query region must be non-empty"); -} - -#[test] -fn fri_fold4_ext2_remainder64() { - let source = format!( - "{FRI_PREPROCESS_SOURCE} - use miden::core::pcs::fri::frie2f4 - - begin - exec.preprocess - exec.frie2f4::verify - end - " - ); - - let trace_len_e = 14; - let blowup_exp = 3; - let depth = trace_len_e + blowup_exp; - let domain_size = 1 << depth; - - let FriResult { - partial_trees, - advice_maps, - positions, - alphas, - commitments, - remainder, - num_queries, - } = fri_prove_verify_fold4_ext2(trace_len_e).unwrap(); - - let advice_stack = prepare_advice_stack( - depth, - domain_size, - num_queries, - positions, - alphas, - commitments, - remainder, - ); - - let advice_map: BTreeMap> = BTreeMap::from_iter(advice_maps); - let domain_generator = Felt::get_root_of_unity(domain_size.ilog2()).as_canonical_u64(); - - let mut store = MerkleStore::new(); - for partial_tree in &partial_trees { - store.extend(partial_tree.inner_nodes()); - } - let test = build_test!(&source, &[domain_generator], &advice_stack, store, advice_map.clone()); - - test.expect_stack(&[]); -} - -#[test] -fn fri_fold4_ext2_remainder128() { - let source = format!( - "{FRI_PREPROCESS_SOURCE} - use miden::core::pcs::fri::frie2f4 - - begin - exec.preprocess - exec.frie2f4::verify - end - " - ); - - let trace_len_e = 13; - let blowup_exp = 3; - let depth = trace_len_e + blowup_exp; - let domain_size = 1 << depth; - - let FriResult { - partial_trees, - advice_maps, - positions, - alphas, - commitments, - remainder, - num_queries, - } = fri_prove_verify_fold4_ext2(trace_len_e).unwrap(); - - let advice_stack = prepare_advice_stack( - depth, - domain_size, - num_queries, - positions, - alphas, - commitments, - remainder, - ); - - let advice_map: BTreeMap> = BTreeMap::from_iter(advice_maps); - let domain_generator = Felt::get_root_of_unity(domain_size.ilog2()).as_canonical_u64(); - - let mut store = MerkleStore::new(); - for partial_tree in &partial_trees { - store.extend(partial_tree.inner_nodes()); - } - let test = build_test!(&source, &[domain_generator], &advice_stack, store, advice_map.clone()); - - test.expect_stack(&[]); -} - -fn prepare_advice_stack( - depth: usize, - domain_size: u32, - num_queries: usize, - position_eval: Vec, - alphas: Vec, - com: Vec, - remainder: Vec, -) -> Vec { - let mut stack = vec![]; - let remainder_length = remainder.len() / 2; - let num_layers = (com.len() / 4) - 1; - - stack.push(num_queries as u64); - - stack.extend_from_slice(&position_eval[..]); - - stack.push(num_layers as u64); - - let mut current_domain_size = domain_size as u64; - let mut current_depth = depth as u64; - - for i in 0..num_layers { - current_domain_size /= 4; - - stack.extend_from_slice(&com[(4 * i)..(4 * i + 4)]); - stack.extend_from_slice(&alphas[(4 * i)..(4 * i + 2)]); - // - 2 is due to the fact that we are folding by 4 - stack.extend_from_slice(&[current_depth - 2, current_domain_size]); - current_depth -= 2; - } - - stack.push(remainder_length as u64 / 2); - for i in 0..remainder_length / 2 { - let mut remainder_4 = vec![0; 4]; - remainder_4[0] = remainder[4 * i]; - remainder_4[1] = remainder[4 * i + 1]; - remainder_4[2] = remainder[4 * i + 2]; - remainder_4[3] = remainder[4 * i + 3]; - - stack.extend_from_slice(&remainder_4); - } - - stack -} diff --git a/crates/lib/core/tests/pcs/fri/verifier_fri_e2f4.rs b/crates/lib/core/tests/pcs/fri/verifier_fri_e2f4.rs deleted file mode 100644 index acbf25ad73..0000000000 --- a/crates/lib/core/tests/pcs/fri/verifier_fri_e2f4.rs +++ /dev/null @@ -1,476 +0,0 @@ -use core::{marker::PhantomData, mem}; - -use miden_core::{EMPTY_WORD, Felt, FieldElement, QuadFelt, Word}; -use miden_processor::crypto::{Hasher, RandomCoin, WinterRandomCoin}; -use miden_utils_testing::{ - MerkleTreeVC, PrimeField64, - crypto::{MerklePath, NodeIndex, PartialMerkleTree, Poseidon2 as MidenHasher}, - group_slice_elements, - math::fft, -}; -use winter_fri::{ - DefaultProverChannel, FriOptions, FriProof, FriProver, VerifierError, folding::fold_positions, -}; - -use super::channel::{MidenFriVerifierChannel, UnBatch}; - -const MAX_REMAINDER_POLY_DEGREE: usize = 128; -const FRI_FOLDING_FACTOR: usize = 4; -const BLOWUP_FACTOR: usize = 8; -const NUM_FRI_QUERIES: usize = 32; - -type AdvMap = Vec<(Word, Vec)>; - -pub struct FriResult { - /// A vector containing the Merkle authentication paths used to authenticate the queries. - pub partial_trees: Vec, - - /// A map used to unhash Merkle nodes to a sequence of field elements representing the - /// query-values. - pub advice_maps: AdvMap, - - /// A vector of consecutive quadruples of the form (poe, p, e1, e0) where p is index of the - /// query at the first layer and (e1, e0) is its corresponding evaluation and poe is g^p with g - /// being the initial domain generator. - pub positions: Vec, - - /// A vector of tuples representing the folding challenges. - pub alphas: Vec, - - /// A vector of consecutive quadruples (c3, c2, c1, c0) representing the Merkle tree layer - /// commitments. - pub commitments: Vec, - - /// The remainder codeword as consecutive (r0, r1). - pub remainder: Vec, - - /// The number of queries contained in the current FRI proof. - pub num_queries: usize, -} - -// This function proves and then verifies a FRI proof with the following fixed parameters: -// 1) Max remainder codeword (1 << 6). -// 2) Blow up factor 8. -// 3) Folding factor 4. -// -// The main purpose of this function is to build the non-deterministic inputs needed to verify -// a FRI proof inside the Miden VM. -// The output is organized as follows: -pub fn fri_prove_verify_fold4_ext2(trace_length_e: usize) -> Result { - let trace_length = 1 << trace_length_e; - let lde_blowup = BLOWUP_FACTOR; - let max_remainder_size = MAX_REMAINDER_POLY_DEGREE; - let folding_factor = FRI_FOLDING_FACTOR; - let nonce = 0_u64; - - let options = FriOptions::new(lde_blowup, folding_factor, max_remainder_size); - let mut channel = build_prover_channel(trace_length, &options); - let evaluations = build_evaluations(trace_length, lde_blowup); - - // instantiate the prover and generate the proof - let mut prover = FriProver::<_, _, _, MerkleTreeVC>::new(options.clone()); - prover.build_layers(&mut channel, evaluations.clone()); - let positions = channel.draw_query_positions(nonce); - let proof = prover.build_proof(&positions); - - // make sure the proof can be verified - let commitments = channel.layer_commitments().to_vec(); - - let max_degree = trace_length - 1; - let result = verify_proof( - proof.clone(), - commitments.clone(), - &evaluations, - max_degree, - trace_length * lde_blowup, - &positions, - &options, - ); - let commitments: Vec = channel - .layer_commitments() - .to_vec() - .iter() - .flat_map(|digest| digest.as_elements().iter().map(|e| e.as_canonical_u64())) - .collect(); - - let remainder_poly: Vec = - proof.parse_remainder().expect("should return remainder polynomial"); - let remainder: Vec = QuadFelt::slice_as_base_elements(&remainder_poly[..]) - .to_owned() - .iter() - .map(|a| a.as_canonical_u64()) - .collect(); - - match result { - Ok(((partial_trees, advice_maps), all_position_evaluation, alphas)) => Ok(FriResult { - partial_trees, - advice_maps, - positions: all_position_evaluation, - alphas, - commitments, - remainder, - num_queries: positions.len(), - }), - Err(err) => Err(err), - } -} - -// HELPER UTILS -// ================================================================================================ - -pub fn build_prover_channel( - trace_length: usize, - options: &FriOptions, -) -> DefaultProverChannel> { - DefaultProverChannel::new(trace_length * options.blowup_factor(), NUM_FRI_QUERIES) -} - -pub fn build_evaluations(trace_length: usize, lde_blowup: usize) -> Vec { - let mut p = (0..trace_length as u32) - .map(|i| (i, i)) - .map(|(i, j)| QuadFelt::new([i.into(), j.into()])) - .collect::>(); - let domain_size = trace_length * lde_blowup; - p.resize(domain_size, QuadFelt::ZERO); - - let twiddles = fft::get_twiddles::(domain_size); - - fft::evaluate_poly(&mut p, &twiddles); - p -} - -#[expect(clippy::type_complexity)] -fn verify_proof( - proof: FriProof, - commitments: Vec<::Digest>, - evaluations: &[QuadFelt], - max_degree: usize, - domain_size: usize, - positions: &[usize], - options: &FriOptions, -) -> Result<((Vec, AdvMap), Vec, Vec), VerifierError> { - let mut channel = MidenFriVerifierChannel::::new( - proof, - commitments.clone(), - domain_size, - options.folding_factor(), - ) - .unwrap(); - let mut coin = WinterRandomCoin::new(&[]); - - let miden_verifier = - FriVerifierFold4Ext2::new(&mut channel, &mut coin, options.clone(), max_degree)?; - - let queried_evaluations = positions.iter().map(|&p| evaluations[p]).collect::>(); - - let result = - miden_verifier.verify_fold_4_ext_2(&mut channel, &queried_evaluations, positions)?; - - Ok(result) -} - -/// Partial implementation for verification in the case of folding factor 4 -pub struct FriVerifierFold4Ext2 { - domain_size: usize, - domain_generator: Felt, - layer_commitments: Vec, - layer_alphas: Vec, - options: FriOptions, - _channel: PhantomData>, -} - -impl FriVerifierFold4Ext2 { - pub fn new( - channel: &mut MidenFriVerifierChannel, - public_coin: &mut WinterRandomCoin, - options: FriOptions, - max_poly_degree: usize, - ) -> Result { - assert_eq!(options.blowup_factor(), BLOWUP_FACTOR); - assert_eq!(options.folding_factor(), FRI_FOLDING_FACTOR); - - // infer evaluation domain info - let domain_size = max_poly_degree.next_power_of_two() * options.blowup_factor(); - let domain_generator = Felt::get_root_of_unity(domain_size.ilog2()); - - // read layer commitments from the channel and use them to build a list of alphas - let layer_commitments = channel.read_fri_layer_commitments(); - let mut layer_alphas = Vec::with_capacity(layer_commitments.len()); - let mut max_degree_plus_1 = max_poly_degree + 1; - for (depth, commitment) in layer_commitments.iter().enumerate() { - public_coin.reseed(*commitment); - let alpha = public_coin.draw().map_err(VerifierError::RandomCoinError)?; - layer_alphas.push(alpha); - - // make sure the degree can be reduced by the folding factor at all layers - // but the remainder layer - if depth != layer_commitments.len() - 1 - && !max_degree_plus_1.is_multiple_of(options.folding_factor()) - { - return Err(VerifierError::DegreeTruncation( - max_degree_plus_1 - 1, - options.folding_factor(), - depth, - )); - } - max_degree_plus_1 /= options.folding_factor(); - } - - Ok(FriVerifierFold4Ext2 { - domain_size, - domain_generator, - layer_commitments, - layer_alphas, - options, - _channel: PhantomData, - }) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns size of the domain over which a polynomial commitment checked by this verifier - /// has been evaluated. - /// - /// The domain size can be computed by rounding `max_poly_degree` to the next power of two - /// and multiplying the result by the `blowup_factor` from the protocol options. - pub fn domain_size(&self) -> usize { - self.domain_size - } - - /// Verifier in the setting of (folding_factor, blowup_factor, extension_degree) = (4, (1 << 3), - /// 2) - #[expect(clippy::type_complexity)] - fn verify_fold_4_ext_2( - &self, - channel: &mut MidenFriVerifierChannel, - evaluations: &[QuadFelt], - positions: &[usize], - ) -> Result<((Vec, AdvMap), Vec, Vec), VerifierError> { - // 1 ----- verify the recursive components of the FRI proof ------------------------------- - let positions = positions.to_vec(); - let evaluations = evaluations.to_vec(); - let mut final_pos_eval: Vec<(usize, QuadFelt)> = vec![]; - let advice_provider = - channel.unbatch::<4, 3>(&positions, self.domain_size(), self.layer_commitments.clone()); - - let mut d_generator = self.domain_generator; - let mut all_alphas = vec![]; - let mut all_position_evaluation = vec![]; - for (index, &position) in positions.iter().enumerate() { - d_generator = self.domain_generator; - let (cur_pos, evaluation, position_evaluation, alphas) = iterate_query_fold_4_quad_ext( - &self.layer_alphas, - &advice_provider.0, - &advice_provider.1, - position, - self.options.num_fri_layers(self.domain_size()), - self.domain_size(), - &evaluations[index], - &mut d_generator, - )?; - all_position_evaluation.extend_from_slice(&position_evaluation[..]); - all_alphas = alphas; - - final_pos_eval.push((cur_pos, evaluation)); - } - - // 2 ----- verify the remainder of the FRI proof ------------------------------------------ - - // read the remainder from the channel and make sure it matches with the columns - // of the previous layer - let remainder_commitment = self.layer_commitments.last().unwrap(); - let remainder_poly = channel.read_remainder(remainder_commitment)?; - let offset = Felt::GENERATOR; - for &(final_pos, final_eval) in final_pos_eval.iter() { - let comp_eval = eval_horner_rev( - &remainder_poly, - offset * d_generator.exp_vartime(final_pos as u64), - ); - if comp_eval != final_eval { - return Err(VerifierError::InvalidRemainderFolding); - } - } - - Ok((advice_provider, all_position_evaluation, all_alphas)) - } -} - -fn iterate_query_fold_4_quad_ext( - layer_alphas: &[QuadFelt], - partial_trees: &[PartialMerkleTree], - key_val_map: &[(Word, Vec)], - position: usize, - number_of_layers: usize, - initial_domain_size: usize, - evaluation: &QuadFelt, - domain_generator: &mut Felt, -) -> Result<(usize, QuadFelt, Vec, Vec), VerifierError> { - let mut cur_pos = position; - let mut evaluation = *evaluation; - let mut domain_size = initial_domain_size; - let get_domain_offset = Felt::GENERATOR; - - let initial_domain_generator = *domain_generator; - let norm_cst = Felt::get_root_of_unity(2).inv(); - let mut init_exp = initial_domain_generator.exp(position as u64); - - let arr = vec![evaluation]; - let a = QuadFelt::slice_as_base_elements(&arr); - - let position_evaluation = vec![ - a[0].as_canonical_u64(), - a[1].as_canonical_u64(), - position as u64, - init_exp.as_canonical_u64(), - ]; - - let mut alphas = vec![]; - for depth in 0..number_of_layers { - let target_domain_size = domain_size / FRI_FOLDING_FACTOR; - - let folded_pos = cur_pos % target_domain_size; - - // Assumes the num_partitions == 1 - let position_index = folded_pos; - - let tree_depth = target_domain_size.ilog2(); - - let query_nodes = partial_trees[depth] - .get_node(NodeIndex::new(tree_depth as u8, position_index as u64).unwrap()) - .unwrap(); - let query_values = &key_val_map - .iter() - .find(|(k, _)| *k == query_nodes) - .expect("must contain the leaf values") - .1; - - let query_values = [ - QuadFelt::new([query_values[0], query_values[1]]), - QuadFelt::new([query_values[2], query_values[3]]), - QuadFelt::new([query_values[4], query_values[5]]), - QuadFelt::new([query_values[6], query_values[7]]), - ]; - - let query_value = query_values[cur_pos / target_domain_size]; - - if evaluation != query_value { - return Err(VerifierError::InvalidLayerFolding(depth)); - } - - let xs_new = match cur_pos / target_domain_size { - 0 => init_exp, - 1 => init_exp * norm_cst, - 2 => init_exp * (norm_cst * norm_cst), - _ => init_exp * (norm_cst * norm_cst * norm_cst), - } * get_domain_offset; - - init_exp = init_exp * init_exp * init_exp * init_exp; - - evaluation = { - let f_minus_x = query_values[2]; - let f_x = query_values[0]; - let x_star = QuadFelt::from(xs_new); - let alpha = layer_alphas[depth]; - - let tmp0 = fri_2(f_x, f_minus_x, x_star, alpha); - - let f_minus_x = query_values[3]; - let f_x = query_values[1]; - let alpha = layer_alphas[depth]; - - let tmp1 = fri_2(f_x, f_minus_x, x_star * QuadFelt::from(norm_cst.inv()), alpha); - - fri_2(tmp0, tmp1, x_star * x_star, alpha * alpha) - }; - - let arr = vec![layer_alphas[depth]]; - let a = QuadFelt::slice_as_base_elements(&arr); - alphas.push(a[0].as_canonical_u64()); - alphas.push(a[1].as_canonical_u64()); - alphas.push(0); - alphas.push(0); - - *domain_generator = (*domain_generator).exp((FRI_FOLDING_FACTOR as u32).into()); - cur_pos = folded_pos; - domain_size /= FRI_FOLDING_FACTOR; - } - - Ok((cur_pos, evaluation, position_evaluation, alphas)) -} - -impl UnBatch for MidenFriVerifierChannel { - fn unbatch( - &mut self, - positions_: &[usize], - domain_size: usize, - layer_commitments: Vec, - ) -> (Vec, Vec<(Word, Vec)>) { - let queries = self.layer_queries().clone(); - let mut current_domain_size = domain_size; - let mut positions = positions_.to_vec(); - let depth = layer_commitments.len() - 1; - - let mut adv_key_map = vec![]; - let mut partial_trees = vec![]; - let mut layer_proofs = self.layer_proofs(); - for query in queries.iter().take(depth) { - let mut folded_positions = fold_positions(&positions, current_domain_size, N); - - let layer_proof = layer_proofs.remove(0); - - let x = group_slice_elements::(query); - let leaves: Vec = x.iter().map(|row| MidenHasher::hash_elements(row)).collect(); - let unbatched_proof = layer_proof.into_openings(&leaves, &folded_positions).unwrap(); - assert_eq!(x.len(), unbatched_proof.len()); - - let nodes: Vec<[Felt; 4]> = - leaves.iter().map(|leaf| [leaf[0], leaf[1], leaf[2], leaf[3]]).collect(); - - let paths: Vec = - unbatched_proof.into_iter().map(|list| list.1.into()).collect(); - - let iter_pos = folded_positions.iter_mut().map(|a| *a as u64); - let nodes_tmp = nodes.clone(); - let iter_nodes = nodes_tmp.iter(); - let iter_paths = paths.into_iter(); - let mut tmp_vec = vec![]; - for (p, (node, path)) in iter_pos.zip(iter_nodes.zip(iter_paths)) { - tmp_vec.push((p, Word::from(*node), path)); - } - - let new_set = - PartialMerkleTree::with_paths(tmp_vec).expect("should not fail from paths"); - partial_trees.push(new_set); - - nodes.into_iter().zip(x.iter()).for_each(|(a, b)| { - let mut value = QuadFelt::slice_as_base_elements(b).to_owned(); - value.extend(EMPTY_WORD); - adv_key_map.push((a.to_owned().into(), value)); - }); - - mem::swap(&mut positions, &mut folded_positions); - current_domain_size /= N; - } - - (partial_trees, adv_key_map) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn fri_2(f_x: E, f_minus_x: E, x_star: E, alpha: E) -> E -where - B: PrimeField64, - E: FieldElement, -{ - (f_x + f_minus_x + ((f_x - f_minus_x) * alpha / x_star)) / E::ONE.double() -} - -pub fn eval_horner_rev(p: &[E], x: E::BaseField) -> E -where - E: FieldElement, -{ - p.iter().fold(E::ZERO, |acc, &coeff| acc * E::from(x) + coeff) -} diff --git a/crates/lib/core/tests/pcs/mod.rs b/crates/lib/core/tests/pcs/mod.rs deleted file mode 100644 index 4bb3590380..0000000000 --- a/crates/lib/core/tests/pcs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod fri; diff --git a/crates/mast-package/Cargo.toml b/crates/mast-package/Cargo.toml index 943cbc80e7..15a66b578e 100644 --- a/crates/mast-package/Cargo.toml +++ b/crates/mast-package/Cargo.toml @@ -48,5 +48,3 @@ proptest.workspace = true proptest-derive.workspace = true serde_json.workspace = true -[package.metadata.cargo-shear] -ignored = ["serde_json"] diff --git a/crates/precompiles/benches/precompiles_bench/support.rs b/crates/precompiles/benches/precompiles_bench/support.rs index c65ca15ae3..bcb7f42adf 100644 --- a/crates/precompiles/benches/precompiles_bench/support.rs +++ b/crates/precompiles/benches/precompiles_bench/support.rs @@ -38,7 +38,7 @@ impl PrecompileFixture { } fn generate_stack_inputs(workload: PrecompileWorkload) -> StackInputs { - assert!(workload.keccaks <= u32::MAX as usize, "Keccak workload count must fit in u32"); + assert!(u32::try_from(workload.keccaks).is_ok(), "Keccak workload count must fit in u32"); StackInputs::new(&[Felt::new_unchecked(workload.keccaks as u64)]) .expect("single Keccak count should fit on the operand stack") } diff --git a/processor/src/trace/chiplets/ace/trace.rs b/processor/src/trace/chiplets/ace/trace.rs index db8bb444bd..6abdbbf4bd 100644 --- a/processor/src/trace/chiplets/ace/trace.rs +++ b/processor/src/trace/chiplets/ace/trace.rs @@ -216,7 +216,8 @@ impl CircuitEvaluation { eval.m_0 = m_0; } - debug_assert!(multiplicities_iter.next().is_none()); + let next = multiplicities_iter.next(); + debug_assert!(next.is_none()); } /// Returns the output value, if the circuit has finished evaluating. diff --git a/processor/src/trace/chiplets/hasher/mod.rs b/processor/src/trace/chiplets/hasher/mod.rs index e4901aaf8e..15d453e1d3 100644 --- a/processor/src/trace/chiplets/hasher/mod.rs +++ b/processor/src/trace/chiplets/hasher/mod.rs @@ -259,7 +259,8 @@ impl Hasher { // Last batch: boundary output only. let last_batch = batch_groups.next().expect("multi-batch block has a final op batch"); - debug_assert!(batch_groups.next().is_none()); + let next = batch_groups.next(); + debug_assert!(next.is_none()); absorb_into_state(&mut state, last_batch); let permuted = self.append_controller_permutation( LINEAR_HASH, diff --git a/processor/src/trace/parallel/tests.rs b/processor/src/trace/parallel/tests.rs index 2f8a806004..065394ac03 100644 --- a/processor/src/trace/parallel/tests.rs +++ b/processor/src/trace/parallel/tests.rs @@ -1320,7 +1320,7 @@ fn collect_end_flags(trace: &ExecutionTrace) -> Vec { fn read_opcode(main_trace: &MainTrace, row_idx: RowIndex) -> u8 { let opcode = main_trace.get_op_code(row_idx).as_canonical_u64(); - assert!(opcode <= u8::MAX as u64, "invalid opcode"); + assert!(u8::try_from(opcode).is_ok(), "invalid opcode"); opcode as u8 } diff --git a/processor/src/trace/trace_state.rs b/processor/src/trace/trace_state.rs index c0b726f707..07a0bb8e55 100644 --- a/processor/src/trace/trace_state.rs +++ b/processor/src/trace/trace_state.rs @@ -387,6 +387,11 @@ pub struct NodeFlags { impl NodeFlags { /// Creates a new instance of `NodeFlags`. + /// + /// The four booleans are independent flags packed into the hasher state's + /// second word for an END operation; there is no natural smaller grouping, + /// so we accept the bool parameters explicitly. + #[allow(clippy::fn_params_excessive_bools)] pub fn new(is_loop_body: bool, is_loop: bool, is_call: bool, is_syscall: bool) -> Self { Self { is_loop_body, diff --git a/tools/miden-core-fuzz/Cargo.lock b/tools/miden-core-fuzz/Cargo.lock index 96833bd7cf..d695ddee7e 100644 --- a/tools/miden-core-fuzz/Cargo.lock +++ b/tools/miden-core-fuzz/Cargo.lock @@ -756,7 +756,6 @@ dependencies = [ "miden-debug-types", "miden-rowan", "miden-utils-diagnostics", - "thiserror", ] [[package]]

GmU?2J{Z`iUVz#6 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 H@K^r_Co*Z& literal 143626 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